Files
WebRTC-Broadcast/tests/frontend/WebSocketClient.test.ts
2025-09-05 00:36:54 -04:00

205 lines
5.6 KiB
TypeScript

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);
});
});