Skip to content

test: cover verify_pin_cubit + three DFX services (+32 tests)#328

Merged
TaprootFreak merged 1 commit into
developfrom
test/verify-pin-and-dfx-services
May 15, 2026
Merged

test: cover verify_pin_cubit + three DFX services (+32 tests)#328
TaprootFreak merged 1 commit into
developfrom
test/verify-pin-and-dfx-services

Conversation

@TaprootFreak
Copy link
Copy Markdown
Contributor

Summary

Stage 5 of the coverage push. Adds 32 unit tests for the PIN unlock cubit and three previously-untested DFX backend services. mocktail mocks for AppStore + repository boundaries; `http.testing.MockClient` for the HTTP surface.

File under test Test file Cases
`lib/screens/pin/bloc/verify_pin/verify_pin_cubit.dart` `test/screens/pin/verify_pin_cubit_test.dart` 18
`lib/packages/service/dfx/dfx_country_service.dart` `test/packages/service/dfx/dfx_country_service_test.dart` 5
`lib/packages/service/dfx/dfx_blockchain_api_service.dart` `test/packages/service/dfx/dfx_blockchain_api_service_test.dart` 5
`lib/packages/service/dfx/dfx_faucet_service.dart` `test/packages/service/dfx/dfx_faucet_service_test.dart` 4

What each file covers

  • verify_pin_cubit: initial state; `addDigit` / `deleteDigit` including no-ops at boundaries + while `VerifyPinTemporarilyLocked` / `VerifyPinLocked`; correct pin resets lockout and emits `VerifyPinSuccess` (real 600k-iter PBKDF2 via `compute()`); wrong-pin first-attempt path; `enableLockout: false` never persists attempts; 5th wrong attempt triggers a 1-minute temporary lockout; reaching `permanentLockoutThreshold` emits `VerifyPinLocked` and skips the temporary-lockout write; `onLockExpired` preserves `failedAttempts`; `checkBiometricAvailability` for the in-window / expired / threshold / success / fail / unavailable branches.
  • dfx_country_service: list mapping from the DTO + cache (single fetch even on repeated reads), non-200 throws, case-insensitive symbol lookup, unknown-symbol error, public `cachedCountries` field is populated post-fetch.
  • dfx_blockchain_api_service: POST shape (address + Bearer JWT + chain name + asset id), testnet flips `blockchain` to `'Sepolia'`, empty balances list returns `0.0`, 201 acceptance in addition to 200, `ApiException` on non-2xx with the JSON body.
  • dfx_faucet_service: POST + Bearer JWT to `/v1/faucet`, 201 acceptance, `ApiException` on non-2xx, documents the current behaviour of sending the literal string `'Bearer null'` when no auth token is set — useful as a regression marker if/when the service grows a guard.

Notes

  • The verify-pin happy-path test exercises a real PBKDF2 (600k iterations) via `compute()`. On the Flutter-test isolate shim this takes a few seconds; the test uses a 30 s timeout, same approach as in PR test(screens): cover screen cubits/blocs (+37 tests) #327's setup_pin test.
  • `MockClient` lets us stub `AppStore.httpClient` without giving the service its own real `http.Client`; the production code is unchanged.

Excluded (and why)

  • `dfx_kyc_service`, `real_unit_registration_service` — overlap with the KYC cubits that test(kyc): add Phase 0 cubit unit tests for BitBox-gated KYC flow #319 already covers; want to avoid a stack on top of that work mid-review.
  • `real_unit_account_service`, `transaction_history_service` — hit `appStore.wallet.currentAccount.primaryAddress.address.hexEip55`, which needs a real `SoftwareWallet` plumbed through the mock. Doable but more setup than the surface justifies; will be covered in a follow-up alongside hook tests.
  • `dfx_brokerbot_service`, `real_unit_sell_payment_info_service` — possibly touched by fix: keep dashboard buy actions visible #321 (dashboard buy/sell auth). Hold to avoid review-time conflicts.
  • `biometric_service`, `price_service` — same constraints documented in test(service): cover non-DFX services (+38 tests) #326.

