205 lines
5.6 KiB
TypeScript
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);
|
|
});
|
|
}); |