feat(test): FakeBitboxCredentials + Phase 1 sign-flow integration tests#320
Merged
TaprootFreak merged 1 commit intoMay 15, 2026
Merged
Conversation
Phase 1 foundation of #314: a controllable in-test stand-in for `BitboxCredentials` and a first cross-layer integration test exercising the BitBox-gated KYC sign flow end-to-end. `FakeBitboxCredentials extends BitboxCredentials`, so existing `credentials is BitboxCredentials` type guards (e.g. the `BitboxNotConnectedException` check in `RealUnitRegistrationService`) treat the fake identically to a real hardware wallet. The behaviour is selected via a `FakeBitboxBehavior` enum that mirrors every observed real-world outcome of the ceremony: - success — produces a deterministic EIP-712 / personal-message signature from an embedded test private key - cancel — returns '0x' (matches the iOS bridge cancel signal) - disconnect — throws SigningCancelledException; isConnected == false - timeout — never resolves; caller imposes its own outer timeout - malformed — returns non-hex data (simulates frame-desync regressions like bitbox_flutter PR #11) Unit tests cover every behaviour mode across `signTypedDataV4` and `signPersonalMessage`, including the disconnect-flip-to-success retry pattern. Cross-layer integration tests live under `test/integration/` (headless, runs as part of `flutter test`): - happy path: fake → Eip712Signer.signRegistration → 65-byte sig - cancel mid-sign: fake "0x" → Eip712Signer guard → SigningCancelledException - BLE disconnect: SigningCancelledException at the credentials boundary - reconnect-and-retry: flipped fake completes on the second attempt The top-level `integration_test/` directory is intentionally deferred: scenarios that need full app boot, BLE / USB channels, or the integration_test binding will land there as a follow-up. Stacks on top of feat/kyc-cubit-unit-tests (Phase 0). 242 / 242 tests green; flutter analyze clean.
This was referenced May 14, 2026
TaprootFreak
added a commit
that referenced
this pull request
May 15, 2026
) ## Summary Adds 38 unit tests across six previously-untested files in `lib/packages/utils/` and `lib/packages/wallet/`. Each spec lives in the mirror path under `test/` per the project convention. | File under test | Test file | Cases | | --- | --- | --- | | `lib/packages/utils/fast_hash.dart` | `test/packages/utils/fast_hash_test.dart` | 5 | | `lib/packages/utils/jwt_decoder.dart` | `test/packages/utils/jwt_decoder_test.dart` | 7 | | `lib/packages/wallet/payment_uri.dart` | `test/packages/wallet/payment_uri_test.dart` | 4 | | `lib/packages/wallet/wallet_account.dart` | `test/packages/wallet/wallet_account_test.dart` | 7 | | `lib/packages/wallet/wallet.dart` | `test/packages/wallet/wallet_test.dart` | 11 | | `lib/packages/wallet/eip7702_signer.dart` | `test/packages/wallet/eip7702_signer_test.dart` | 4 | ## What each file covers - **fast_hash:** FNV-1a determinism, distinct inputs differ, ordering matters, unicode safety, empty-string offset basis. - **jwt_decoder:** well-formed payload parsing, segment-count errors, non-map payload rejection, all three valid base64url padding lengths, illegal length-mod-4 rejection. - **payment_uri:** empty-amount short form, dotted and comma-locale amounts, preservation of decimal precision. - **wallet_account:** BIP-44 derivation path format, deterministic Hardhat account #0 address from the standard test mnemonic, distinct indices, signMessage shape + determinism + sensitivity to addressIndex. - **wallet:** `SoftwareWallet` walletType + primary/current account identity + `selectAccount` semantics + id/name mutability; `DebugWallet` sign refusal. - **eip7702_signer:** hardware-credential refusal (BitBox cannot sign EIP-7702), software-credential signing, signature determinism, nonce-sensitivity. ## Why Stage 2 of the coverage push tracked in the README features matrix ([#322](#322)). These six files are pure-Dart with no platform dependencies, so they were the cheapest meaningful coverage win available — no service mocks, no widget rendering, no merge-conflict risk with the three in-flight test PRs ([#319](#319), [#320](#320), [#321](#321)). ## Test plan - [x] `flutter analyze` clean on all six new test files (locally on Flutter 3.38.5) - [x] `flutter test test/packages/utils/ test/packages/wallet/` — 38 / 38 passing - [ ] CI green on this branch - [ ] Spot-check: coverage artifact from #323 (once that lands) shows non-zero coverage on the six files above
TaprootFreak
added a commit
that referenced
this pull request
May 15, 2026
## Summary Replace the bare README with a full project-overview document modelled on the `zk-coins/app` README. Adds: 1. **Contributing rule:** 100% test coverage on the activated surface is required to merge into `develop`. Defensive code is exempted via `// coverage:ignore-*`. Branch protection enforces. 2. **Coverage scope:** `lib/packages/**` + every `cubits/`/`bloc/` directory under `lib/screens/<feature>/`. Widget files render-tested via `testWidgets` only. 3. **Coverage infrastructure roadmap:** honest list of what still needs to land before the rule is enforceable (CI `--coverage` step, lcov threshold gate, branch protection, build-time feature flags, inline ignore annotations). 4. **Features matrix:** every user-facing function, its activation status, the triage decision (`mvp` / `defer` / `planned`), and the tests that currently cover it. 5. **Triage gaps:** explicit list of `mvp` features still below 100%, with links to the in-flight PRs ([#319](#319), [#320](#320), [#321](#321)) that close them. 6. **Testing tiers:** ties the matrix to the 4-tier model in [#314](#314). 7. Original **Getting started** section preserved at the end. ## Why The repo has no source of truth for "what does this wallet actually do" or "what is its test-coverage commitment". Tests in `test/` exist but are unbound to features — a reviewer cannot tell at a glance whether a feature added in a PR has the required coverage. This document closes that gap. The coverage rule deliberately mirrors `zk-coins/app` exactly (same wording, same scope shape) so that reviewers familiar with one project transfer their expectations to the other. The 4-tier testing model is owned by [#314](#314); this README references it but does not duplicate it. ## What this PR is _not_ - It does **not** wire `flutter test --coverage` into CI. That is item 1 of the roadmap and will land in a follow-up. - It does **not** add branch protection. That is item 3 of the roadmap. - It does **not** introduce build-time feature flags. That is item 4 of the roadmap and is a prerequisite for the rule being realistic across all features. - It does **not** add any new tests — the in-flight test PRs ([#319](#319), [#320](#320), [#321](#321)) own that work. Until the roadmap items land, the rule is aspirational, explicitly noted as such in the README, and not yet a merge blocker. ## Test plan - [ ] Visual review of the rendered README on GitHub (tables align, all links resolve) - [ ] Confirm every `widget`-tagged row points at an existing `*_test.dart` file in `test/` - [ ] Confirm every "in flight" reference points at an open PR - [ ] Confirm "Coverage scope" wording matches what we want to measure once the lcov gate lands
3 tasks
TaprootFreak
added a commit
that referenced
this pull request
May 15, 2026
## Summary Stage 3 of the coverage push. Adds 38 unit tests across four previously-untested service files in \`lib/packages/service/\` that have no DFX backend coupling. \`mocktail\` mocks for the repository / bitbox dependencies; no real I/O. | Service | Test file | Cases | | --- | --- | --- | | \`session_cache.dart\` | \`test/packages/service/session_cache_test.dart\` | 9 | | \`settings_service.dart\` | \`test/packages/service/settings_service_test.dart\` | 6 | | \`app_store.dart\` | \`test/packages/service/app_store_test.dart\` | 6 | | \`wallet_service.dart\` | \`test/packages/service/wallet_service_test.dart\` | 17 | ## What each file covers - **session_cache:** auth-token in-memory-only lifecycle (no disk write); signature/address persistence through \`CacheRepository\`; \`loadSignature\` preserves in-memory state when already set; tolerates missing repo entries; \`clear\` wipes both keys, auth token, and memory. - **settings_service:** terms / software-terms read + write delegation; the two flags are independent. - **app_store:** wallet-not-set throws; primaryAddress derived through current account; selectAccount-driven address change; \`apiConfig\` getter re-evaluates on every access (network mode can switch at runtime); sessionCache passthrough. - **wallet_service:** \`createSeedWallet\` (bip39 generation + persistence, does NOT mark current); \`restoreWallet\` (seed persistence + marks current); \`createDebugWallet\` (view-wallet path); \`getWalletById\` (software / debug branches + null-repo error); \`setCurrentWallet\`; \`getCurrentWallet\` (resolves through id); \`deleteCurrentWallet\`; \`hasWallet\` (both branches); \`validateSeed\` (valid / invalid / empty / wrong-checksum). ## Excluded (with reasons) - **biometric_service.dart** — the \`LocalAuthentication\` instance is constructed in the field initializer with no seam. Testing requires either a prod refactor (out of scope here) or platform-channel test plumbing. Flagged in the README features matrix as a triage gap. - **transaction_history_service.dart** — hits \`app_store.httpClient\` (a final \`http.Client\` field) which is not currently injectable. Same constraint as the DFX services. - **price_service.dart** — abstract interface only; no implementation to test yet. - **wallet_service \`createBitboxWallet\` / getWalletById bitbox branch** — belongs to Tier 1 with \`FakeBitboxCredentials\` (now landed via #319/#320). Will be covered in a follow-up. ## Test plan - [x] \`flutter analyze\` clean (locally on Flutter 3.38.5) - [x] \`flutter test test/packages/service/\` — 38 / 38 passing on these new files - [ ] CI green
3 tasks
TaprootFreak
added a commit
that referenced
this pull request
May 15, 2026
## Summary Stage 4 of the coverage push. Adds bloc_test specs for five screen-level cubits/blocs that previously only had widget-level coverage. All run in pure Dart (no widget pumping); mocktail for the service / repository / SoftwareWallet boundaries. | Cubit | Test file | Cases | | --- | --- | --- | | \`legal_disclaimer_cubit.dart\` | \`test/screens/legal/cubit/legal_disclaimer_cubit_test.dart\` | 7 | | \`restore_wallet/cubit/validate_seed/validate_seed_cubit.dart\` | \`test/screens/restore_wallet/cubit/validate_seed_cubit_test.dart\` | 7 | | \`transaction_history/cubits/filter/transaction_history_filter_cubit.dart\` | \`test/screens/transaction_history/cubits/transaction_history_filter_cubit_test.dart\` | 6 | | \`verify_seed/cubit/verify_seed_cubit.dart\` | \`test/screens/verify_seed/cubit/verify_seed_cubit_test.dart\` | 6 | | \`pin/bloc/setup_pin/setup_pin_cubit.dart\` | \`test/screens/pin/setup_pin_cubit_test.dart\` | 11 | ## What each file covers - **legal_disclaimer_cubit:** initial step-0 state, nextStep advance, full walk to last step, onComplete callback fires on last step (no emit), no-op without callback on last step, previousStep moves back, no-op at step 0. - **validate_seed_cubit:** initial uncomplete; \`checkSeedLength\` for 12 valid words / fewer-than-12 / 12 words with an out-of-wordlist token / extra inner whitespace tolerated; \`validateSeed\` delegates to \`WalletService\` for both branches. - **transaction_history_filter_cubit:** subscribes to the repo stream with the configured asset + address, default 1-year-back \`startDate\`, stream pushes populate \`all\` + \`filtered\`, date-window filter narrows \`filtered\` without touching \`all\`, boundaries are inclusive, filter re-applies on subsequent stream emissions. - **verify_seed_cubit:** 4 random ascending word indices within seed length, debug-mode pre-fill, \`canVerify\` reflects all four slots filled, \`updateWord\` trims + lowercases + clears \`hasError\`, \`verify\` returns true and marks the wallet current on match, \`verify\` returns false / flags \`hasError\` / does NOT mark current on mismatch. - **setup_pin_cubit:** initial state, \`addDigit\` appends / 6-digit cap, \`deleteDigit\` / no-op on empty, create→confirm transition on 6th digit, matching confirm-pin persists salt + hash and emits \`isComplete\` (exercises real PBKDF2 via \`compute()\`), mismatching confirm-pin resets + flags \`mismatch\`, \`reset\` returns to initial, \`isBiometricAvailable\` + \`enableBiometrics\` passthrough to \`BiometricService\`. ## Notes - The \`setup_pin_cubit\` matching-pin test runs a real **600k-iteration PBKDF2** through \`compute()\`. On the Flutter-test isolate shim this takes ~12 s on a Mac Studio. The test uses a 30 s timeout to keep CI honest without flaking. - \`SecureStorage.setPinSalt\` takes a \`Uint8List\`, which is a restricted type — a \`Fake\` subclass is illegal, so the mocktail fallback is registered with a real \`Uint8List(0)\` instance instead. ## Excluded (and why) - \`pin/bloc/verify_pin/verify_pin_cubit.dart\` — talks to \`SecureStorage.verifyPin\` which already exercises PBKDF2. Adding a full happy-path test would compound the slow-test cost. Will be covered in a follow-up. - \`restore_wallet/cubit/restore_wallet_cubit.dart\` — coupled to \`WalletService.restoreWallet\` (already covered in \`wallet_service_test.dart\`) plus screen navigation; small marginal value here, fits better in a Tier 1 integration test. - Buy / sell / KYC cubits — owned by other recent PRs (#312/#319/#321 area) and would invite merge conflicts. - Bitbox-coupled cubits (\`sell_bitbox\`, \`hardware_connect_bitbox\`) — belong with Tier 1 \`FakeBitboxCredentials\` (now landed via #319/#320) in a follow-up. ## Test plan - [x] \`flutter analyze test/screens/\` clean - [x] \`flutter test\` on all 5 new files — 37 / 37 passing (~8 s total locally) - [ ] CI green
3 tasks
TaprootFreak
added a commit
that referenced
this pull request
May 15, 2026
Closes Acceptance Criterion #6 of #314 — _\"Documentation: \`docs/testing.md\` explains when to use which tier, with examples.\"_ ## Summary A single page that captures everything we've learned across Phase 0 (#319) and Phase 1 Foundation (#320 / #321) about how to test BitBox-touching code without re-deriving the rules every PR: - **Tier matrix** (0–4) with hardware / CI status for each, so reviewers can tell at a glance what a test does and doesn't prove - **Decision tree** for picking a tier — \"if a Tier 0 test would have to mock the very thing under test, drop down a tier\" - **Tier 0 patterns** with concrete excerpts from real test files: - Cubit / Bloc tests (\`bloc_test\` + \`mocktail.Mock\`) pointing at \`test/screens/kyc/cubits/kyc/kyc_cubit_test.dart\` - Widget tests (\`pumpApp\` + \`MockCubit\`) pointing at \`test/screens/kyc/steps/kyc_email_page_test.dart\` - Service + HTTP tests (\`http/testing\` \`MockClient\` + \`_MockAppStore\`) pointing at \`test/packages/service/dfx/dfx_bank_account_service_test.dart\`, including the **\`setAuthToken('test-jwt')\` pre-seed** trick for DFXAuthService-derived services (the gotcha that broke CI on #321) - **Tier 1 patterns** for \`FakeBitboxCredentials\` with the \`FakeBitboxBehavior\` matrix and the disconnect-flip-to-success reconnect pattern, pointing at \`test/integration/kyc_sign_flow_test.dart\` - **Tiers 2–4** marked deferred with status pointers back to the corresponding phase of #314 - **Mocktail gotchas** (\`Future\` + \`thenAnswer\`, \`registerFallbackValue\`, private mocks) - **Add-tests checklist** for PRs touching \`KycCubit\` / \`Eip712Signer\` / \`DFXAuthService\` / \`BitboxCredentials\` / \`bitbox_flutter\` CONTRIBUTING.md's Testing section gets a one-line cross-link. ## Test plan - [x] Doc compiles / renders (no broken intra-repo links in the markdown) - [x] Every code example matches a real file already on \`develop\` - [x] \`flutter analyze\` — no new issues (4 pre-existing errors in \`test/screens/home/home_bloc_test.dart\` are unrelated to this PR; verified they exist on \`develop\` too)
TaprootFreak
added a commit
that referenced
this pull request
May 23, 2026
- fake_async example: add bitbox_flutter.dart import and inline the pairedServiceSync idiom so the snippet is copy-paste compilable - platform-coupled section: real-plugin counterpart is Tier 2/3, not Tier 1/2 (Tier 1 is FakeBitboxCredentials by definition); add the CONTRIBUTING.md footnote-165 caveat that the annotation is the documenting form today - README: PRs #319/#320/#321/#322/#323 are all merged since 2026-05-15; replace "in flight"/"landing in"/"extended in" with "added via" / "have closed" / "partially covered after"
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 foundation of #314 — stacks on top of #319 (Phase 0).
Summary
A controllable in-test stand-in for `BitboxCredentials` plus a first set of cross-layer integration tests that exercise the BitBox-gated KYC sign flow end-to-end (no device, no simulator).
`FakeBitboxCredentials extends BitboxCredentials` so every existing `credentials is BitboxCredentials` type guard — most notably the `BitboxNotConnectedException` check in `RealUnitRegistrationService` — treats the fake identically to a real hardware wallet. Behaviour is selected via a `FakeBitboxBehavior` enum that mirrors every observed real-world outcome of the ceremony:
What's in the PR
What's NOT in the PR (deferred to follow-up Phase 1 PRs)
Test plan