2026-04-29 15:18:30 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-05-23 23:49:47 +08:00
|
|
|
const anyParticipantId = expect.any(String);
|
|
|
|
|
|
2026-06-02 23:17:15 +08:00
|
|
|
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
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 15:18:30 +08:00
|
|
|
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);
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({
|
|
|
|
|
type: "connect",
|
|
|
|
|
connectionId: connectionId,
|
|
|
|
|
polite: true,
|
|
|
|
|
role: "participant",
|
|
|
|
|
participantId: anyParticipantId
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('create connection from session2', async () => {
|
|
|
|
|
await wsHandler.onConnect(client2, connectionId2);
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({
|
|
|
|
|
type: "connect",
|
|
|
|
|
connectionId: connectionId2,
|
|
|
|
|
polite: true,
|
|
|
|
|
role: "participant",
|
|
|
|
|
participantId: anyParticipantId
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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());
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({
|
|
|
|
|
from: connectionId,
|
|
|
|
|
to: "",
|
|
|
|
|
type: "answer",
|
|
|
|
|
data: receiveAnswer,
|
|
|
|
|
participantId: anyParticipantId
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('send candidate from sesson1', async () => {
|
|
|
|
|
const msg = { connectionId: connectionId, candidate: "testcandidate", sdpMLineIndex: 0, sdpMid: "0" };
|
|
|
|
|
await wsHandler.onCandidate(client, msg);
|
2026-05-23 23:49:47 +08:00
|
|
|
expect(true).toBe(true);
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('delete connection from session2', async () => {
|
|
|
|
|
await wsHandler.onDisconnect(client2, connectionId);
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({ type: "participant-left", connectionId: connectionId, participantId: anyParticipantId });
|
2026-04-29 15:18:30 +08:00
|
|
|
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);
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({
|
|
|
|
|
type: "connect",
|
|
|
|
|
connectionId: connectionId,
|
|
|
|
|
polite: false,
|
|
|
|
|
role: "host",
|
|
|
|
|
participantId: anyParticipantId
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('create connection from session2', async () => {
|
|
|
|
|
await wsHandler.onConnect(client2, connectionId);
|
2026-05-23 23:49:47 +08:00
|
|
|
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
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
2026-05-25 16:58:41 +08:00
|
|
|
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'
|
|
|
|
|
})
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-02 02:34:40 +08:00
|
|
|
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);
|
2026-06-02 23:17:15 +08:00
|
|
|
await expect(server).toReceiveMessage(recordingEnvelope(connectionId, expected));
|
|
|
|
|
await expect(server).toReceiveMessage(recordingEnvelope(connectionId, expected));
|
2026-06-02 02:34:40 +08:00
|
|
|
|
|
|
|
|
expect(wsHandler.broadcastRecordingPeerRequest(session)).toBe(true);
|
2026-06-02 23:17:15 +08:00
|
|
|
const peerRequest = {
|
2026-06-02 02:34:40 +08:00
|
|
|
...expected,
|
|
|
|
|
type: 'recording-peer-request',
|
|
|
|
|
mediaMode: 'webrtc-sendonly'
|
2026-06-02 23:17:15 +08:00
|
|
|
};
|
|
|
|
|
await expect(server).toReceiveMessage(recordingEnvelope(connectionId, peerRequest));
|
|
|
|
|
await expect(server).toReceiveMessage(recordingEnvelope(connectionId, peerRequest));
|
2026-06-02 02:34:40 +08:00
|
|
|
});
|
|
|
|
|
|
2026-04-29 15:18:30 +08:00
|
|
|
test('send offer from session1', async () => {
|
|
|
|
|
await wsHandler.onOffer(client, { connectionId: connectionId, sdp: testsdp });
|
|
|
|
|
const receiveOffer = new Offer(testsdp, Date.now(), true);
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({
|
|
|
|
|
from: connectionId,
|
|
|
|
|
to: "",
|
|
|
|
|
type: "offer",
|
|
|
|
|
data: receiveOffer,
|
|
|
|
|
participantId: anyParticipantId
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('send answer from session2', async () => {
|
|
|
|
|
await wsHandler.onAnswer(client2, { connectionId: connectionId, sdp: testsdp });
|
|
|
|
|
const receiveAnswer = new Answer(testsdp, Date.now());
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({
|
|
|
|
|
from: connectionId,
|
|
|
|
|
to: "",
|
|
|
|
|
type: "answer",
|
|
|
|
|
data: receiveAnswer,
|
|
|
|
|
participantId: anyParticipantId
|
|
|
|
|
});
|
2026-04-29 15:18:30 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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);
|
2026-05-23 23:49:47 +08:00
|
|
|
await expect(server).toReceiveMessage({ type: "participant-left", connectionId: connectionId, participantId: anyParticipantId });
|
2026-04-29 15:18:30 +08:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|