Skip to content

Tighten native consolidation contract gates after MRZ unification#1826

Merged
transphorm merged 1 commit intodevfrom
justin/native-consolidation-phase3
Mar 7, 2026
Merged

Tighten native consolidation contract gates after MRZ unification#1826
transphorm merged 1 commit intodevfrom
justin/native-consolidation-phase3

Conversation

@transphorm
Copy link
Copy Markdown
Member

@transphorm transphorm commented Mar 7, 2026

This PR is a follow-up to #1823 and keeps scope limited to contract cleanup.

It corrects drift between the current PassportReader/NFC test baseline and the real app-facing interface, and trims CONTRACTS.md so it only documents verified, test-enforced facts. It also adds exact platform-specific assertions for NFC module-unavailable error text so both iOS and Android
behavior are pinned.

This PR does not change native MRZ code, does not start PassportReader consolidation, and does not alter public native module names.

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced NFC scan error messages with additional installation guidance for improved troubleshooting.
  • Tests

    • Updated NFC scanner tests to validate error message content.
    • Refactored PassportReader tests to use standalone reset function export.
    • Adjusted API parameter expectations for scanner operations.
  • Chores

    • Updated contract documentation and test mock configurations.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 7, 2026

📝 Walkthrough

Walkthrough

The pull request restructures the PassportReader mock implementation by converting the reset method to a standalone exported function. The scanPassport method arity increases from 9 to 10 parameters to accommodate a new sessionId parameter. Test assertions and contract documentation are updated to reflect these structural changes.

Changes

Cohort / File(s) Summary
Mock Setup & Arity
app/jest.setup.js, src/integrations/nfc/passportReader
Removed public reset method from PassportReader mock object; increased scanPassport parameter count from 9 to 10 to include sessionId.
PassportReader Tests
app/tests/src/integrations/nfc/passportReader.test.ts
Added standalone reset() export from PassportReader module; updated test imports and invocations to use the new export directly; adjusted scanPassport arity expectation to 10.
NFC Scanner Tests
app/tests/src/integrations/nfc/nfcScanner.test.ts
Refactored error assertions to use toMatchObject for message field validation instead of toThrow; added new guidance text "Please ensure the app is properly installed." to error messages.
Contract Documentation
specs/projects/sdk/workstreams/native-consolidation/CONTRACTS.md
Updated contract header and reframed scope; added "Dispatch and Invocation Contract" section; refined PassportReader interface contract to specify reset as standalone export and updated scanPassport.length expectation from 9 to 10.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #978: Modifies react-native-passport-reader mock and PassportReader exports/arity in opposing directions.
  • #1822: Updates NFC PassportReader bridge tests and CONTRACTS with compatible scanPassport arity and reset export changes.
  • #979: Touches PassportReader test mocks and scanPassport/reset exports/arity.

Suggested labels

codex

Suggested reviewers

  • aaronmgdr

Poem

🔑 The reset stands alone, no longer bound,
Ten parameters now guide the scan's rebound,
Exports shine clear through the contract's sight,
Standalone functions make the interface right,
Native paths unified, working in flight! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description addresses the PR's purpose and scope but lacks the required template sections: Tested and How to QA details are missing. Add 'Tested' and 'How to QA' sections explaining how the contract changes and integration test updates have been validated.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective: tightening native consolidation contract gates following MRZ unification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin/native-consolidation-phase3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@transphorm transphorm changed the title update integration tests Tighten native consolidation contract gates after MRZ unification Mar 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
specs/projects/sdk/workstreams/native-consolidation/CONTRACTS.md (1)

27-27: ⚠️ Potential issue | 🟠 Major

The Android ordering guarantee is stronger than the test coverage.

