mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
485 lines
19 KiB
TypeScript
485 lines
19 KiB
TypeScript
// Tests for simple session events that delegate 1:1 to SessionPersistence
|
|
// or RoomPersistence with minimal logic.
|
|
|
|
jest.mock('../../persistence', () => ({
|
|
SessionPersistence: {
|
|
gameJoined: jest.fn(),
|
|
notifyUser: jest.fn(),
|
|
replayAdded: jest.fn(),
|
|
serverMessage: jest.fn(),
|
|
serverShutdown: jest.fn(),
|
|
updateUsers: jest.fn(),
|
|
updateInfo: jest.fn(),
|
|
userJoined: jest.fn(),
|
|
userLeft: jest.fn(),
|
|
userMessage: jest.fn(),
|
|
addToBuddyList: jest.fn(),
|
|
addToIgnoreList: jest.fn(),
|
|
removeFromBuddyList: jest.fn(),
|
|
removeFromIgnoreList: jest.fn(),
|
|
playerPropertiesChanged: jest.fn(),
|
|
},
|
|
RoomPersistence: {
|
|
updateRooms: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
jest.mock('../../WebClient', () => ({
|
|
__esModule: true,
|
|
default: {
|
|
clientOptions: { autojoinrooms: false },
|
|
options: {},
|
|
protocolVersion: 14,
|
|
},
|
|
}));
|
|
|
|
jest.mock('../../commands/session', () => ({
|
|
joinRoom: jest.fn(),
|
|
updateStatus: jest.fn(),
|
|
disconnect: jest.fn(),
|
|
login: jest.fn(),
|
|
register: jest.fn(),
|
|
activate: jest.fn(),
|
|
requestPasswordSalt: jest.fn(),
|
|
forgotPasswordRequest: jest.fn(),
|
|
forgotPasswordChallenge: jest.fn(),
|
|
forgotPasswordReset: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('../../utils', () => ({
|
|
generateSalt: jest.fn().mockReturnValue('newSalt'),
|
|
passwordSaltSupported: jest.fn().mockReturnValue(0),
|
|
}));
|
|
|
|
jest.mock('../../services/ProtoController', () => ({
|
|
ProtoController: {
|
|
root: {
|
|
Event_ConnectionClosed: {
|
|
CloseReason: {
|
|
USER_LIMIT_REACHED: 0,
|
|
TOO_MANY_CONNECTIONS: 1,
|
|
BANNED: 2,
|
|
DEMOTED: 3,
|
|
SERVER_SHUTDOWN: 4,
|
|
USERNAMEINVALID: 5,
|
|
LOGGEDINELSEWERE: 6,
|
|
OTHER: 7,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
import { WebSocketConnectReason } from 'types';
|
|
|
|
import { SessionPersistence, RoomPersistence } from '../../persistence';
|
|
import webClient from '../../WebClient';
|
|
import * as SessionCmds from '../../commands/session';
|
|
import * as Utils from '../../utils';
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
(Utils.generateSalt as jest.Mock).mockReturnValue('newSalt');
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// gameJoined
|
|
// ----------------------------------------------------------------
|
|
describe('gameJoined', () => {
|
|
const { gameJoined } = jest.requireActual('./gameJoined');
|
|
|
|
it('calls SessionPersistence.gameJoined', () => {
|
|
const data = { gameId: 1 } as any;
|
|
gameJoined(data);
|
|
expect(SessionPersistence.gameJoined).toHaveBeenCalledWith(data);
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// notifyUser
|
|
// ----------------------------------------------------------------
|
|
describe('notifyUser', () => {
|
|
const { notifyUser } = jest.requireActual('./notifyUser');
|
|
|
|
it('calls SessionPersistence.notifyUser', () => {
|
|
const data = { message: 'yo' } as any;
|
|
notifyUser(data);
|
|
expect(SessionPersistence.notifyUser).toHaveBeenCalledWith(data);
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// replayAdded
|
|
// ----------------------------------------------------------------
|
|
describe('replayAdded', () => {
|
|
const { replayAdded } = jest.requireActual('./replayAdded');
|
|
|
|
it('calls SessionPersistence.replayAdded with matchInfo', () => {
|
|
replayAdded({ matchInfo: { id: 42 } } as any);
|
|
expect(SessionPersistence.replayAdded).toHaveBeenCalledWith({ id: 42 });
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// serverCompleteList
|
|
// ----------------------------------------------------------------
|
|
describe('serverCompleteList', () => {
|
|
const { serverCompleteList } = jest.requireActual('./serverCompleteList');
|
|
|
|
it('calls SessionPersistence.updateUsers and RoomPersistence.updateRooms', () => {
|
|
serverCompleteList({ userList: ['u'], roomList: ['r'] } as any);
|
|
expect(SessionPersistence.updateUsers).toHaveBeenCalledWith(['u']);
|
|
expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(['r']);
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// serverMessage
|
|
// ----------------------------------------------------------------
|
|
describe('serverMessage', () => {
|
|
const { serverMessage } = jest.requireActual('./serverMessage');
|
|
|
|
it('calls SessionPersistence.serverMessage with message', () => {
|
|
serverMessage({ message: 'hello server' });
|
|
expect(SessionPersistence.serverMessage).toHaveBeenCalledWith('hello server');
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// serverShutdown
|
|
// ----------------------------------------------------------------
|
|
describe('serverShutdown', () => {
|
|
const { serverShutdown } = jest.requireActual('./serverShutdown');
|
|
|
|
it('calls SessionPersistence.serverShutdown', () => {
|
|
const payload = { reason: 'maintenance' } as any;
|
|
serverShutdown(payload);
|
|
expect(SessionPersistence.serverShutdown).toHaveBeenCalledWith(payload);
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// userJoined
|
|
// ----------------------------------------------------------------
|
|
describe('userJoined', () => {
|
|
const { userJoined } = jest.requireActual('./userJoined');
|
|
|
|
it('calls SessionPersistence.userJoined with userInfo', () => {
|
|
userJoined({ userInfo: { name: 'alice' } } as any);
|
|
expect(SessionPersistence.userJoined).toHaveBeenCalledWith({ name: 'alice' });
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// userLeft
|
|
// ----------------------------------------------------------------
|
|
describe('userLeft', () => {
|
|
const { userLeft } = jest.requireActual('./userLeft');
|
|
|
|
it('calls SessionPersistence.userLeft with name', () => {
|
|
userLeft({ name: 'bob' });
|
|
expect(SessionPersistence.userLeft).toHaveBeenCalledWith('bob');
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// userMessage
|
|
// ----------------------------------------------------------------
|
|
describe('userMessage', () => {
|
|
const { userMessage } = jest.requireActual('./userMessage');
|
|
|
|
it('calls SessionPersistence.userMessage', () => {
|
|
const payload = { userName: 'alice', message: 'hi' } as any;
|
|
userMessage(payload);
|
|
expect(SessionPersistence.userMessage).toHaveBeenCalledWith(payload);
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// addToList
|
|
// ----------------------------------------------------------------
|
|
describe('addToList', () => {
|
|
const { addToList } = jest.requireActual('./addToList');
|
|
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
afterAll(() => logSpy.mockRestore());
|
|
|
|
it('buddy list → addToBuddyList', () => {
|
|
addToList({ listName: 'buddy', userInfo: { name: 'alice' } } as any);
|
|
expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith({ name: 'alice' });
|
|
});
|
|
|
|
it('ignore list → addToIgnoreList', () => {
|
|
addToList({ listName: 'ignore', userInfo: { name: 'bob' } } as any);
|
|
expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith({ name: 'bob' });
|
|
});
|
|
|
|
it('unknown list → console.log', () => {
|
|
addToList({ listName: 'unknown', userInfo: {} } as any);
|
|
expect(logSpy).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// removeFromList
|
|
// ----------------------------------------------------------------
|
|
describe('removeFromList', () => {
|
|
const { removeFromList } = jest.requireActual('./removeFromList');
|
|
|
|
it('buddy list → removeFromBuddyList', () => {
|
|
removeFromList({ listName: 'buddy', userName: 'alice' } as any);
|
|
expect(SessionPersistence.removeFromBuddyList).toHaveBeenCalledWith('alice');
|
|
});
|
|
|
|
it('ignore list → removeFromIgnoreList', () => {
|
|
removeFromList({ listName: 'ignore', userName: 'bob' } as any);
|
|
expect(SessionPersistence.removeFromIgnoreList).toHaveBeenCalledWith('bob');
|
|
});
|
|
|
|
it('unknown list → console.log', () => {
|
|
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
removeFromList({ listName: 'other', userName: 'x' } as any);
|
|
expect(logSpy).toHaveBeenCalled();
|
|
logSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// listRooms
|
|
// ----------------------------------------------------------------
|
|
describe('listRooms', () => {
|
|
const { listRooms } = jest.requireActual('./listRooms');
|
|
|
|
it('calls RoomPersistence.updateRooms', () => {
|
|
listRooms({ roomList: [] });
|
|
expect(RoomPersistence.updateRooms).toHaveBeenCalledWith([]);
|
|
});
|
|
|
|
it('does not call joinRoom when autojoinrooms is false', () => {
|
|
(webClient as any).clientOptions = { autojoinrooms: false };
|
|
listRooms({ roomList: [{ autoJoin: true, roomId: 1 }] } as any);
|
|
expect(SessionCmds.joinRoom).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('calls joinRoom for autoJoin rooms when autojoinrooms is true', () => {
|
|
(webClient as any).clientOptions = { autojoinrooms: true };
|
|
listRooms({ roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] } as any);
|
|
expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1);
|
|
expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2);
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// connectionClosed
|
|
// ----------------------------------------------------------------
|
|
describe('connectionClosed', () => {
|
|
const { connectionClosed } = jest.requireActual('./connectionClosed');
|
|
|
|
it('uses reasonStr when provided', () => {
|
|
connectionClosed({ reason: 0, reasonStr: 'custom' } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom');
|
|
});
|
|
|
|
it('USER_LIMIT_REACHED → specific message', () => {
|
|
connectionClosed({ reason: 0 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
expect.stringContaining('maximum user capacity')
|
|
);
|
|
});
|
|
|
|
it('TOO_MANY_CONNECTIONS → specific message', () => {
|
|
connectionClosed({ reason: 1 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('too many concurrent'));
|
|
});
|
|
|
|
it('BANNED → specific message', () => {
|
|
connectionClosed({ reason: 2 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('banned'));
|
|
});
|
|
|
|
it('DEMOTED → specific message', () => {
|
|
connectionClosed({ reason: 3 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('demoted'));
|
|
});
|
|
|
|
it('SERVER_SHUTDOWN → specific message', () => {
|
|
connectionClosed({ reason: 4 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('shutdown'));
|
|
});
|
|
|
|
it('USERNAMEINVALID → specific message', () => {
|
|
connectionClosed({ reason: 5 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('username'));
|
|
});
|
|
|
|
it('LOGGEDINELSEWERE → specific message', () => {
|
|
connectionClosed({ reason: 6 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('logged out'));
|
|
});
|
|
|
|
it('OTHER → "Unknown reason"', () => {
|
|
connectionClosed({ reason: 7 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason');
|
|
});
|
|
|
|
it('BANNED with valid positive endTime → shows formatted date', () => {
|
|
connectionClosed({ reason: 2, endTime: 1700000000 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
expect.stringContaining('You are banned until')
|
|
);
|
|
});
|
|
|
|
it('BANNED with endTime = 0 → shows generic banned message', () => {
|
|
connectionClosed({ reason: 2, endTime: 0 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
|
});
|
|
|
|
it('BANNED with endTime = -1 → shows generic banned message', () => {
|
|
connectionClosed({ reason: 2, endTime: -1 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
|
});
|
|
|
|
it('BANNED with endTime = NaN → shows generic banned message', () => {
|
|
connectionClosed({ reason: 2, endTime: NaN } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
|
});
|
|
|
|
it('BANNED with endTime = Infinity → shows generic banned message', () => {
|
|
connectionClosed({ reason: 2, endTime: Infinity } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
|
});
|
|
|
|
it('BANNED with reasonStr → uses reasonStr regardless of endTime', () => {
|
|
connectionClosed({ reason: 2, endTime: 0, reasonStr: 'custom ban reason' } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom ban reason');
|
|
});
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// serverIdentification
|
|
// ----------------------------------------------------------------
|
|
describe('serverIdentification', () => {
|
|
const { serverIdentification } = jest.requireActual('./serverIdentification');
|
|
|
|
beforeEach(() => {
|
|
(webClient as any).protocolVersion = 14;
|
|
(webClient as any).options = {};
|
|
});
|
|
|
|
it('disconnects when protocolVersion mismatches', () => {
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalled();
|
|
expect(SessionCmds.disconnect).toHaveBeenCalled();
|
|
});
|
|
|
|
it('LOGIN reason without salt → calls login with password as separate param', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.login).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ password: expect.anything() }),
|
|
'secret'
|
|
);
|
|
});
|
|
|
|
it('LOGIN reason with salt → calls requestPasswordSalt with password as separate param', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
|
expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ password: expect.anything() }),
|
|
'secret'
|
|
);
|
|
});
|
|
|
|
it('REGISTER reason without salt → calls register with password and null salt', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.register).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ password: expect.anything() }),
|
|
'secret',
|
|
null
|
|
);
|
|
});
|
|
|
|
it('REGISTER reason with salt → calls register with password and generated salt', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
|
expect(SessionCmds.register).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ password: expect.anything() }),
|
|
'secret',
|
|
'newSalt'
|
|
);
|
|
});
|
|
|
|
it('ACTIVATE_ACCOUNT reason without salt → calls activate with password as separate param', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.activate).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ password: expect.anything() }),
|
|
'secret'
|
|
);
|
|
});
|
|
|
|
it('ACTIVATE_ACCOUNT reason with salt → calls requestPasswordSalt with password as separate param', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.ACTIVATE_ACCOUNT, password: 'secret' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
|
expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ password: expect.anything() }),
|
|
'secret'
|
|
);
|
|
});
|
|
|
|
it('PASSWORD_RESET_REQUEST reason → calls forgotPasswordRequest', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_REQUEST };
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.forgotPasswordRequest).toHaveBeenCalled();
|
|
});
|
|
|
|
it('PASSWORD_RESET_CHALLENGE reason → calls forgotPasswordChallenge', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET_CHALLENGE };
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.forgotPasswordChallenge).toHaveBeenCalled();
|
|
});
|
|
|
|
it('PASSWORD_RESET reason without salt → calls forgotPasswordReset with newPassword as separate param', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.forgotPasswordReset).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ newPassword: expect.anything() }),
|
|
'newpw'
|
|
);
|
|
});
|
|
|
|
it('PASSWORD_RESET reason with salt → calls requestPasswordSalt with newPassword as separate param', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.PASSWORD_RESET, newPassword: 'newpw' };
|
|
(Utils.passwordSaltSupported as jest.Mock).mockReturnValue(1);
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
|
expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({ newPassword: expect.anything() }),
|
|
undefined,
|
|
'newpw'
|
|
);
|
|
});
|
|
|
|
it('unknown reason → updateStatus DISCONNECTED and disconnect', () => {
|
|
(webClient as any).options = { reason: 999 };
|
|
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionCmds.updateStatus).toHaveBeenCalled();
|
|
expect(SessionCmds.disconnect).toHaveBeenCalled();
|
|
});
|
|
|
|
it('updates webClient.options to empty and calls SessionPersistence.updateInfo', () => {
|
|
(webClient as any).options = { reason: WebSocketConnectReason.LOGIN };
|
|
serverIdentification({ serverName: 'myServer', serverVersion: '2.0', protocolVersion: 14, serverOptions: 0 } as any);
|
|
expect(SessionPersistence.updateInfo).toHaveBeenCalledWith('myServer', '2.0');
|
|
expect((webClient as any).options).toEqual({});
|
|
});
|
|
});
|