import { WalletManager } from '../wallets.js'; import { WalletStreamManager } from '../stream.js'; // --- 1. Environment Mocks (Storage & Timers) --- const mockStorage = {}; globalThis.localStorage = { getItem: (key) => mockStorage[key] || null, setItem: (key, value) => { mockStorage[key] = String(value); }, clear: () => { Object.keys(mockStorage).forEach(k => delete mockStorage[k]); } }; // Speed up time so our backoff tests don't take 30 actual seconds to run const originalSetTimeout = globalThis.setTimeout; globalThis.setTimeout = (callback, delay) => { return originalSetTimeout(callback, 1); // Force all retries to happen instantly }; // Mock location for stream.js WS_URL evaluation globalThis.location = { protocol: 'http:', host: 'localhost:3000' }; // --- 2. The Mock WebSocket Server Mirror --- const mockInstances = []; let lastSentPayload = null; let connectionCount = 0; class MockWebSocket { constructor(url) { this.url = url; this.readyState = 0; // CONNECTING connectionCount++; mockInstances.push(this); originalSetTimeout(() => { this.readyState = 1; // OPEN if (this.onopen) this.onopen(); }, 2); } send(data) { lastSentPayload = JSON.parse(data); } close() { this.readyState = 3; // CLOSED if (this.onclose) this.onclose({ code: 1000, reason: "Normal closure" }); } // Helper method for our test runner to mimic the server pushing data to client simulateServerPush(msgObject) { if (this.onmessage) { this.onmessage({ data: JSON.stringify(msgObject) }); } } // Helper method to simulate a sudden dirty network disconnect simulateNetworkDrop() { this.readyState = 3; if (this.onclose) this.onclose({ code: 1006, reason: "Abnormal Drop" }); } } globalThis.WebSocket = MockWebSocket; // --- 3. Test Runner Execution --- function assert(condition, message) { if (!condition) throw new Error(`āŒ FAIL: ${message}`); console.log(`āœ… PASS: ${message}`); } async function runStreamTests() { console.log("šŸš€ Starting WalletStreamManager Real-Time Test Suite...\n"); localStorage.clear(); // Seed two dummy wallets to simulate an existing local state const wm = new WalletManager(); wm.addWallet("0x04f728C520C438A000f7A5E9d904F0e725FFAEFE", "base", "Base Acc"); wm.addWallet("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "bitcoin", "Satoshi Acc"); const stream = new WalletStreamManager(wm); stream.connect(); // Wait a few milliseconds for our mocked async connection handshakes to settle await new Promise(r => originalSetTimeout(r, 10)); // --- TEST 1: Multiplexed On-Open Subscription --- assert(connectionCount === 1, "WebSocket established exactly one connection"); assert(lastSentPayload !== null, "Server received subscription frame on startup"); assert(lastSentPayload.action === "subscribe", "Payload correctly set action to 'subscribe'"); assert(lastSentPayload.subscriptions.length === 2, "Multiplexed frame packed all stored wallets successfully"); // --- TEST 2: Real-time Message Callback Routing --- let balanceUpdatedCalled = false; stream.on('balance_update', (msg) => { if (msg.address === "0x04f728C520C438A000f7A5E9d904F0e725FFAEFE") { balanceUpdatedCalled = true; } }); // Access the underlying mock instance to simulate the server pushing data const activeSocketInstance = stream._ws; activeSocketInstance.simulateServerPush({ type: "balance_update", address: "0x04f728C520C438A000f7A5E9d904F0e725FFAEFE", chain: "base", balance: "50000000000" }); assert(balanceUpdatedCalled === true, "Incoming server events route seamlessly to their respective listeners"); // --- TEST 3: Callback Error Isolation Safeguard --- let secondaryListenerExecuted = false; stream.on('transfer', () => { throw new Error("CRASHING INTRUDER: I am a broken UI component callback!"); }); stream.on('transfer', () => { secondaryListenerExecuted = true; }); // Push a transfer event. The first listener throws an exception, but shouldn't halt execution. try { activeSocketInstance.simulateServerPush({ type: "transfer", tx_hash: "0xabc" }); assert(secondaryListenerExecuted === true, "ERROR ISOLATION: Second event handler fired successfully despite preceding listener throwing a hard error"); } catch (e) { assert(false, "The StreamManager leaked a callback exception and crashed the event loop"); } // --- TEST 4: Dynamic Delta Streaming Tracker Updates --- const dynamicWallet = { address: "H7vjR5vP6dfYuxUzoZ76yvBpx6Z3zM7p9qE1vRkWk3xp", chain: "solana" }; stream.subscribeToNewWallet(dynamicWallet); assert(lastSentPayload.action === "subscribe_wallet", "Dynamic frame used accurate 'subscribe_wallet' verb"); assert(lastSentPayload.wallet?.address === dynamicWallet.address, "Dynamic delta subscription dispatched for runtime updates without connection cycling"); // --- TEST 5: Resilient Exponential Backoff Retry Loop --- console.log("\nšŸ•µļøā€ā™‚ļø Triggering a sudden network failure drop scenario..."); const preDropCount = connectionCount; stream._ws.simulateNetworkDrop(); // Crash the current open pipe await new Promise(r => originalSetTimeout(r, 15)); // Wait for fast-forward timers to run the retry loops assert(connectionCount > preDropCount, "RESILIENCE: Stream Manager successfully caught connection drop and automatically invoked reconnection logic"); // Cleanup stream.disconnect(); console.log("\nšŸŽ‰ All core real-time streaming transport client tests passed!"); } runStreamTests().catch(console.error);