Files
Toju/server/src/websocket/broadcast.spec.ts

103 lines
3.3 KiB
TypeScript

import {
beforeEach,
describe,
expect,
it
} from 'vitest';
import { WebSocket } from 'ws';
import { connectedUsers } from './state';
import { ConnectedUser } from './types';
import { broadcastToServer, findUserByOderId, findVoiceActiveConnection } from './broadcast';
function createMockWs(): WebSocket & { sentMessages: string[] } {
const sent: string[] = [];
const ws = {
readyState: WebSocket.OPEN,
send: (data: string) => { sent.push(data); },
close: () => {},
sentMessages: sent
} as unknown as WebSocket & { sentMessages: string[] };
return ws;
}
function createConnectedUser(
connectionId: string,
oderId: string,
overrides: Partial<ConnectedUser> = {}
): ConnectedUser {
const user: ConnectedUser = {
oderId,
ws: createMockWs(),
authenticated: true,
serverIds: new Set(['server-1']),
displayName: 'Test User',
lastPong: Date.now(),
...overrides
};
connectedUsers.set(connectionId, user);
return user;
}
describe('broadcastToServer', () => {
beforeEach(() => {
connectedUsers.clear();
});
it('delivers chat_message to every connection in the server except the sender connection', () => {
createConnectedUser('conn-a1', 'user-1');
const connA2 = createConnectedUser('conn-a2', 'user-1');
const connB = createConnectedUser('conn-b', 'user-2');
broadcastToServer('server-1', { type: 'chat_message', text: 'hello' }, {
excludeConnectionId: 'conn-a1'
});
expect((connA2.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(1);
expect((connB.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(1);
expect(connectedUsers.get('conn-a1')?.ws).toBeDefined();
expect((connectedUsers.get('conn-a1')!.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(0);
});
it('excludes every connection for an identity when excludeIdentityOderId is set', () => {
const connA1 = createConnectedUser('conn-a1', 'user-1');
const connA2 = createConnectedUser('conn-a2', 'user-1');
const connB = createConnectedUser('conn-b', 'user-2');
broadcastToServer('server-1', { type: 'user_left', oderId: 'user-1' }, {
excludeIdentityOderId: 'user-1'
});
expect((connA1.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(0);
expect((connA2.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(0);
expect((connB.ws as WebSocket & { sentMessages: string[] }).sentMessages).toHaveLength(1);
});
});
describe('findVoiceActiveConnection', () => {
beforeEach(() => {
connectedUsers.clear();
});
it('returns the connection marked voiceActive for the user', () => {
createConnectedUser('conn-passive', 'user-1', { voiceActive: false });
const active = createConnectedUser('conn-active', 'user-1', { voiceActive: true });
expect(findVoiceActiveConnection('user-1')).toBe(active);
});
it('returns undefined when no voiceActive connection exists', () => {
createConnectedUser('conn-1', 'user-1');
expect(findVoiceActiveConnection('user-1')).toBeUndefined();
});
it('findUserByOderId falls back to any open connection when no voiceActive connection exists', () => {
const fallback = createConnectedUser('conn-1', 'user-1');
expect(findUserByOderId('user-1')).toBe(fallback);
});
});