Skip to content

test(bitbox): guard exception surface + lock-in P1 channel-hash ordering#467

Merged
TaprootFreak merged 2 commits into
developfrom
test/bitbox-error-surface-and-p1
May 20, 2026
Merged

test(bitbox): guard exception surface + lock-in P1 channel-hash ordering#467
TaprootFreak merged 2 commits into
developfrom
test/bitbox-error-surface-and-p1

Conversation

@TaprootFreak
Copy link
Copy Markdown
Contributor

Summary

Two cheap, plugin-independent test additions that close concrete drift risks identified during the PR #461 review.

1. Exception surface guard

BitboxNotConnectedException had no toString() override. Any code path that surfaces the raw exception (snackbar fallback, log line, error.toString()) therefore rendered the default Instance of '...' Dart string instead of a human-readable message — a real failure mode observed in the BitBox-disconnect flows.

  • Adds toString() => 'BitBox is not connected'.
  • New test/packages/service/dfx/exceptions/exception_surface_test.dart enumerates every typed exception in the app (BitboxNotConnectedException, SigningCancelledException) and asserts the rendered string contains no Instance of and is non-empty. New typed exceptions just need to be appended to the list.

2. P1 channel-hash-before-confirm drift test

BitBox firmware quirk P1 (critical, per DFXswiss/bitbox-testkit): the pairing channel hash must reach the UI before the host calls confirmPairing() on the device — otherwise the user has no opportunity to verify the hash against the on-device display.

ConnectBitboxCubit already does the right thing today (BitboxCheckHash is emitted before user-driven confirmPairing()), but nothing prevents a future refactor from silently inverting the order. The new test in connect_bitbox_cubit_test.dart pins this ordering with a verifyNever(() => service.confirmPairing()) assertion at the BitboxCheckHash state.

Out of scope (follow-up PRs)

  • bitbox_flutter v0.0.5 → v0.0.7 bump (unlocks the installSimulatedBitboxPlatform helper) — separate dependency-bump PR
  • BitboxService lifecycle suite (re-attach on reconnect, observer behavior, USB-FD release) — depends on the bump above

Test plan

  • dart run tool/generate_localization.dart
  • flutter analyze on touched files — clean
  • flutter test test/packages/service/dfx/exceptions/exception_surface_test.dart test/screens/hardware_connect_bitbox/bloc/connect_bitbox_cubit_test.dart — 11/11 green
  • Full flutter test in CI

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

BitBox02 simulator check

Host: linux/amd64 — Started: 2026-05-20T17:22:17Z — Duration: 665ms

Firmware: bitbox02-multi-v9.26.1-simulator1.0.0-linux-amd64

Scenario Result Duration Detail
pair_and_device_info 40ms
restore_simulator_mnemonic 17ms
root_fingerprint_deterministic 0ms
eth_address_mainnet 42ms
eth_address_polygon_multibyte_v 42ms
eth_sign_message_ascii 52ms
eth_sign_message_boundary_1024 45ms
eth_sign_legacy_polygon_multibyte_v 43ms
eth_sign_eip1559_mainnet 42ms
eth_sign_typed_data_kyc_multipage 44ms
eth_sign_typed_data_non_ascii_rejected 1ms
btc_xpub_zpub_mainnet 41ms
btc_address_p2wpkh_mainnet 41ms
btc_address_p2tr_taproot 42ms
btc_sign_message_mainnet 44ms

Summary: 15 total · 15 passed · 0 failed

@TaprootFreak TaprootFreak force-pushed the test/bitbox-error-surface-and-p1 branch from c25e81c to 5c3f159 Compare May 20, 2026 15:13
…e-confirm

Two cheap test additions that close concrete drift risks in the BitBox
integration path:

- `BitboxNotConnectedException` now overrides toString() so a leaked
  raw exception in a snackbar / log surfaces as "BitBox is not connected"
  instead of the default `Instance of '...'` Dart representation. A new
  `exception_surface_test` enumerates every typed exception in the app
  and asserts the rendered string is human-readable, so any future
  exception missing the override is caught at test time rather than in
  the UI.