In app/tests/src/integrations/nfc/nfcScanner.test.ts, Lines 489-490 only verify that reset() and Android scan(...) were both called; they do not verify that reset() happened first. Since this file says it only records facts pinned by tests, the "reset() then scan(...)" wording is too strong unless you also add an ordering assertion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specs/projects/sdk/workstreams/native-consolidation/CONTRACTS.md` at line 27,
The CONTRACTS.md statement "scan() must call reset() then call Android
scan(...)" overstates the tests; either update the wording in CONTRACTS.md to
say the tests only assert that reset() and Android scan(...) were both called
(without ordering), or add an ordering assertion to the integration test
app/tests/src/integrations/nfc/nfcScanner.test.ts (around the calls to reset()
and Android scan(...)) to assert reset() occurred before scan(); locate the test
that references reset() and scan() and either relax the CONTRACTS.md language or
add a strict ordering check in the test so the contract matches the verified
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/tests/src/integrations/nfc/nfcScanner.test.ts`:
- Around line 426-429: The test assertion for isolatedScan currently uses
rejects.toMatchObject({ message: 'NFC scanning is currently unavailable. Please
ensure the app is properly installed.' }) which only checks for a message
property; update the assertion to use rejects.toThrow('NFC scanning is currently
unavailable. Please ensure the app is properly installed.') so the test verifies
the rejection is an Error instance with the exact message; apply the same change
for the similar assertion at the other occurrence (around the isolatedScan / NFC
failure tests) to enforce Error type + message semantics.

In `@app/tests/src/integrations/nfc/passportReader.test.ts`:
- Around line 31-32: Remove the brittle Function.length assertion for
PassportReader.scanPassport (it only validates mock decoration set in
jest.setup.js) and either delete the
expect(PassportReader.scanPassport.length).toBe(10) line or replace it with a
behavior-driven assertion that verifies parameters are forwarded (e.g., ensure
mockScanPassport is called with the full parameter list); refer to
PassportReader.scanPassport, mockScanPassport, and the existing
nfcScanner.test.ts test for the correct behavioral assertion pattern.

---

Outside diff comments:
In `@specs/projects/sdk/workstreams/native-consolidation/CONTRACTS.md`:
- Line 27: The CONTRACTS.md statement "scan() must call reset() then call
Android scan(...)" overstates the tests; either update the wording in
CONTRACTS.md to say the tests only assert that reset() and Android scan(...)
were both called (without ordering), or add an ordering assertion to the
integration test app/tests/src/integrations/nfc/nfcScanner.test.ts (around the
calls to reset() and Android scan(...)) to assert reset() occurred before
scan(); locate the test that references reset() and scan() and either relax the
CONTRACTS.md language or add a strict ordering check in the test so the contract
matches the verified behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0fa4a0e5-6dc3-438a-8e3d-7611094640bc

📥 Commits

Reviewing files that changed from the base of the PR and between 88e2873 and 13290cf.

📒 Files selected for processing (4)
  • app/jest.setup.js
  • app/tests/src/integrations/nfc/nfcScanner.test.ts
  • app/tests/src/integrations/nfc/passportReader.test.ts
  • specs/projects/sdk/workstreams/native-consolidation/CONTRACTS.md

Comment on lines +426 to +429
await expect(isolatedScan(mockInputs)).rejects.toMatchObject({
message:
'NFC scanning is currently unavailable. Please ensure the app is properly installed.',
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '79,85p' app/src/integrations/nfc/nfcScanner.ts
sed -n '121,128p' app/src/integrations/nfc/nfcScanner.ts
sed -n '412,448p' app/tests/src/integrations/nfc/nfcScanner.test.ts

Repository: selfxyz/self

Length of output: 1993


🏁 Script executed:

#!/bin/bash
# Look for other rejects assertions in the test file to see established patterns
rg "rejects\." app/tests/src/integrations/nfc/nfcScanner.test.ts -A 2 | head -40

Repository: selfxyz/self

Length of output: 523


🏁 Script executed:

#!/bin/bash
# Check if there are any existing Error type checks in the test file
rg "toBeInstanceOf|toThrow|toEqual.*Error" app/tests/src/integrations/nfc/nfcScanner.test.ts

Repository: selfxyz/self

Length of output: 359


🏁 Script executed:

#!/bin/bash
# Also check the full context around those error paths in the source to confirm Error is used
rg "Promise\.reject\(new Error" app/src/integrations/nfc/nfcScanner.ts -B 2 -A 1

Repository: selfxyz/self

Length of output: 186


Use rejects.toThrow() to validate Error type and message together.

The test currently uses rejects.toMatchObject({ message: ... }), which only checks if the rejection has a message property—allowing a plain object with that field to pass. Since app/src/integrations/nfc/nfcScanner.ts rejects with Promise.reject(new Error(...)), the test should enforce that contract. The test file already uses the correct pattern elsewhere: rejects.toThrow('native scan failed'). This matcher validates both that the rejection is an Error instance and that the message matches.

Corrected assertions
-      await expect(isolatedScan(mockInputs)).rejects.toMatchObject({
-        message:
-          'NFC scanning is currently unavailable. Please ensure the app is properly installed.',
-      });
+      await expect(isolatedScan(mockInputs)).rejects.toThrow(
+        'NFC scanning is currently unavailable. Please ensure the app is properly installed.'
+      );
-      await expect(isolatedScan(mockInputs)).rejects.toMatchObject({
-        message: 'NFC scanning is currently unavailable.',
-      });
+      await expect(isolatedScan(mockInputs)).rejects.toThrow(
+        'NFC scanning is currently unavailable.'
+      );

Per coding guidelines: "Test value over mock wiring—prefer tests that validate behavior; avoid tests that only assert mocks were called unless that is the behavior being validated."

Also applies to: 446-448

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/tests/src/integrations/nfc/nfcScanner.test.ts` around lines 426 - 429,
The test assertion for isolatedScan currently uses rejects.toMatchObject({
message: 'NFC scanning is currently unavailable. Please ensure the app is
properly installed.' }) which only checks for a message property; update the
assertion to use rejects.toThrow('NFC scanning is currently unavailable. Please
ensure the app is properly installed.') so the test verifies the rejection is an
Error instance with the exact message; apply the same change for the similar
assertion at the other occurrence (around the isolatedScan / NFC failure tests)
to enforce Error type + message semantics.

Comment on lines +31 to +32
// scanPassport should take exactly 10 parameters including sessionId
expect(PassportReader.scanPassport.length).toBe(10);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "scanPassport\\.length|Object\\.defineProperty\\(mockScanPassport, 'length'" \
  app/tests/src/integrations/nfc/passportReader.test.ts \
  app/jest.setup.js

Repository: selfxyz/self

Length of output: 351


🏁 Script executed:

sed -n '20,40p' app/tests/src/integrations/nfc/passportReader.test.ts

Repository: selfxyz/self

Length of output: 844


🏁 Script executed:

rg -B 5 -A 5 "scanPassport\.length|scanPassport\(" app/tests/src/integrations/nfc/passportReader.test.ts | head -40

Repository: selfxyz/self

Length of output: 870


🏁 Script executed:

rg -n "scanPassport.*sessionId|sessionId.*scanPassport" app/tests/src/integrations/nfc/ --context 5

Repository: selfxyz/self

Length of output: 1113


🏁 Script executed:

grep -n "scan\(" app/tests/src/integrations/nfc/nfcScanner.test.ts | head -20

Repository: selfxyz/self

Length of output: 79


🏁 Script executed:

rg "scanPassport.*\(" app/tests/src/integrations/nfc/nfcScanner.test.ts -A 8 | head -50

Repository: selfxyz/self

Length of output: 857


🏁 Script executed:

rg "sessionId" app/tests/src/integrations/nfc/ -B 2 -A 2

Repository: selfxyz/self

Length of output: 1639


🏁 Script executed:

rg "scanPassport\(" app/tests/src/integrations/nfc/nfcScanner.test.ts -B 3 -A 12 | head -80

Repository: selfxyz/self

Length of output: 38


🏁 Script executed:

rg "PassportReader" app/tests/src/integrations/nfc/nfcScanner.test.ts -B 2 -A 8 | head -100

Repository: selfxyz/self

Length of output: 2900


🏁 Script executed:

sed -n '1,50p' app/tests/src/integrations/nfc/nfcScanner.test.ts

Repository: selfxyz/self

Length of output: 1718


🏁 Script executed:

sed -n '100,140p' app/tests/src/integrations/nfc/nfcScanner.test.ts

Repository: selfxyz/self

Length of output: 1444


🏁 Script executed:

sed -n '200,260p' app/tests/src/integrations/nfc/nfcScanner.test.ts

Repository: selfxyz/self

Length of output: 2029


🏁 Script executed:

rg "expect.*toHaveBeenCalledWith" app/tests/src/integrations/nfc/nfcScanner.test.ts -B 2 -A 15 | head -120

Repository: selfxyz/self

Length of output: 2416


Remove or replace the Function.length assertion; it validates mock decoration, not behavior.

This test only verifies that jest.setup.js set Object.defineProperty(mockScanPassport, 'length', { value: 10 }) correctly—not actual function behavior. The real behavior validation already exists in nfcScanner.test.ts, which properly asserts all 10 parameters are forwarded (passportNumber, dateOfBirth, dateOfExpiry, canNumber, useCan, skipPACE, skipCA, extendedMode, usePacePolling, sessionId) via toHaveBeenCalledWith().

Per coding guidelines: "Test value over mock wiring—prefer tests that validate behavior; avoid tests that only assert mocks were called unless that is the behavior being validated."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/tests/src/integrations/nfc/passportReader.test.ts` around lines 31 - 32,
Remove the brittle Function.length assertion for PassportReader.scanPassport (it
only validates mock decoration set in jest.setup.js) and either delete the
expect(PassportReader.scanPassport.length).toBe(10) line or replace it with a
behavior-driven assertion that verifies parameters are forwarded (e.g., ensure
mockScanPassport is called with the full parameter list); refer to
PassportReader.scanPassport, mockScanPassport, and the existing
nfcScanner.test.ts test for the correct behavioral assertion pattern.

@transphorm transphorm merged commit 5211378 into dev Mar 7, 2026
23 checks passed
@transphorm transphorm deleted the justin/native-consolidation-phase3 branch March 7, 2026 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant