Files
video_socket-server/test/websockethandler.test.ts

345 lines
11 KiB
TypeScript

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