Summary
android/app/src/main/java/org/enbox/mobile/nativemodules/NativeBiometricVaultModule.kt currently has no unit-test infrastructure. The Kotlin module owns the device-level biometric prompt, Android Keystore key generation/deletion, EncryptedSharedPreferences persistence, and a per-alias serialization lock. Today these contracts are validated only through manual emulator runs and the TypeScript spec contract — the JS unit tests stub the native module.
This is a gap. Concurrency primitives in particular (the aliasInProgress set + releaseAliasLockOnce lambda paths through BiometricPrompt.AuthenticationCallback) deserve direct coverage.
Why This Matters
- Per-alias serialization was added because two concurrent
generateAndStoreSecret(alias) calls could otherwise pass the non-upsert check, then race through the destructive delete-then-create window — corrupting a working wallet.
- The lock has multiple terminal release sites:
onAuthenticationError, the finally of onAuthenticationSucceeded, the catch around biometricPrompt.authenticate(), and several early-exit paths. Missing one would cause a permanent lock leak for the alias.
- The Keystore delete path is a checked delete; swallowing or misclassifying its exceptions can either resurrect a wiped wallet or report a phantom failure on a clean Keystore.
Proposed Direction
- Add a JUnit / Robolectric harness to
android/app (or a sibling test source set).
- Mock
KeyStore, KeyGenerator, EncryptedSharedPreferences, and BiometricPrompt boundaries.
- Cover at minimum:
- Per-alias serialization: concurrent
generateAndStoreSecret on the same alias rejects the second caller with VAULT_ERROR_OPERATION_IN_PROGRESS; concurrent on different aliases run in parallel; lock is released on every terminal callback path (success, biometric error, biometric prompt construction error).
- Keystore lifecycle:
generateAndStoreSecret non-upsert guard, deleteSecret symmetric cleanup of Keystore entry + prefs, idempotent missing-alias deletes.
- Hex-input validator: lowercase-only acceptance, length / character rejections.
- Probe semantics:
hasSecret distinguishes "no entry" from a transient Keystore read failure.
Acceptance Criteria
- A JVM/Robolectric test source set exists and is wired into
./gradlew testDebugUnitTest (or equivalent).
- The CI pipeline runs the Kotlin tests on every push.
- The above coverage matrix is in place.
- Documentation (CONTRIBUTING / README) explains how to add new tests.
Out of Scope
- Espresso / instrumented tests on a real emulator (already partially covered by
debug-emulator.yml).
- iOS XCTest coverage of
RCTNativeBiometricVault.mm — file a sibling issue if desired.
Summary
android/app/src/main/java/org/enbox/mobile/nativemodules/NativeBiometricVaultModule.ktcurrently has no unit-test infrastructure. The Kotlin module owns the device-level biometric prompt, Android Keystore key generation/deletion, EncryptedSharedPreferences persistence, and a per-alias serialization lock. Today these contracts are validated only through manual emulator runs and the TypeScript spec contract — the JS unit tests stub the native module.This is a gap. Concurrency primitives in particular (the
aliasInProgressset +releaseAliasLockOncelambda paths throughBiometricPrompt.AuthenticationCallback) deserve direct coverage.Why This Matters
generateAndStoreSecret(alias)calls could otherwise pass the non-upsert check, then race through the destructive delete-then-create window — corrupting a working wallet.onAuthenticationError, thefinallyofonAuthenticationSucceeded, the catch aroundbiometricPrompt.authenticate(), and several early-exit paths. Missing one would cause a permanent lock leak for the alias.Proposed Direction
android/app(or a sibling test source set).KeyStore,KeyGenerator,EncryptedSharedPreferences, andBiometricPromptboundaries.generateAndStoreSecreton the same alias rejects the second caller withVAULT_ERROR_OPERATION_IN_PROGRESS; concurrent on different aliases run in parallel; lock is released on every terminal callback path (success, biometric error, biometric prompt construction error).generateAndStoreSecretnon-upsert guard,deleteSecretsymmetric cleanup of Keystore entry + prefs, idempotent missing-alias deletes.hasSecretdistinguishes "no entry" from a transient Keystore read failure.Acceptance Criteria
./gradlew testDebugUnitTest(or equivalent).Out of Scope
debug-emulator.yml).RCTNativeBiometricVault.mm— file a sibling issue if desired.