Test plan

  • `flutter analyze` on the four new files — clean
  • `flutter test` — 32 / 32 passing locally
  • CI green

Adds 32 unit tests across one screen cubit and three DFX backend
services. mocktail mocks for AppStore + repository boundaries;
`http.testing.MockClient` for the HTTP surface.

- verify_pin_cubit (18): initial state, addDigit / deleteDigit incl.
  no-ops at boundaries + while locked, correct pin resets lockout and
  emits VerifyPinSuccess (real PBKDF2 via compute()), wrong pin path
  for the first attempt, lockout-disabled mode never persists, 5th
  wrong attempt triggers a 1-min temporary lockout, permanent
  lockout at threshold; onLockExpired preserves failedAttempts;
  checkBiometricAvailability for all four state transitions
  (in-window / expired / threshold / success / fail / unavailable)
- dfx_country_service (5): list mapping + caching, error path, case-
  insensitive symbol lookup, unknown-symbol error, cache field
- dfx_blockchain_api_service (5): POST shape (address + JWT + chain),
  testnet flips to 'Sepolia', empty balances → 0.0, 201 acceptance,
  ApiException on non-2xx
- dfx_faucet_service (4): POST + JWT, 201 acceptance, ApiException on
  non-2xx, documents 'Bearer null' behaviour when no auth token set
