Follow-up split from #376 (parent #323).
#376's acceptance named three unhappy approval-flow branches: biometric failure, user cancellation, and kill-switch-active. The kill-switch branch is now covered by ApprovalFlowKillSwitchInstrumentedTest. The biometric-failure and user-cancellation branches are documented as out-of-scope in that suite rather than exercised; this issue tracks closing that gap.
Why deferred
The failure/cancellation decision logic lives inside anonymous BiometricPrompt.AuthenticationCallback objects constructed inline in BiometricHelper (authenticateWithResult / authenticateWithCrypto): onAuthenticationError maps to AuthResult.FAILED / LOCKOUT, and ERROR_USER_CANCELED / ERROR_NEGATIVE_BUTTON resume with null. BiometricPrompt is instantiated directly with no injected factory, and on the target devices biometric is BIOMETRIC_STRONG with no PIN fallback, so those callbacks are invoked only by the real hardware prompt. There is no non-UI seam to drive them, and no emulator biometric injection is available. Stubbing them into a hollow pass was rejected in favor of documenting the gap.
Work
Introduce a test seam so the biometric outcome can be driven deterministically without real hardware, then assert key use is gated on both branches:
- Extract an injectable
BiometricPrompt factory (or an interface around the authenticate call) in BiometricHelper so tests can supply a fake that invokes onAuthenticationError with FAILED / LOCKOUT and with ERROR_USER_CANCELED / ERROR_NEGATIVE_BUTTON.
- Assert that on failure the approval flow does not sign: no
signature emitted, result maps to the canceled/error contract.
- Assert that on cancellation the flow resumes with
null and maps to the canceled/rejected contract, never a signature.
Acceptance
- The biometric-failure and user-cancellation branches are each exercised via a deterministic seam (no real-hardware dependency, no flakiness).
- Each asserts key use is correctly gated (no
signature, correct result mapping), matching the unhappy-path contract used by the sibling NIP-55 instrumented tests.
Mirrors the #389 precedent: a deferred branch tracked as a follow-up scoped around adding a deterministic test seam rather than a real-hardware dependency.
Follow-up split from #376 (parent #323).
#376's acceptance named three unhappy approval-flow branches: biometric failure, user cancellation, and kill-switch-active. The kill-switch branch is now covered by
ApprovalFlowKillSwitchInstrumentedTest. The biometric-failure and user-cancellation branches are documented as out-of-scope in that suite rather than exercised; this issue tracks closing that gap.Why deferred
The failure/cancellation decision logic lives inside anonymous
BiometricPrompt.AuthenticationCallbackobjects constructed inline inBiometricHelper(authenticateWithResult/authenticateWithCrypto):onAuthenticationErrormaps toAuthResult.FAILED/LOCKOUT, andERROR_USER_CANCELED/ERROR_NEGATIVE_BUTTONresume withnull.BiometricPromptis instantiated directly with no injected factory, and on the target devices biometric isBIOMETRIC_STRONGwith no PIN fallback, so those callbacks are invoked only by the real hardware prompt. There is no non-UI seam to drive them, and no emulator biometric injection is available. Stubbing them into a hollow pass was rejected in favor of documenting the gap.Work
Introduce a test seam so the biometric outcome can be driven deterministically without real hardware, then assert key use is gated on both branches:
BiometricPromptfactory (or an interface around the authenticate call) inBiometricHelperso tests can supply a fake that invokesonAuthenticationErrorwithFAILED/LOCKOUTand withERROR_USER_CANCELED/ERROR_NEGATIVE_BUTTON.signatureemitted, result maps to the canceled/error contract.nulland maps to the canceled/rejected contract, never a signature.Acceptance
signature, correct result mapping), matching the unhappy-path contract used by the sibling NIP-55 instrumented tests.Mirrors the #389 precedent: a deferred branch tracked as a follow-up scoped around adding a deterministic test seam rather than a real-hardware dependency.