Files
dione/test/README.md
Dione a61e0b0457 feat: multi-wallet architecture with localStorage state, EIP-712 verification, cross-tab WS leadership
- wallets.js: WalletManager state management with chain validation
- verifier.js: WalletVerifier EIP-712 signing via window.ethereum
- stream.js: WalletStreamManager with BroadcastChannel leader election, backoff + jitter
- AGENTS.md: updated with coding guidelines
2026-06-06 12:16:41 +00:00

6.2 KiB

cbBTC Dashboard Test Suite

The test suite is organized under the /test folder. All tests run in Node.js with ES modules and mock browser APIs (localStorage, WebSocket, BroadcastChannel, window.ethereum). The setup file test/test_setup.mjs provides the global location object that stream.js requires at import time.

Files

File Targets Requires How to run
test/test_wallets.js WalletManager — CRUD, persistence, security None node --experimental-modules test/test_wallets.js
test/test_stream.js WalletStreamManager — WebSocket, backoff, routing test/test_setup.mjs node --experimental-modules --import ./test/test_setup.mjs ./test/test_stream.js
test/test_tabs.js WalletStreamManager — multi-tab leader election, failover test/test_setup.mjs node --experimental-modules --import ./test/test_setup.mjs ./test/test_tabs.js
test/test_verifier.js WalletVerifier — Web3 provider error handling None node --experimental-modules test/test_verifier.js
test/test_setup.mjs Provides globalThis.location for stream.js Imported via --import

What Can Be Tested — by Module

1. WalletManager (test_wallets.js)

The WalletManager class manages tracked wallets in localStorage. The test suite covers:

Address Validation

  • Rejection of invalid address formats per chain (EVM, Bitcoin, Solana).
  • Acceptance of valid addresses for EVM (0x...), Bitcoin legacy (1...), and Solana base58 patterns.

CRUD (Create/Read/Update/Delete)

  • addWallet() — adds wallets, persists to storage, returns success/error.
  • getWallets() — returns deep copies of tracked wallets.
  • findWallet() — returns wallet object or undefined.
  • removeWallet() — splices wallet from state and persists.

Duplicate Guard

  • Same address + chain pair is rejected on second addWallet() call.

Verification Lifecycle

  • Wallets start with isVerified: false.
  • verifyWallet() sets the flag to true and stores the signature.
  • revokeVerification() resets the flag and clears the signature.

Security: localStorage Tampering

  • loadWallets() always resets isVerified to false on restart, regardless of what was written to storage. This prevents a user from forging verification by editing localStorage directly.

2. WalletVerifier (test_verifier.js)

The WalletVerifier class handles Web3 provider interactions. The test suite covers error-path cases:

Missing Extension

  • When window.ethereum is undefined, verification fails with "No Web3 wallet extension found".

User Rejection (Account Request)

  • When the user denies eth_requestAccounts (EIP-1193 error 4001), verification cancels with "Verification cancelled".

Signature Rejection

  • When the user denies the typed-data signature request, the error is mapped to "rejected".

3. WalletStreamManager — Core (test_stream.js)

The WalletStreamManager class manages the WebSocket connection, event routing, and reconnect logic with exponential backoff. The test suite covers:

Multiplexed Subscription

  • On connect, the manager sends a single subscribe frame containing all tracked wallets (from WalletManager).

Event Routing

  • Incoming server messages are parsed and dispatched to registered on('balance_update', ...) callbacks.
  • Events route by data.type (or data.event, or fallback 'message').

Callback Error Isolation

  • If one registered callback throws, remaining callbacks for the same event still execute. Exceptions are caught and logged but never propagate.

Dynamic Subscription

  • subscribeToNewWallet() sends an immediate subscribe_wallet frame without cycling the connection.

Exponential Backoff + Jitter

  • On network drop (simulateNetworkDrop()), the manager retries connecting. The delay follows BASE_DELAY * 2^attempt with ±25% jitter, capped at MAX_DELAY (30s).
  • Tests speed up time by overriding setTimeout to fire instantly.

4. WalletStreamManager — Multi-Tab (test_tabs.js)

The leader/follower election and cross-tab synchronization system relies on BroadcastChannel and localStorage heartbeats. The test suite covers:

Leader Election

  • First tab to connect finds no heartbeat in localStorage, claims leadership, opens the WebSocket, and starts heartbeats.
  • Second tab finds the heartbeat, assigns itself as a follower, and avoids opening a second WebSocket.

Cross-Tab Event Broadcasting

  • Leader receives server data on the WebSocket and posts it to BroadcastChannel.
  • Followers receive the broadcast and dispatch it to their local listeners.

Proxy Subscription Forwarding

  • When a follower calls subscribeToNewWallet(), the request is sent through BroadcastChannel to the leader, which opens the actual network subscription.

Automatic Failover

  • When the leader tab closes (disconnect()), it broadcasts LEADER_DEATH before closing the channel.
  • Followers detect the signal, claim leadership, remove the old heartbeat, write a new one, and open their own WebSocket connection.

Mock Infrastructure

All tests replace browser APIs with in-memory equivalents:

  • localStorage — plain object (mockStorage) with getItem, setItem, removeItem, clear.
  • setTimeout — overridden to fire callbacks after 1ms instead of the specified delay, so backoff/retry logic runs instantly.
  • WebSocketMockWebSocket auto-opens after 2ms, exposes simulateServerPush() and simulateNetworkDrop() helpers.
  • BroadcastChannelMockBroadcastChannel delivers messages synchronously to all other registered channels, simulating cross-tab delivery.

Extending the Test Suite

To add a new test:

  1. Add a describe/it block or inline assert inside the relevant test_<module>.js file.
  2. For stream-related tests, ensure the file is imported via --import ./test/test_setup.mjs.
  3. Run the test from the project root with the appropriate command.

To add a new module test:

  1. Create test/test_<module>.js with imports using ../<module>.js (relative to the /test folder).
  2. Add the required mock infrastructure at the top.
  3. Register it in this README with its run command.