@TaprootFreak TaprootFreak marked this pull request as ready for review May 15, 2026 08:40
@TaprootFreak TaprootFreak merged commit 10c9d81 into develop May 15, 2026
1 check passed
@TaprootFreak TaprootFreak deleted the test/verify-pin-and-dfx-services branch May 15, 2026 08:40
TaprootFreak added a commit that referenced this pull request May 15, 2026
…sts) (#329)

## Summary
Stage 6 of the coverage push. Adds 23 unit tests for two more DFX
services and two screen-level cubits whose only previous coverage was at
the widget layer.

| File under test | Test file | Cases |
| --- | --- | --- |
| \`lib/packages/service/dfx/dfx_bank_account_service.dart\` |
\`test/packages/service/dfx/dfx_bank_account_service_test.dart\` | 7 |
| \`lib/packages/service/dfx/dfx_price_service.dart\` |
\`test/packages/service/dfx/dfx_price_service_test.dart\` | 8 |
| \`lib/screens/settings_contact/cubit/settings_contact_cubit.dart\` |
\`test/screens/settings_contact/settings_contact_cubit_test.dart\` | 4 |
|
\`lib/screens/support/cubits/support_tickets/support_tickets_cubit.dart\`
| \`test/screens/support/cubits/support_tickets_cubit_test.dart\` | 4 |

## What each file covers
- **dfx_bank_account_service:** \`getBankAccounts\` GET shape (path, JWT
header, list mapping) + \`ApiException\` on non-2xx;
\`createBankAccount\` POST body, optional-label omission, error path;
\`updateBankAccount\` PUT to \`/v1/bankAccount/{id}\` with only the
provided fields (also pins the \`isDefault → "default"\` wire mapping).
- **dfx_price_service:** \`getPriceOfAsset\` CHF / EUR scaled by 100
(rappen / cents); \`getPriceChart\` maps each entry with asset, scaled
price, and UTC time; \`getChfToEurRate\` returns \`eur / chf\` or
\`0.0\` when chf == 0 (no division by zero); all three paths throw on
non-200.
- **settings_contact_cubit:** Success with \`emailSet: true\` /
\`false\`, Failure on async-throw, and a manual \`init()\` call
recovering from a transient failure.
- **support_tickets_cubit:** Loaded with DTO → \`SupportIssue\` mapping,
Loaded(empty), Error on async-throw, and a manual \`loadTickets()\` call
recovering from a transient failure.

## Notes
- Both cubits fire their work (\`init()\` / \`loadTickets()\`) in the
constructor, so by the time a stream subscriber attaches the synchronous
Loading emit has already passed on the broadcast stream. The tests
therefore assert the **final state** via \`stream.firstWhere\` rather
than the full sequence — a deliberate documented choice in the test file
headers.
- Mocked \`getUser\`/\`getTickets\` use \`thenAnswer((_) async => throw
…)\` instead of \`thenThrow\` so the throw goes through a microtask
boundary (otherwise the cubit's \`catch\` block emits Failure before any
listener can hear it).
- Service tests use \`http.testing.MockClient\` against
\`AppStore.httpClient\`, same pattern as #328.

## Excluded (and why)
- \`dfx_kyc_service\` — large surface (≈ 260 lines), heavy overlap with
the KYC cubit logic already covered by #319; would invite review-time
conflicts.
- \`dfx_brokerbot_service\`, \`real_unit_sell_payment_info_service\` —
likely touched by #321 (dashboard buy/sell auth).
- \`transaction_history_service\`, \`real_unit_account_service\` —
depend on \`AppStore.wallet.currentAccount\` plumbing; will be covered
alongside the wallet-coupled hook tests in a follow-up.
- \`support_chat_cubit\`, \`support_create_ticket_cubit\` — share
\`DfxSupportService\` with this PR; held back to keep the diff focused.

## Test plan
- [x] \`flutter analyze\` on the four new files — clean
- [x] \`flutter test\` — 23 / 23 passing locally
- [ ] CI green
TaprootFreak added a commit that referenced this pull request May 15, 2026
## Summary
Stage 10 of the coverage push. Adds 21 unit tests for the two remaining
easy-to-mock DFX backend services.

| Service under test | Test file | Cases |
| --- | --- | --- |
| \`lib/packages/service/dfx/dfx_support_service.dart\` |
\`test/packages/service/dfx/dfx_support_service_test.dart\` | 9 |
| \`lib/packages/service/dfx/dfx_brokerbot_service.dart\` |
\`test/packages/service/dfx/dfx_brokerbot_service_test.dart\` | 12 |

## What each file covers
- **dfx_support_service:** \`getTickets\` GET shape (path, Bearer JWT) +
ApiException on non-200; \`getTicket\` by uid + ApiException;
\`createTicket\` POST body (type/reason/name + optional message) + omits
message when null + requires status \`201\` (200 is rejected as
ApiException); \`sendMessage\` POST shape + ApiException on non-201.
\`getAuthToken\` is short-circuited by pre-populating
\`sessionCache.authToken\` so the signing flow stays out of these unit
tests.
- **dfx_brokerbot_service:** \`getBuyPrice\` GET + currency-code query +
invalid-input guards (non-numeric, zero, negative, non-200);
\`getBuyShares\` GET + currency + invalid-input guards; \`getSellPrice\`
with Bearer JWT + ApiException on non-200 + the invalid-input case skips
the HTTP call entirely; same matrix for \`getSellShares\`.

## Notes
- Same mocktail + \`http.testing.MockClient\` pattern as the previous
DFX-service PRs (#326 / #328 / #329).
- The "invalid input never reaches HTTP" assertions are a small but
meaningful contract: callers can rely on these methods to fail fast
before any network round-trip.

## Excluded (deferred)
- \`settings_user_data_cubit\` was on the original Stage-10 plan but
coordinates 3 services + Country lookups + multi-branch KYC-step-status
handling. It deserves its own focused PR rather than tagging it onto
these two service tests.
- \`dfx_kyc_service\` — held back to avoid review conflicts with open PR
#332 (KYC routing / bitbox sign hardening).
- Buy / sell / Bitbox cubits — held back while #321 and #332 are open.

## Test plan
- [x] \`flutter analyze\` on the two new files — clean
- [x] \`flutter test\` — 21 / 21 passing locally
- [ ] CI green
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