Skip to content

Add JVM unit tests for NativeBiometricVaultModule (per-alias serialization + Keystore lifecycle) #5

Description

@LiranCohen

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

  1. Add a JUnit / Robolectric harness to android/app (or a sibling test source set).
  2. Mock KeyStore, KeyGenerator, EncryptedSharedPreferences, and BiometricPrompt boundaries.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions