# 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. - **WebSocket** — `MockWebSocket` auto-opens after 2ms, exposes `simulateServerPush()` and `simulateNetworkDrop()` helpers. - **BroadcastChannel** — `MockBroadcastChannel` 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_.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_.js` with imports using `../.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.