import { test, expect, describe, beforeEach, afterEach } from "bun:test"; // Mock WebSocket for testing class MockWebSocket { public readyState = WebSocket.CONNECTING; public url: string; public sentMessages: string[] = []; public onopen: (() => void) | null = null; public onclose: (() => void) | null = null; public onmessage: ((event: { data: string }) => void) | null = null; public onerror: ((error: any) => void) | null = null; constructor(url: string) { this.url = url; // Simulate successful connection setTimeout(() => { this.readyState = WebSocket.OPEN; if (this.onopen) { this.onopen(); } }, 10); } send(data: string) { if (this.readyState === WebSocket.OPEN) { this.sentMessages.push(data); } } close() { this.readyState = WebSocket.CLOSED; if (this.onclose) { this.onclose(); } } simulateMessage(data: any) { if (this.onmessage) { this.onmessage({ data: JSON.stringify(data) }); } } simulateError(error: any) { if (this.onerror) { this.onerror(error); } } } // Mock the global WebSocket (global as any).WebSocket = MockWebSocket; // Import after mocking import { WebSocketClient } from "../../public/js/services/WebSocketClient.ts"; import type { ISignalingMessage } from "../../public/js/interfaces/IWebRTCClient.ts"; describe("WebSocketClient", () => { let wsClient: WebSocketClient; let mockWs: MockWebSocket; beforeEach(() => { wsClient = new WebSocketClient('publisher'); // Access the internal WebSocket instance for testing }); test("should connect as publisher", async () => { const connectPromise = wsClient.connect(); // Wait for connection await connectPromise; expect(wsClient.isConnected()).toBe(true); }); test("should connect as subscriber", async () => { const subscriberClient = new WebSocketClient('subscriber'); await subscriberClient.connect(); expect(subscriberClient.isConnected()).toBe(true); }); test("should handle connection error", async () => { const errorClient = new WebSocketClient('publisher'); // Start connection but simulate error before it completes const connectPromise = errorClient.connect(); // Simulate error during connection setTimeout(() => { // Access the internal WebSocket and trigger error const ws = (errorClient as any).ws; if (ws && ws.onerror) { ws.onerror(new Error('Connection failed')); } }, 5); await expect(connectPromise).rejects.toThrow(); }); test("should send messages when connected", async () => { await wsClient.connect(); const message: ISignalingMessage = { type: 'offer', data: { sdp: 'test-sdp' } }; wsClient.sendMessage(message); const ws = (wsClient as any).ws as MockWebSocket; expect(ws.sentMessages).toHaveLength(1); expect(JSON.parse(ws.sentMessages[0])).toEqual(message); }); test("should not send messages when disconnected", () => { const message: ISignalingMessage = { type: 'offer', data: { sdp: 'test-sdp' } }; wsClient.sendMessage(message); // Should not have sent anything since not connected const ws = (wsClient as any).ws; expect(ws).toBeNull(); }); test("should handle incoming messages", async () => { await wsClient.connect(); const receivedMessages: ISignalingMessage[] = []; wsClient.onMessage('join', (message) => { receivedMessages.push(message); }); const testMessage: ISignalingMessage = { type: 'join', data: { clientId: 'test-id', role: 'publisher' } }; const ws = (wsClient as any).ws as MockWebSocket; ws.simulateMessage(testMessage); expect(receivedMessages).toHaveLength(1); expect(receivedMessages[0]).toEqual(testMessage); }); test("should handle multiple message types", async () => { await wsClient.connect(); const joinMessages: ISignalingMessage[] = []; const offerMessages: ISignalingMessage[] = []; wsClient.onMessage('join', (message) => joinMessages.push(message)); wsClient.onMessage('offer', (message) => offerMessages.push(message)); const ws = (wsClient as any).ws as MockWebSocket; ws.simulateMessage({ type: 'join', data: { clientId: 'test' } }); ws.simulateMessage({ type: 'offer', data: { sdp: 'test-sdp' } }); ws.simulateMessage({ type: 'join', data: { clientId: 'test2' } }); expect(joinMessages).toHaveLength(2); expect(offerMessages).toHaveLength(1); }); test("should disconnect properly", async () => { await wsClient.connect(); expect(wsClient.isConnected()).toBe(true); wsClient.disconnect(); expect(wsClient.isConnected()).toBe(false); }); test("should handle malformed JSON messages gracefully", async () => { await wsClient.connect(); // Mock console.error to verify error logging const originalConsoleError = console.error; const errorLogs: any[] = []; console.error = (...args: any[]) => errorLogs.push(args); const ws = (wsClient as any).ws as MockWebSocket; if (ws.onmessage) { ws.onmessage({ data: 'invalid-json' }); } expect(errorLogs.length).toBeGreaterThan(0); expect(errorLogs[0][0]).toBe('Failed to parse WebSocket message:'); // Restore console.error console.error = originalConsoleError; }); test("should handle close event", async () => { await wsClient.connect(); expect(wsClient.isConnected()).toBe(true); const ws = (wsClient as any).ws as MockWebSocket; ws.close(); expect(wsClient.isConnected()).toBe(false); }); });