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

117 lines
6.2 KiB
Markdown

# 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_<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.