- A new test in `connect_bitbox_cubit_test` pins the pairing-channel-hash
  ordering required by BitBox firmware quirk P1 (hash must reach the UI
  before the host calls confirmPairing). The current cubit already does
  the right thing — the test guards against future refactors silently
  inverting the order, which would defeat the on-device hash comparison.
PR #461 introduced WalletLockedException; the surface-drift test should
enumerate every typed exception so a missing toString() on any of them
is caught at test time. ApiException's toString() is also worth pinning
for the same reason — it's the most-leaked exception in the app.
@TaprootFreak TaprootFreak force-pushed the test/bitbox-error-surface-and-p1 branch from 5c3f159 to ba53442 Compare May 20, 2026 17:21
@TaprootFreak TaprootFreak marked this pull request as ready for review May 20, 2026 17:29
@TaprootFreak TaprootFreak merged commit 7d834a0 into develop May 20, 2026
3 checks passed
@TaprootFreak TaprootFreak deleted the test/bitbox-error-surface-and-p1 branch May 20, 2026 17:46
TaprootFreak added a commit that referenced this pull request May 20, 2026
…d integration-test conventions (#476)

## Summary

Documents three test-style conventions in `CONTRIBUTING.md` so the
patterns introduced by #467, #470, and #473 don't decay back to ad-hoc
mocks over time.

## Conventions added

| # | Rule | Reference example |
|---|---|---|
| 1 | Service-lifecycle tests use the real class as SUT with
`installSimulatedBitboxPlatform` + `fake_async`. No mocking of the
service itself. |
`test/packages/hardware_wallet/bitbox_service_test.dart` |
| 2 | Every typed `Exception` in `lib/` needs a `toString()` override
and an entry in the shared surface test. |
`test/packages/service/dfx/exceptions/exception_surface_test.dart` |
| 3 | Platform-specific code paths need an `integration_test/`
counterpart OR an explicit `// @no-integration-test: <reason>`
annotation. | (forward-looking — no `integration_test/` yet) |

## Why

Conventions only stick if they live in the repo, not in PR descriptions.
Without this, a future hire would naturally reach for mocktail mocks of
`BitboxService` again, the exception surface test would not get appended
to as new exceptions land, and platform-specific code would keep
slipping through review.

## Test plan
- [x] CONTRIBUTING.md edits visible and well-formatted
- [ ] Reviewer agrees the conventions reflect the patterns shipped in
#467/#470/#473
TaprootFreak added a commit that referenced this pull request May 20, 2026
…ariants

The exception-surface drift test shipped in #467 enumerated 4 of the 6
typed Exception classes in lib/. RegistrationRequiredException and
KycLevelRequiredException (both ApiException subclasses with their own
toString() override) were missed, defeating the test's whole-set
guarantee.

Adds both to the surface list with a minimal constructor invocation.
All 6 typed exceptions now have their toString() pinned against
"Instance of …" drift.
TaprootFreak added a commit that referenced this pull request May 20, 2026
…ariants (#478)

## Summary

The exception-surface drift test shipped in #467 enumerates only 4 of
the 6 typed `Exception` classes that actually exist in `lib/`. Two
`ApiException` subclasses — `RegistrationRequiredException` and
`KycLevelRequiredException` — both define their own `toString()`
override but were not listed in the surface guard. A regression on
either would not be caught.

Adds both to the list. Test count moves from 4 → 6.

Discovered during the independent review of #476 (`docs(contributing)`),
which pointed at this test as the canonical example for Convention 2.
The convention can't honestly reference an incomplete enumeration.

## Test plan
- [x] All 6 typed exceptions enumerated
- [x] `flutter analyze` clean
- [x] `flutter test` green on the touched file
- [ ] CI once marked ready
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