import WS from "jest-websocket-mock"; import Answer from "../src/class/answer"; import Candidate from "../src/class/candidate"; import Offer from "../src/class/offer"; import * as wsHandler from '../src/class/websockethandler'; Date.now = jest.fn(() => 1482363367071); const anyParticipantId = expect.any(String); function recordingEnvelope(connectionId: string, data: any): any { const innerData = { ...data }; delete innerData.type; return { from: connectionId, to: "", type: "on-message", data: JSON.stringify({ type: data.type, data: innerData }) }; } describe('websocket signaling test in public mode', () => { let server: WS; let client: WebSocket; let client2: WebSocket; const connectionId = "12345"; const connectionId2 = "67890"; const testsdp = "test sdp"; beforeAll(async () => { wsHandler.reset("public"); server = new WS("ws://localhost:1234", { jsonProtocol: true }); client = new WebSocket("ws://localhost:1234"); await server.connected; client2 = new WebSocket("ws://localhost:1234"); await server.connected; }); afterAll(() => { WS.clean(); }); test('create session1', async () => { expect(client).not.toBeNull(); await wsHandler.add(client); }); test('create session2', async () => { expect(client2).not.toBeNull(); await wsHandler.add(client2); }); test('create connection from session1', async () => { await wsHandler.onConnect(client, connectionId); await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: true, role: "participant", participantId: anyParticipantId }); }); test('create connection from session2', async () => { await wsHandler.onConnect(client2, connectionId2); await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId2, polite: true, role: "participant", participantId: anyParticipantId }); }); test('send offer from session1', async () => { await wsHandler.onOffer(client, { connectionId: connectionId, sdp: testsdp }); const receiveOffer = new Offer(testsdp, Date.now(), false); await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "offer", data: receiveOffer }); expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "offer", data: receiveOffer }]); }); test('send answer from session2', async () => { await wsHandler.onAnswer(client2, { connectionId: connectionId, sdp: testsdp }); const receiveAnswer = new Answer(testsdp, Date.now()); await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "answer", data: receiveAnswer, participantId: anyParticipantId }); }); test('send candidate from sesson1', async () => { const msg = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: "0" }; await wsHandler.onCandidate(client, msg); expect(true).toBe(true); }); test('delete connection from session2', async () => { await wsHandler.onDisconnect(client2, connectionId); await expect(server).toReceiveMessage({ type: "participant-left", connectionId: connectionId, participantId: anyParticipantId }); await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); }); test('delete connection from session1', async () => { await wsHandler.onDisconnect(client, connectionId); await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); }); test('delete session2', async () => { expect(client).not.toBeNull(); await wsHandler.remove(client2); }); test('delete session1', async () => { expect(client2).not.toBeNull(); await wsHandler.remove(client); }); }); describe('websocket signaling test in private mode', () => { let server: WS; let client: WebSocket; let client2: WebSocket; const connectionId = "12345"; const testsdp = "test sdp"; beforeAll(async () => { wsHandler.reset("private"); server = new WS("ws://localhost:1234", { jsonProtocol: true }); client = new WebSocket("ws://localhost:1234"); await server.connected; client2 = new WebSocket("ws://localhost:1234"); await server.connected; }); afterAll(() => { WS.clean(); }); test('create session1', async () => { expect(client).not.toBeNull(); await wsHandler.add(client); }); test('create session2', async () => { expect(client2).not.toBeNull(); await wsHandler.add(client2); }); test('create connection from session1', async () => { await wsHandler.onConnect(client, connectionId); await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: false, role: "host", participantId: anyParticipantId }); }); test('create connection from session2', async () => { await wsHandler.onConnect(client2, connectionId); await expect(server).toReceiveMessage({ type: "participant-joined", connectionId: connectionId, participantId: anyParticipantId }); await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: true, role: "participant", participantId: anyParticipantId }); }); test('save room and member info', async () => { wsHandler.onHostUserInfo(client, { id: 'host-user', name: 'Host User', avatar: '/host.png' }); wsHandler.onHostUserInfo(client2, { id: 'guest-user', name: 'Guest User', avatar: '/guest.png' }); expect(wsHandler.onGetRooms()).toEqual([ expect.objectContaining({ roomId: connectionId, connectionId: connectionId, userCount: 2, members: expect.arrayContaining([ expect.objectContaining({ connectionId: connectionId, role: 'host', userId: 'host-user', name: 'Host User', avatar: '/host.png' }), expect.objectContaining({ connectionId: connectionId, role: 'participant', userId: 'guest-user', name: 'Guest User', avatar: '/guest.png' }) ]) }) ]); }); test('broadcast recording status to room members', async () => { const session = { id: 'recording-1', connectionId: connectionId, status: 'recording', layout: 'grid', format: 'webm', createdAt: '2026-06-01T00:00:00.000Z', startedAt: '2026-06-01T00:00:00.000Z', updatedAt: '2026-06-01T00:00:00.000Z' } as any; const expected = { type: 'recording-started', connectionId: connectionId, recordingId: 'recording-1', status: 'recording', layout: 'grid', format: 'webm', startedAt: '2026-06-01T00:00:00.000Z' }; expect(wsHandler.broadcastRecordingStarted(session)).toBe(true); await expect(server).toReceiveMessage(recordingEnvelope(connectionId, expected)); await expect(server).toReceiveMessage(recordingEnvelope(connectionId, expected)); expect(wsHandler.broadcastRecordingPeerRequest(session)).toBe(true); const peerRequest = { ...expected, type: 'recording-peer-request', mediaMode: 'webrtc-sendonly' }; await expect(server).toReceiveMessage(recordingEnvelope(connectionId, peerRequest)); await expect(server).toReceiveMessage(recordingEnvelope(connectionId, peerRequest)); }); test('send offer from session1', async () => { await wsHandler.onOffer(client, { connectionId: connectionId, sdp: testsdp }); const receiveOffer = new Offer(testsdp, Date.now(), true); await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "offer", data: receiveOffer, participantId: anyParticipantId }); }); test('send answer from session2', async () => { await wsHandler.onAnswer(client2, { connectionId: connectionId, sdp: testsdp }); const receiveAnswer = new Answer(testsdp, Date.now()); await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "answer", data: receiveAnswer, participantId: anyParticipantId }); }); test('send candidate from sesson1', async () => { const msg = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: "0" }; await wsHandler.onCandidate(client, msg); const receiveCandidate = new Candidate("testcandidate", 0, "0", Date.now()); await expect(server).toReceiveMessage({ from: connectionId, to: "", type: "candidate", data: receiveCandidate }); expect(server).toHaveReceivedMessages([{ from: connectionId, to: "", type: "candidate", data: receiveCandidate }]); }); test('delete connection from session2', async () => { await wsHandler.onDisconnect(client2, connectionId); await expect(server).toReceiveMessage({ type: "participant-left", connectionId: connectionId, participantId: anyParticipantId }); await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); }); test('delete connection from session1', async () => { await wsHandler.onDisconnect(client, connectionId); await expect(server).toReceiveMessage({ type: "disconnect", connectionId: connectionId }); }); test('delete session2', async () => { expect(client).not.toBeNull(); await wsHandler.remove(client2); }); test('delete session1', async () => { expect(client2).not.toBeNull(); await wsHandler.remove(client); }); }); describe('recording offer validation', () => { let server: WS; let client: WebSocket; const connectionId = "recording-room"; beforeAll(async () => { wsHandler.reset("private"); server = new WS("ws://localhost:1234", { jsonProtocol: true }); client = new WebSocket("ws://localhost:1234"); await server.connected; await wsHandler.add(client); await wsHandler.onConnect(client, connectionId); await expect(server).toReceiveMessage({ type: "connect", connectionId: connectionId, polite: false, role: "host", participantId: anyParticipantId }); }); afterAll(() => { WS.clean(); }); test('rejects recording offers without audio or video media sections', async () => { await wsHandler.onRecordingOffer(client, { recordingId: 'recording-empty-offer', connectionId, sdp: [ 'v=0', 'o=- 25268170 0 IN IP4 0.0.0.0', 's=-', 't=0 0', 'a=group:BUNDLE ', 'a=extmap-allow-mixed', 'a=msid-semantic:WMS *', '' ].join('\r\n') }); await expect(server).toReceiveMessage(expect.objectContaining({ from: connectionId, to: "", type: "on-message", data: expect.stringContaining('"status":"no-media-offer"') })); }); });