mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
upgrade packages + improve typing
This commit is contained in:
parent
fd55f4fb7f
commit
19f5eefdd2
138 changed files with 4504 additions and 11015 deletions
|
|
@ -1,18 +1,22 @@
|
|||
vi.mock('./services/WebSocketService', () => ({
|
||||
WebSocketService: vi.fn().mockImplementation(() => ({
|
||||
message$: { subscribe: vi.fn() },
|
||||
connect: vi.fn(),
|
||||
testConnect: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
})),
|
||||
WebSocketService: vi.fn().mockImplementation(function WebSocketServiceImpl() {
|
||||
return {
|
||||
message$: { subscribe: vi.fn() },
|
||||
connect: vi.fn(),
|
||||
testConnect: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('./services/ProtobufService', () => ({
|
||||
ProtobufService: vi.fn().mockImplementation(() => ({
|
||||
handleMessageEvent: vi.fn(),
|
||||
sendKeepAliveCommand: vi.fn(),
|
||||
resetCommands: vi.fn(),
|
||||
})),
|
||||
ProtobufService: vi.fn().mockImplementation(function ProtobufServiceImpl() {
|
||||
return {
|
||||
handleMessageEvent: vi.fn(),
|
||||
sendKeepAliveCommand: vi.fn(),
|
||||
resetCommands: vi.fn(),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('./persistence', () => ({
|
||||
|
|
@ -20,12 +24,17 @@ vi.mock('./persistence', () => ({
|
|||
SessionPersistence: { clearStore: vi.fn(), initialized: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock('store', () => ({
|
||||
GameDispatch: { clearStore: vi.fn() },
|
||||
}));
|
||||
|
||||
import { WebClient } from './WebClient';
|
||||
import { WebSocketService } from './services/WebSocketService';
|
||||
import { ProtobufService } from './services/ProtobufService';
|
||||
import { RoomPersistence, SessionPersistence } from './persistence';
|
||||
import { StatusEnum } from 'types';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
describe('WebClient', () => {
|
||||
let client: WebClient;
|
||||
|
|
@ -33,18 +42,22 @@ describe('WebClient', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(ProtobufService as vi.Mock).mockImplementation(() => ({
|
||||
handleMessageEvent: vi.fn(),
|
||||
sendKeepAliveCommand: vi.fn(),
|
||||
resetCommands: vi.fn(),
|
||||
}));
|
||||
(ProtobufService as Mock).mockImplementation(function ProtobufServiceImpl() {
|
||||
return {
|
||||
handleMessageEvent: vi.fn(),
|
||||
sendKeepAliveCommand: vi.fn(),
|
||||
resetCommands: vi.fn(),
|
||||
};
|
||||
});
|
||||
messageSubject = new Subject<MessageEvent>();
|
||||
(WebSocketService as vi.Mock).mockImplementation(() => ({
|
||||
message$: messageSubject,
|
||||
connect: vi.fn(),
|
||||
testConnect: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
}));
|
||||
(WebSocketService as Mock).mockImplementation(function WebSocketServiceImpl() {
|
||||
return {
|
||||
message$: messageSubject,
|
||||
connect: vi.fn(),
|
||||
testConnect: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
};
|
||||
});
|
||||
// suppress console.log from constructor in non-test-env check
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
client = new WebClient();
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ export class WebClient {
|
|||
}
|
||||
}
|
||||
|
||||
public keepAlive(pingReceived: Function) {
|
||||
public keepAlive(pingReceived: () => void) {
|
||||
this.protobuf.sendKeepAliveCommand(pingReceived);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
* Defaults to 2 (ext, value, options).
|
||||
* Use 3 for sendRoomCommand (roomId, ext, value, options).
|
||||
*/
|
||||
export function makeCallbackHelpers(mockFn: vi.Mock, optsArgIndex = 2) {
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
export function makeCallbackHelpers(mockFn: Mock, optsArgIndex = 2) {
|
||||
function getLastSendOpts() {
|
||||
const calls = mockFn.mock.calls;
|
||||
return calls[calls.length - 1]?.[optsArgIndex];
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function makeMockWebSocketInstance() {
|
|||
return {
|
||||
send: vi.fn(),
|
||||
close: vi.fn(),
|
||||
readyState: WebSocket.OPEN,
|
||||
readyState: WebSocket.OPEN as number,
|
||||
binaryType: '' as BinaryType,
|
||||
onopen: null as any,
|
||||
onclose: null as any,
|
||||
|
|
@ -18,12 +18,17 @@ export function makeMockWebSocketInstance() {
|
|||
};
|
||||
}
|
||||
|
||||
/** Installs a mock WebSocket constructor on global. Returns the mock instance. */
|
||||
/** Installs a mock WebSocket constructor on global. Returns the mock instance and a cleanup function. */
|
||||
export function installMockWebSocket() {
|
||||
const originalWebSocket = (globalThis as any).WebSocket;
|
||||
const mockInstance = makeMockWebSocketInstance();
|
||||
const MockWS = vi.fn(() => mockInstance) as any;
|
||||
const MockWS = vi.fn(function MockWebSocket() {
|
||||
return mockInstance;
|
||||
}) as any;
|
||||
MockWS.OPEN = 1;
|
||||
MockWS.CLOSED = 3;
|
||||
(global as any).WebSocket = MockWS;
|
||||
return { MockWS, mockInstance };
|
||||
(globalThis as any).WebSocket = MockWS;
|
||||
return { MockWS, mockInstance, restore: () => {
|
||||
(globalThis as any).WebSocket = originalWebSocket;
|
||||
} };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import { reloadConfig } from './reloadConfig';
|
|||
import { shutdownServer } from './shutdownServer';
|
||||
import { updateServerMessage } from './updateServerMessage';
|
||||
|
||||
const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers(
|
||||
BackendService.sendAdminCommand as vi.Mock,
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
const { invokeOnSuccess } = makeCallbackHelpers(
|
||||
BackendService.sendAdminCommand as Mock,
|
||||
2
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -72,8 +72,10 @@ vi.mock('../../services/BackendService', () => ({
|
|||
|
||||
const gameId = 1;
|
||||
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
beforeEach(() => {
|
||||
(BackendService.sendGameCommand as vi.Mock).mockClear();
|
||||
(BackendService.sendGameCommand as Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('Game commands — delegate to BackendService.sendGameCommand', () => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function getWarnList(modName: string, userName: string, userClientid: str
|
|||
BackendService.sendModeratorCommand(Command_GetWarnList_ext, create(Command_GetWarnListSchema, { modName, userName, userClientid }), {
|
||||
responseExt: Response_WarnList_ext,
|
||||
onSuccess: (response) => {
|
||||
ModeratorPersistence.warnListOptions(response.warning);
|
||||
ModeratorPersistence.warnListOptions([response]);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@ import { updateAdminNotes } from './updateAdminNotes';
|
|||
import { viewLogHistory } from './viewLogHistory';
|
||||
import { warnUser } from './warnUser';
|
||||
|
||||
const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers(
|
||||
BackendService.sendModeratorCommand as vi.Mock,
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
const { invokeOnSuccess } = makeCallbackHelpers(
|
||||
BackendService.sendModeratorCommand as Mock,
|
||||
2
|
||||
);
|
||||
|
||||
|
|
@ -175,11 +177,11 @@ describe('getWarnList', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('onSuccess calls ModeratorPersistence.warnListOptions with warning', () => {
|
||||
it('onSuccess calls ModeratorPersistence.warnListOptions with the response', () => {
|
||||
getWarnList('mod1', 'alice', 'US');
|
||||
const resp = { warning: ['w1', 'w2'] };
|
||||
const resp = { warning: ['w1', 'w2'], userName: 'alice', userClientid: 'US' };
|
||||
invokeOnSuccess(resp, { responseCode: 0 });
|
||||
expect(ModeratorPersistence.warnListOptions).toHaveBeenCalledWith(['w1', 'w2']);
|
||||
expect(ModeratorPersistence.warnListOptions).toHaveBeenCalledWith([resp]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import { joinGame } from './joinGame';
|
|||
import { leaveRoom } from './leaveRoom';
|
||||
import { roomSay } from './roomSay';
|
||||
|
||||
const { getLastSendOpts, invokeOnSuccess } = makeCallbackHelpers(
|
||||
BackendService.sendRoomCommand as vi.Mock,
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
const { invokeOnSuccess } = makeCallbackHelpers(
|
||||
BackendService.sendRoomCommand as Mock,
|
||||
// sendRoomCommand(roomId, ext, value, options) — options at index 3
|
||||
3
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ export function deckList(): void {
|
|||
BackendService.sendSessionCommand(Command_DeckList_ext, create(Command_DeckListSchema), {
|
||||
responseExt: Response_DeckList_ext,
|
||||
onSuccess: (response) => {
|
||||
SessionPersistence.updateServerDecks(response);
|
||||
if (response.root) {
|
||||
SessionPersistence.updateServerDecks(response);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ export function deckUpload(path: string, deckId: number, deckList: string): void
|
|||
BackendService.sendSessionCommand(Command_DeckUpload_ext, create(Command_DeckUploadSchema, { path, deckId, deckList }), {
|
||||
responseExt: Response_DeckUpload_ext,
|
||||
onSuccess: (response) => {
|
||||
SessionPersistence.uploadServerDeck(path, response.newFile);
|
||||
if (response.newFile) {
|
||||
SessionPersistence.uploadServerDeck(path, response.newFile);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ export function joinRoom(roomId: number): void {
|
|||
BackendService.sendSessionCommand(Command_JoinRoom_ext, create(Command_JoinRoomSchema, { roomId }), {
|
||||
responseExt: Response_JoinRoom_ext,
|
||||
onSuccess: (response) => {
|
||||
RoomPersistence.joinRoom(response.roomInfo);
|
||||
if (response.roomInfo) {
|
||||
RoomPersistence.joinRoom(response.roomInfo);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { create } from '@bufbuild/protobuf';
|
|||
import { BackendService } from '../../services/BackendService';
|
||||
import { Command_Ping_ext, Command_PingSchema } from 'generated/proto/session_commands_pb';
|
||||
|
||||
export function ping(pingReceived: Function): void {
|
||||
export function ping(pingReceived: () => void): void {
|
||||
BackendService.sendSessionCommand(Command_Ping_ext, create(Command_PingSchema), {
|
||||
onResponse: (raw) => pingReceived(raw),
|
||||
onResponse: () => pingReceived(),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ServerRegisterParams } from 'store';
|
||||
import { StatusEnum, WebSocketConnectOptions } from 'types';
|
||||
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { create, getExtension } from '@bufbuild/protobuf';
|
||||
import type { MessageInitShape } from '@bufbuild/protobuf';
|
||||
import webClient from '../../WebClient';
|
||||
import { BackendService } from '../../services/BackendService';
|
||||
|
|
@ -9,6 +9,7 @@ import { Command_Register_ext, Command_RegisterSchema } from 'generated/proto/se
|
|||
import { SessionPersistence } from '../../persistence';
|
||||
import { hashPassword } from '../../utils';
|
||||
import { Response_ResponseCode } from 'generated/proto/response_pb';
|
||||
import { Response_Register_ext } from 'generated/proto/response_register_pb';
|
||||
|
||||
import { login, disconnect, updateStatus } from './';
|
||||
|
||||
|
|
@ -65,9 +66,12 @@ export function register(options: WebSocketConnectOptions, password?: string, pa
|
|||
[Response_ResponseCode.RespRegistrationDisabled]: () => onRegistrationError(
|
||||
() => SessionPersistence.registrationFailed('Registration is currently disabled')
|
||||
),
|
||||
[Response_ResponseCode.RespUserIsBanned]: (raw) => onRegistrationError(
|
||||
() => SessionPersistence.registrationFailed(raw.reasonStr, raw.endTime)
|
||||
),
|
||||
[Response_ResponseCode.RespUserIsBanned]: (raw) => {
|
||||
const register = getExtension(raw, Response_Register_ext);
|
||||
onRegistrationError(
|
||||
() => SessionPersistence.registrationFailed(register.deniedReasonStr, Number(register.deniedEndTime))
|
||||
);
|
||||
},
|
||||
},
|
||||
onError: () => onRegistrationError(
|
||||
() => SessionPersistence.registrationFailed('Registration failed due to a server issue')
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ vi.mock('./', async () => {
|
|||
return makeSessionBarrelMock();
|
||||
});
|
||||
|
||||
import { Mock } from 'vitest';
|
||||
import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers';
|
||||
import { BackendService } from '../../services/BackendService';
|
||||
import { SessionPersistence } from '../../persistence';
|
||||
|
|
@ -51,6 +52,9 @@ import {
|
|||
import { Response_ForgotPasswordRequest_ext } from 'generated/proto/response_forgotpasswordrequest_pb';
|
||||
import { Response_Login_ext } from 'generated/proto/response_login_pb';
|
||||
import { Response_PasswordSalt_ext } from 'generated/proto/response_password_salt_pb';
|
||||
import { Response_Register_ext, Response_RegisterSchema } from 'generated/proto/response_register_pb';
|
||||
import { create, setExtension } from '@bufbuild/protobuf';
|
||||
import { ResponseSchema } from 'generated/proto/response_pb';
|
||||
import { connect } from './connect';
|
||||
import { updateStatus } from './updateStatus';
|
||||
import { login } from './login';
|
||||
|
|
@ -61,16 +65,16 @@ import { forgotPasswordRequest } from './forgotPasswordRequest';
|
|||
import { forgotPasswordReset } from './forgotPasswordReset';
|
||||
import { requestPasswordSalt } from './requestPasswordSalt';
|
||||
|
||||
const { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers(
|
||||
BackendService.sendSessionCommand as vi.Mock,
|
||||
const { invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers(
|
||||
BackendService.sendSessionCommand as Mock,
|
||||
2
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(hashPassword as vi.Mock).mockReturnValue('hashed_pw');
|
||||
(generateSalt as vi.Mock).mockReturnValue('randSalt');
|
||||
(passwordSaltSupported as vi.Mock).mockReturnValue(0);
|
||||
(hashPassword as Mock).mockReturnValue('hashed_pw');
|
||||
(generateSalt as Mock).mockReturnValue('randSalt');
|
||||
(passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
|
@ -182,7 +186,7 @@ describe('login', () => {
|
|||
login({ userName: 'alice' } as any, 'secret');
|
||||
const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } };
|
||||
invokeOnSuccess(loginResp, { responseCode: 0 });
|
||||
const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0];
|
||||
const calledWith = (SessionPersistence.loginSuccessful as Mock).mock.calls[0][0];
|
||||
expect(calledWith).not.toHaveProperty('password');
|
||||
});
|
||||
|
||||
|
|
@ -190,7 +194,7 @@ describe('login', () => {
|
|||
login({ userName: 'alice' } as any, 'pw', 'salt');
|
||||
const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } };
|
||||
invokeOnSuccess(loginResp, { responseCode: 0 });
|
||||
const calledWith = (SessionPersistence.loginSuccessful as vi.Mock).mock.calls[0][0];
|
||||
const calledWith = (SessionPersistence.loginSuccessful as Mock).mock.calls[0][0];
|
||||
expect(calledWith).toHaveProperty('hashedPassword', 'hashed_pw');
|
||||
});
|
||||
|
||||
|
|
@ -347,9 +351,11 @@ describe('register', () => {
|
|||
expect(SessionPersistence.registrationFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespUserIsBanned calls registrationFailed with raw.reasonStr and raw.endTime', () => {
|
||||
it('RespUserIsBanned calls registrationFailed with deniedReasonStr and deniedEndTime', () => {
|
||||
register({ userName: 'alice' } as any, 'pw');
|
||||
invokeResponseCode(Response_ResponseCode.RespUserIsBanned, { reasonStr: 'bad user', endTime: 9999 });
|
||||
const raw = create(ResponseSchema, { responseCode: Response_ResponseCode.RespUserIsBanned });
|
||||
setExtension(raw, Response_Register_ext, create(Response_RegisterSchema, { deniedReasonStr: 'bad user', deniedEndTime: 9999n }));
|
||||
invokeResponseCode(Response_ResponseCode.RespUserIsBanned, raw);
|
||||
expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ vi.mock('./', async () => {
|
|||
return { ...(actual as any), ...makeSessionBarrelMock() };
|
||||
});
|
||||
|
||||
import { Mock } from 'vitest';
|
||||
import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers';
|
||||
import { BackendService } from '../../services/BackendService';
|
||||
import { SessionPersistence } from '../../persistence';
|
||||
import { RoomPersistence } from '../../persistence';
|
||||
import webClient from '../../WebClient';
|
||||
import * as SessionCommands from './';
|
||||
import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils';
|
||||
import {
|
||||
Command_AccountEdit_ext,
|
||||
|
|
@ -95,15 +95,15 @@ import { replayGetCode } from './replayGetCode';
|
|||
import { replaySubmitCode } from './replaySubmitCode';
|
||||
|
||||
const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers(
|
||||
BackendService.sendSessionCommand as vi.Mock,
|
||||
BackendService.sendSessionCommand as Mock,
|
||||
2
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(hashPassword as vi.Mock).mockReturnValue('hashed_pw');
|
||||
(generateSalt as vi.Mock).mockReturnValue('randSalt');
|
||||
(passwordSaltSupported as vi.Mock).mockReturnValue(0);
|
||||
(hashPassword as Mock).mockReturnValue('hashed_pw');
|
||||
(generateSalt as Mock).mockReturnValue('randSalt');
|
||||
(passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
|
@ -215,9 +215,9 @@ describe('deckList', () => {
|
|||
|
||||
it('calls updateServerDecks on success', () => {
|
||||
deckList();
|
||||
const resp = { folders: [] };
|
||||
invokeOnSuccess(resp, { responseCode: 0 });
|
||||
expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith(resp);
|
||||
const root = { items: [] };
|
||||
invokeOnSuccess({ root }, { responseCode: 0 });
|
||||
expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith({ root });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -380,9 +380,8 @@ describe('ping', () => {
|
|||
it('calls pingReceived via onResponse', () => {
|
||||
const pingReceived = vi.fn();
|
||||
ping(pingReceived);
|
||||
const raw = {};
|
||||
invokeCallback('onResponse', raw);
|
||||
expect(pingReceived).toHaveBeenCalledWith(raw);
|
||||
invokeCallback('onResponse', {});
|
||||
expect(pingReceived).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { ExtensionRegistry } from '../../services/ProtobufService';
|
||||
import { SessionExtensionRegistry } from '../../services/ProtobufService';
|
||||
|
||||
export const CommonEvents: ExtensionRegistry = [];
|
||||
export const CommonEvents: SessionExtensionRegistry = [];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ExtensionRegistry } from '../../services/ProtobufService';
|
||||
import { GameExtensionRegistry, makeGameEntry } from '../../services/ProtobufService';
|
||||
import { attachCard } from './attachCard';
|
||||
import { changeZoneProperties } from './changeZoneProperties';
|
||||
import { createArrow } from './createArrow';
|
||||
|
|
@ -59,35 +59,35 @@ import { Event_DumpZone_ext } from 'generated/proto/event_dump_zone_pb';
|
|||
import { Event_ChangeZoneProperties_ext } from 'generated/proto/event_change_zone_properties_pb';
|
||||
import { Event_ReverseTurn_ext } from 'generated/proto/event_reverse_turn_pb';
|
||||
|
||||
export const GameEvents: ExtensionRegistry = [
|
||||
[Event_Join_ext, joinGame],
|
||||
[Event_Leave_ext, leaveGame],
|
||||
[Event_GameClosed_ext, gameClosed],
|
||||
[Event_GameHostChanged_ext, gameHostChanged],
|
||||
[Event_Kicked_ext, kicked],
|
||||
[Event_GameStateChanged_ext, gameStateChanged],
|
||||
[Event_PlayerPropertiesChanged_ext, playerPropertiesChanged],
|
||||
[Event_GameSay_ext, gameSay],
|
||||
[Event_CreateArrow_ext, createArrow],
|
||||
[Event_DeleteArrow_ext, deleteArrow],
|
||||
[Event_CreateCounter_ext, createCounter],
|
||||
[Event_SetCounter_ext, setCounter],
|
||||
[Event_DelCounter_ext, delCounter],
|
||||
[Event_DrawCards_ext, drawCards],
|
||||
[Event_RevealCards_ext, revealCards],
|
||||
[Event_Shuffle_ext, shuffle],
|
||||
[Event_RollDie_ext, rollDie],
|
||||
[Event_MoveCard_ext, moveCard],
|
||||
[Event_FlipCard_ext, flipCard],
|
||||
[Event_DestroyCard_ext, destroyCard],
|
||||
[Event_AttachCard_ext, attachCard],
|
||||
[Event_CreateToken_ext, createToken],
|
||||
[Event_SetCardAttr_ext, setCardAttr],
|
||||
[Event_SetCardCounter_ext, setCardCounter],
|
||||
[Event_SetActivePlayer_ext, setActivePlayer],
|
||||
[Event_SetActivePhase_ext, setActivePhase],
|
||||
[Event_DumpZone_ext, dumpZone],
|
||||
[Event_ChangeZoneProperties_ext, changeZoneProperties],
|
||||
[Event_ReverseTurn_ext, reverseTurn],
|
||||
export const GameEvents: GameExtensionRegistry = [
|
||||
makeGameEntry(Event_Join_ext, joinGame),
|
||||
makeGameEntry(Event_Leave_ext, leaveGame),
|
||||
makeGameEntry(Event_GameClosed_ext, gameClosed),
|
||||
makeGameEntry(Event_GameHostChanged_ext, gameHostChanged),
|
||||
makeGameEntry(Event_Kicked_ext, kicked),
|
||||
makeGameEntry(Event_GameStateChanged_ext, gameStateChanged),
|
||||
makeGameEntry(Event_PlayerPropertiesChanged_ext, playerPropertiesChanged),
|
||||
makeGameEntry(Event_GameSay_ext, gameSay),
|
||||
makeGameEntry(Event_CreateArrow_ext, createArrow),
|
||||
makeGameEntry(Event_DeleteArrow_ext, deleteArrow),
|
||||
makeGameEntry(Event_CreateCounter_ext, createCounter),
|
||||
makeGameEntry(Event_SetCounter_ext, setCounter),
|
||||
makeGameEntry(Event_DelCounter_ext, delCounter),
|
||||
makeGameEntry(Event_DrawCards_ext, drawCards),
|
||||
makeGameEntry(Event_RevealCards_ext, revealCards),
|
||||
makeGameEntry(Event_Shuffle_ext, shuffle),
|
||||
makeGameEntry(Event_RollDie_ext, rollDie),
|
||||
makeGameEntry(Event_MoveCard_ext, moveCard),
|
||||
makeGameEntry(Event_FlipCard_ext, flipCard),
|
||||
makeGameEntry(Event_DestroyCard_ext, destroyCard),
|
||||
makeGameEntry(Event_AttachCard_ext, attachCard),
|
||||
makeGameEntry(Event_CreateToken_ext, createToken),
|
||||
makeGameEntry(Event_SetCardAttr_ext, setCardAttr),
|
||||
makeGameEntry(Event_SetCardCounter_ext, setCardCounter),
|
||||
makeGameEntry(Event_SetActivePlayer_ext, setActivePlayer),
|
||||
makeGameEntry(Event_SetActivePhase_ext, setActivePhase),
|
||||
makeGameEntry(Event_DumpZone_ext, dumpZone),
|
||||
makeGameEntry(Event_ChangeZoneProperties_ext, changeZoneProperties),
|
||||
makeGameEntry(Event_ReverseTurn_ext, reverseTurn),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ExtensionRegistry } from '../../services/ProtobufService';
|
||||
import { RoomExtensionRegistry, makeRoomEntry } from '../../services/ProtobufService';
|
||||
|
||||
import { joinRoom } from './joinRoom';
|
||||
import { leaveRoom } from './leaveRoom';
|
||||
|
|
@ -12,11 +12,11 @@ import { Event_ListGames_ext } from 'generated/proto/event_list_games_pb';
|
|||
import { Event_RemoveMessages_ext } from 'generated/proto/event_remove_messages_pb';
|
||||
import { Event_RoomSay_ext } from 'generated/proto/event_room_say_pb';
|
||||
|
||||
export const RoomEvents: ExtensionRegistry = [
|
||||
[Event_JoinRoom_ext, joinRoom],
|
||||
[Event_LeaveRoom_ext, leaveRoom],
|
||||
[Event_ListGames_ext, listGames],
|
||||
[Event_RemoveMessages_ext, removeMessages],
|
||||
[Event_RoomSay_ext, roomSay],
|
||||
export const RoomEvents: RoomExtensionRegistry = [
|
||||
makeRoomEntry(Event_JoinRoom_ext, joinRoom),
|
||||
makeRoomEntry(Event_LeaveRoom_ext, leaveRoom),
|
||||
makeRoomEntry(Event_ListGames_ext, listGames),
|
||||
makeRoomEntry(Event_RemoveMessages_ext, removeMessages),
|
||||
makeRoomEntry(Event_RoomSay_ext, roomSay),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ import type { Event_JoinRoom } from 'generated/proto/event_join_room_pb';
|
|||
import type { Event_LeaveRoom } from 'generated/proto/event_leave_room_pb';
|
||||
import type { Event_ListGames } from 'generated/proto/event_list_games_pb';
|
||||
import type { Event_RemoveMessages } from 'generated/proto/event_remove_messages_pb';
|
||||
import type { Event_RoomSay } from 'generated/proto/event_room_say_pb';
|
||||
import type { RoomEvent as GeneratedRoomEvent } from 'generated/proto/room_event_pb';
|
||||
|
||||
export type JoinRoomData = Event_JoinRoom;
|
||||
export type LeaveRoomData = Event_LeaveRoom;
|
||||
export type ListGamesData = Event_ListGames;
|
||||
export type RemoveMessagesData = Event_RemoveMessages;
|
||||
export type RoomSayData = Event_RoomSay;
|
||||
export type RoomEvent = GeneratedRoomEvent;
|
||||
|
|
|
|||
|
|
@ -8,30 +8,37 @@ vi.mock('../../persistence', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { RoomPersistence } from '../../persistence';
|
||||
import { joinRoom } from './joinRoom';
|
||||
import { leaveRoom } from './leaveRoom';
|
||||
import { listGames } from './listGames';
|
||||
import { removeMessages } from './removeMessages';
|
||||
import { roomSay } from './roomSay';
|
||||
import { Event_JoinRoomSchema } from 'generated/proto/event_join_room_pb';
|
||||
import { Event_LeaveRoomSchema } from 'generated/proto/event_leave_room_pb';
|
||||
import { Event_ListGamesSchema } from 'generated/proto/event_list_games_pb';
|
||||
import { Event_RemoveMessagesSchema } from 'generated/proto/event_remove_messages_pb';
|
||||
import { Event_RoomSaySchema } from 'generated/proto/event_room_say_pb';
|
||||
import { RoomEventSchema } from 'generated/proto/room_event_pb';
|
||||
|
||||
const makeRoomEvent = (roomId: number) => ({ roomId }) as any;
|
||||
const makeRoomEvent = (roomId: number) => create(RoomEventSchema, { roomId });
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('joinRoom room event', () => {
|
||||
|
||||
it('calls RoomPersistence.userJoined with roomId and userInfo', () => {
|
||||
const userInfo = { name: 'alice' } as any;
|
||||
joinRoom({ userInfo }, makeRoomEvent(3));
|
||||
expect(RoomPersistence.userJoined).toHaveBeenCalledWith(3, userInfo);
|
||||
const data = create(Event_JoinRoomSchema, { userInfo: { name: 'alice' } });
|
||||
joinRoom(data, makeRoomEvent(3));
|
||||
expect(RoomPersistence.userJoined).toHaveBeenCalledWith(3, data.userInfo);
|
||||
});
|
||||
});
|
||||
|
||||
describe('leaveRoom room event', () => {
|
||||
|
||||
it('calls RoomPersistence.userLeft with roomId and name', () => {
|
||||
leaveRoom({ name: 'alice' }, makeRoomEvent(4));
|
||||
leaveRoom(create(Event_LeaveRoomSchema, { name: 'alice' }), makeRoomEvent(4));
|
||||
expect(RoomPersistence.userLeft).toHaveBeenCalledWith(4, 'alice');
|
||||
});
|
||||
});
|
||||
|
|
@ -39,25 +46,29 @@ describe('leaveRoom room event', () => {
|
|||
describe('listGames room event', () => {
|
||||
|
||||
it('calls RoomPersistence.updateGames with roomId and gameList', () => {
|
||||
const gameList = [{ gameId: 1 }] as any;
|
||||
listGames({ gameList }, makeRoomEvent(5));
|
||||
expect(RoomPersistence.updateGames).toHaveBeenCalledWith(5, gameList);
|
||||
const data = create(Event_ListGamesSchema, { gameList: [{ gameId: 1 }] });
|
||||
listGames(data, makeRoomEvent(5));
|
||||
expect(RoomPersistence.updateGames).toHaveBeenCalledWith(5, data.gameList);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeMessages room event', () => {
|
||||
|
||||
it('calls RoomPersistence.removeMessages with roomId, name, amount', () => {
|
||||
removeMessages({ name: 'bob', amount: 10 }, makeRoomEvent(6));
|
||||
removeMessages(create(Event_RemoveMessagesSchema, { name: 'bob', amount: 10 }), makeRoomEvent(6));
|
||||
expect(RoomPersistence.removeMessages).toHaveBeenCalledWith(6, 'bob', 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('roomSay room event', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers(); vi.setSystemTime(0);
|
||||
});
|
||||
afterEach(() => vi.useRealTimers());
|
||||
|
||||
it('calls RoomPersistence.addMessage with roomId and message', () => {
|
||||
const msg = { text: 'hello' } as any;
|
||||
roomSay(msg, makeRoomEvent(7));
|
||||
expect(RoomPersistence.addMessage).toHaveBeenCalledWith(7, msg);
|
||||
const data = create(Event_RoomSaySchema, { message: 'hello' });
|
||||
roomSay(data, makeRoomEvent(7));
|
||||
expect(RoomPersistence.addMessage).toHaveBeenCalledWith(7, { ...data, timeReceived: 0 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Message } from 'types';
|
||||
|
||||
import { RoomPersistence } from '../../persistence';
|
||||
import { RoomEvent } from './interfaces';
|
||||
import { RoomSayData, RoomEvent } from './interfaces';
|
||||
|
||||
export function roomSay(message: Message, { roomId }: RoomEvent): void {
|
||||
export function roomSay(data: RoomSayData, { roomId }: RoomEvent): void {
|
||||
const message: Message = { ...data, timeReceived: Date.now() };
|
||||
RoomPersistence.addMessage(roomId, message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ExtensionRegistry } from '../../services/ProtobufService';
|
||||
import { SessionExtensionRegistry, makeSessionEntry } from '../../services/ProtobufService';
|
||||
import { addToList } from './addToList';
|
||||
import { connectionClosed } from './connectionClosed';
|
||||
import { listRooms } from './listRooms';
|
||||
|
|
@ -29,20 +29,20 @@ import { Event_UserJoined_ext } from 'generated/proto/event_user_joined_pb';
|
|||
import { Event_UserLeft_ext } from 'generated/proto/event_user_left_pb';
|
||||
import { Event_UserMessage_ext } from 'generated/proto/event_user_message_pb';
|
||||
|
||||
export const SessionEvents: ExtensionRegistry = [
|
||||
[Event_AddToList_ext, addToList],
|
||||
[Event_ConnectionClosed_ext, connectionClosed],
|
||||
[Event_GameJoined_ext, gameJoined],
|
||||
[Event_ListRooms_ext, listRooms],
|
||||
[Event_NotifyUser_ext, notifyUser],
|
||||
[Event_RemoveFromList_ext, removeFromList],
|
||||
[Event_ReplayAdded_ext, replayAdded],
|
||||
[Event_ServerCompleteList_ext, serverCompleteList],
|
||||
[Event_ServerIdentification_ext, serverIdentification],
|
||||
[Event_ServerMessage_ext, serverMessage],
|
||||
[Event_ServerShutdown_ext, serverShutdown],
|
||||
[Event_UserJoined_ext, userJoined],
|
||||
[Event_UserLeft_ext, userLeft],
|
||||
[Event_UserMessage_ext, userMessage],
|
||||
export const SessionEvents: SessionExtensionRegistry = [
|
||||
makeSessionEntry(Event_AddToList_ext, addToList),
|
||||
makeSessionEntry(Event_ConnectionClosed_ext, connectionClosed),
|
||||
makeSessionEntry(Event_GameJoined_ext, gameJoined),
|
||||
makeSessionEntry(Event_ListRooms_ext, listRooms),
|
||||
makeSessionEntry(Event_NotifyUser_ext, notifyUser),
|
||||
makeSessionEntry(Event_RemoveFromList_ext, removeFromList),
|
||||
makeSessionEntry(Event_ReplayAdded_ext, replayAdded),
|
||||
makeSessionEntry(Event_ServerCompleteList_ext, serverCompleteList),
|
||||
makeSessionEntry(Event_ServerIdentification_ext, serverIdentification),
|
||||
makeSessionEntry(Event_ServerMessage_ext, serverMessage),
|
||||
makeSessionEntry(Event_ServerShutdown_ext, serverShutdown),
|
||||
makeSessionEntry(Event_UserJoined_ext, userJoined),
|
||||
makeSessionEntry(Event_UserLeft_ext, userLeft),
|
||||
makeSessionEntry(Event_UserMessage_ext, userMessage),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,21 @@ vi.mock('../../utils', () => ({
|
|||
}));
|
||||
|
||||
import { WebSocketConnectReason } from 'types';
|
||||
import { Event_ConnectionClosed_CloseReason } from 'generated/proto/event_connection_closed_pb';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { Event_ConnectionClosed_CloseReason, Event_ConnectionClosedSchema } from 'generated/proto/event_connection_closed_pb';
|
||||
import { Event_GameJoinedSchema } from 'generated/proto/event_game_joined_pb';
|
||||
import { Event_NotifyUserSchema } from 'generated/proto/event_notify_user_pb';
|
||||
import { Event_ReplayAddedSchema } from 'generated/proto/event_replay_added_pb';
|
||||
import { Event_ServerCompleteListSchema } from 'generated/proto/event_server_complete_list_pb';
|
||||
import { Event_ServerMessageSchema } from 'generated/proto/event_server_message_pb';
|
||||
import { Event_ServerShutdownSchema } from 'generated/proto/event_server_shutdown_pb';
|
||||
import { Event_UserJoinedSchema } from 'generated/proto/event_user_joined_pb';
|
||||
import { Event_UserLeftSchema } from 'generated/proto/event_user_left_pb';
|
||||
import { Event_UserMessageSchema } from 'generated/proto/event_user_message_pb';
|
||||
import { Event_AddToListSchema } from 'generated/proto/event_add_to_list_pb';
|
||||
import { Event_RemoveFromListSchema } from 'generated/proto/event_remove_from_list_pb';
|
||||
import { Event_ListRoomsSchema } from 'generated/proto/event_list_rooms_pb';
|
||||
import { Event_ServerIdentificationSchema } from 'generated/proto/event_server_identification_pb';
|
||||
|
||||
import { SessionPersistence, RoomPersistence } from '../../persistence';
|
||||
import webClient from '../../WebClient';
|
||||
|
|
@ -72,11 +86,12 @@ import { removeFromList } from './removeFromList';
|
|||
import { listRooms } from './listRooms';
|
||||
import { connectionClosed } from './connectionClosed';
|
||||
import { serverIdentification } from './serverIdentification';
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(Utils.generateSalt as vi.Mock).mockReturnValue('newSalt');
|
||||
(Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0);
|
||||
(Utils.generateSalt as Mock).mockReturnValue('newSalt');
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
|
@ -85,7 +100,7 @@ beforeEach(() => {
|
|||
describe('gameJoined', () => {
|
||||
|
||||
it('calls SessionPersistence.gameJoined', () => {
|
||||
const data = { gameId: 1 } as any;
|
||||
const data = create(Event_GameJoinedSchema, { playerId: 1 });
|
||||
gameJoined(data);
|
||||
expect(SessionPersistence.gameJoined).toHaveBeenCalledWith(data);
|
||||
});
|
||||
|
|
@ -97,7 +112,7 @@ describe('gameJoined', () => {
|
|||
describe('notifyUser', () => {
|
||||
|
||||
it('calls SessionPersistence.notifyUser', () => {
|
||||
const data = { message: 'yo' } as any;
|
||||
const data = create(Event_NotifyUserSchema, { warningReason: 'yo' });
|
||||
notifyUser(data);
|
||||
expect(SessionPersistence.notifyUser).toHaveBeenCalledWith(data);
|
||||
});
|
||||
|
|
@ -109,8 +124,10 @@ describe('notifyUser', () => {
|
|||
describe('replayAdded', () => {
|
||||
|
||||
it('calls SessionPersistence.replayAdded with matchInfo', () => {
|
||||
replayAdded({ matchInfo: { id: 42 } } as any);
|
||||
expect(SessionPersistence.replayAdded).toHaveBeenCalledWith({ id: 42 });
|
||||
const data = create(Event_ReplayAddedSchema);
|
||||
data.matchInfo = { gameId: 42 } as any;
|
||||
replayAdded(data);
|
||||
expect(SessionPersistence.replayAdded).toHaveBeenCalledWith(data.matchInfo);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -120,9 +137,10 @@ describe('replayAdded', () => {
|
|||
describe('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']);
|
||||
const data = create(Event_ServerCompleteListSchema, { userList: [], roomList: [] });
|
||||
serverCompleteList(data);
|
||||
expect(SessionPersistence.updateUsers).toHaveBeenCalledWith(data.userList);
|
||||
expect(RoomPersistence.updateRooms).toHaveBeenCalledWith(data.roomList);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -132,7 +150,7 @@ describe('serverCompleteList', () => {
|
|||
describe('serverMessage', () => {
|
||||
|
||||
it('calls SessionPersistence.serverMessage with message', () => {
|
||||
serverMessage({ message: 'hello server' });
|
||||
serverMessage(create(Event_ServerMessageSchema, { message: 'hello server' }));
|
||||
expect(SessionPersistence.serverMessage).toHaveBeenCalledWith('hello server');
|
||||
});
|
||||
});
|
||||
|
|
@ -143,7 +161,7 @@ describe('serverMessage', () => {
|
|||
describe('serverShutdown', () => {
|
||||
|
||||
it('calls SessionPersistence.serverShutdown', () => {
|
||||
const payload = { reason: 'maintenance' } as any;
|
||||
const payload = create(Event_ServerShutdownSchema, { reason: 'maintenance' });
|
||||
serverShutdown(payload);
|
||||
expect(SessionPersistence.serverShutdown).toHaveBeenCalledWith(payload);
|
||||
});
|
||||
|
|
@ -155,8 +173,10 @@ describe('serverShutdown', () => {
|
|||
describe('userJoined', () => {
|
||||
|
||||
it('calls SessionPersistence.userJoined with userInfo', () => {
|
||||
userJoined({ userInfo: { name: 'alice' } } as any);
|
||||
expect(SessionPersistence.userJoined).toHaveBeenCalledWith({ name: 'alice' });
|
||||
const data = create(Event_UserJoinedSchema);
|
||||
data.userInfo = { name: 'alice' } as any;
|
||||
userJoined(data);
|
||||
expect(SessionPersistence.userJoined).toHaveBeenCalledWith(data.userInfo);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -166,7 +186,7 @@ describe('userJoined', () => {
|
|||
describe('userLeft', () => {
|
||||
|
||||
it('calls SessionPersistence.userLeft with name', () => {
|
||||
userLeft({ name: 'bob' });
|
||||
userLeft(create(Event_UserLeftSchema, { name: 'bob' }));
|
||||
expect(SessionPersistence.userLeft).toHaveBeenCalledWith('bob');
|
||||
});
|
||||
});
|
||||
|
|
@ -177,7 +197,7 @@ describe('userLeft', () => {
|
|||
describe('userMessage', () => {
|
||||
|
||||
it('calls SessionPersistence.userMessage', () => {
|
||||
const payload = { userName: 'alice', message: 'hi' } as any;
|
||||
const payload = create(Event_UserMessageSchema, { senderName: 'alice', message: 'hi' });
|
||||
userMessage(payload);
|
||||
expect(SessionPersistence.userMessage).toHaveBeenCalledWith(payload);
|
||||
});
|
||||
|
|
@ -187,21 +207,27 @@ describe('userMessage', () => {
|
|||
// addToList
|
||||
// ----------------------------------------------------------------
|
||||
describe('addToList', () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
afterAll(() => logSpy.mockRestore());
|
||||
let logSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => {
|
||||
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('buddy list → addToBuddyList', () => {
|
||||
addToList({ listName: 'buddy', userInfo: { name: 'alice' } } as any);
|
||||
expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith({ name: 'alice' });
|
||||
const data = create(Event_AddToListSchema, { listName: 'buddy' });
|
||||
data.userInfo = { name: 'alice' } as any;
|
||||
addToList(data);
|
||||
expect(SessionPersistence.addToBuddyList).toHaveBeenCalledWith(data.userInfo);
|
||||
});
|
||||
|
||||
it('ignore list → addToIgnoreList', () => {
|
||||
addToList({ listName: 'ignore', userInfo: { name: 'bob' } } as any);
|
||||
expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith({ name: 'bob' });
|
||||
const data = create(Event_AddToListSchema, { listName: 'ignore' });
|
||||
data.userInfo = { name: 'bob' } as any;
|
||||
addToList(data);
|
||||
expect(SessionPersistence.addToIgnoreList).toHaveBeenCalledWith(data.userInfo);
|
||||
});
|
||||
|
||||
it('unknown list → console.log', () => {
|
||||
addToList({ listName: 'unknown', userInfo: {} } as any);
|
||||
addToList(create(Event_AddToListSchema, { listName: 'unknown' }));
|
||||
expect(logSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -212,18 +238,18 @@ describe('addToList', () => {
|
|||
describe('removeFromList', () => {
|
||||
|
||||
it('buddy list → removeFromBuddyList', () => {
|
||||
removeFromList({ listName: 'buddy', userName: 'alice' } as any);
|
||||
removeFromList(create(Event_RemoveFromListSchema, { listName: 'buddy', userName: 'alice' }));
|
||||
expect(SessionPersistence.removeFromBuddyList).toHaveBeenCalledWith('alice');
|
||||
});
|
||||
|
||||
it('ignore list → removeFromIgnoreList', () => {
|
||||
removeFromList({ listName: 'ignore', userName: 'bob' } as any);
|
||||
removeFromList(create(Event_RemoveFromListSchema, { listName: 'ignore', userName: 'bob' }));
|
||||
expect(SessionPersistence.removeFromIgnoreList).toHaveBeenCalledWith('bob');
|
||||
});
|
||||
|
||||
it('unknown list → console.log', () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
removeFromList({ listName: 'other', userName: 'x' } as any);
|
||||
removeFromList(create(Event_RemoveFromListSchema, { listName: 'other', userName: 'x' }));
|
||||
expect(logSpy).toHaveBeenCalled();
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
|
@ -235,19 +261,19 @@ describe('removeFromList', () => {
|
|||
describe('listRooms', () => {
|
||||
|
||||
it('calls RoomPersistence.updateRooms', () => {
|
||||
listRooms({ roomList: [] });
|
||||
listRooms(create(Event_ListRoomsSchema, { 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);
|
||||
listRooms(create(Event_ListRoomsSchema, { 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);
|
||||
listRooms(create(Event_ListRoomsSchema, { roomList: [{ autoJoin: true, roomId: 2 }, { autoJoin: false, roomId: 3 }] as any[] }));
|
||||
expect(SessionCmds.joinRoom).toHaveBeenCalledTimes(1);
|
||||
expect(SessionCmds.joinRoom).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
|
@ -259,12 +285,12 @@ describe('listRooms', () => {
|
|||
describe('connectionClosed', () => {
|
||||
|
||||
it('uses reasonStr when provided', () => {
|
||||
connectionClosed({ reason: 0, reasonStr: 'custom' } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: 0, reasonStr: 'custom' }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom');
|
||||
});
|
||||
|
||||
it('USER_LIMIT_REACHED → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.USER_LIMIT_REACHED }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.stringContaining('maximum user capacity')
|
||||
|
|
@ -272,42 +298,42 @@ describe('connectionClosed', () => {
|
|||
});
|
||||
|
||||
it('TOO_MANY_CONNECTIONS → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.TOO_MANY_CONNECTIONS }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('too many concurrent'));
|
||||
});
|
||||
|
||||
it('BANNED → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
||||
});
|
||||
|
||||
it('DEMOTED → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.DEMOTED } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.DEMOTED }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('demoted'));
|
||||
});
|
||||
|
||||
it('SERVER_SHUTDOWN → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.SERVER_SHUTDOWN }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('shutdown'));
|
||||
});
|
||||
|
||||
it('USERNAMEINVALID → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.USERNAMEINVALID } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.USERNAMEINVALID }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('username'));
|
||||
});
|
||||
|
||||
it('LOGGEDINELSEWERE → specific message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.LOGGEDINELSEWERE }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), expect.stringContaining('logged out'));
|
||||
});
|
||||
|
||||
it('OTHER → "Unknown reason"', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.OTHER } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.OTHER }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'Unknown reason');
|
||||
});
|
||||
|
||||
it('BANNED with valid positive endTime → shows formatted date', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000 } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 1700000000 }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.stringContaining('You are banned until')
|
||||
|
|
@ -315,27 +341,28 @@ describe('connectionClosed', () => {
|
|||
});
|
||||
|
||||
it('BANNED with endTime = 0 → shows generic banned message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0 }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
||||
});
|
||||
|
||||
it('BANNED with endTime = -1 → shows generic banned message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: -1 }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
||||
});
|
||||
|
||||
it('BANNED with endTime = NaN → shows generic banned message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: NaN }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
||||
});
|
||||
|
||||
it('BANNED with endTime = Infinity → shows generic banned message', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema, { reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: Infinity }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'You are banned');
|
||||
});
|
||||
|
||||
it('BANNED with reasonStr → uses reasonStr regardless of endTime', () => {
|
||||
connectionClosed({ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' } as any);
|
||||
connectionClosed(create(Event_ConnectionClosedSchema,
|
||||
{ reason: Event_ConnectionClosed_CloseReason.BANNED, endTime: 0, reasonStr: 'custom ban reason' }));
|
||||
expect(SessionCmds.updateStatus).toHaveBeenCalledWith(expect.anything(), 'custom ban reason');
|
||||
});
|
||||
});
|
||||
|
|
@ -351,15 +378,17 @@ describe('serverIdentification', () => {
|
|||
});
|
||||
|
||||
it('disconnects when protocolVersion mismatches', () => {
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 } as any);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 99, serverOptions: 0 }));
|
||||
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 vi.Mock).mockReturnValue(0);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
expect(SessionCmds.login).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ password: expect.anything() }),
|
||||
'secret'
|
||||
|
|
@ -368,8 +397,9 @@ describe('serverIdentification', () => {
|
|||
|
||||
it('LOGIN reason with salt → calls requestPasswordSalt with password as separate param', () => {
|
||||
(webClient as any).options = { reason: WebSocketConnectReason.LOGIN, password: 'secret' };
|
||||
(Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(1);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 }));
|
||||
expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ password: expect.anything() }),
|
||||
'secret'
|
||||
|
|
@ -378,8 +408,9 @@ describe('serverIdentification', () => {
|
|||
|
||||
it('REGISTER reason without salt → calls register with password and null salt', () => {
|
||||
(webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' };
|
||||
(Utils.passwordSaltSupported as vi.Mock).mockReturnValue(0);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
expect(SessionCmds.register).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ password: expect.anything() }),
|
||||
'secret',
|
||||
|
|
@ -389,8 +420,9 @@ describe('serverIdentification', () => {
|
|||
|
||||
it('REGISTER reason with salt → calls register with password and generated salt', () => {
|
||||
(webClient as any).options = { reason: WebSocketConnectReason.REGISTER, password: 'secret' };
|
||||
(Utils.passwordSaltSupported as vi.Mock).mockReturnValue(1);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(1);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 }));
|
||||
expect(SessionCmds.register).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ password: expect.anything() }),
|
||||
'secret',
|
||||
|
|
@ -400,8 +432,9 @@ describe('serverIdentification', () => {
|
|||
|
||||
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 vi.Mock).mockReturnValue(0);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
expect(SessionCmds.activate).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ password: expect.anything() }),
|
||||
'secret'
|
||||
|
|
@ -410,8 +443,9 @@ describe('serverIdentification', () => {
|
|||
|
||||
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 vi.Mock).mockReturnValue(1);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(1);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 }));
|
||||
expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ password: expect.anything() }),
|
||||
'secret'
|
||||
|
|
@ -420,20 +454,23 @@ describe('serverIdentification', () => {
|
|||
|
||||
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);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
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);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
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 vi.Mock).mockReturnValue(0);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(0);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
expect(SessionCmds.forgotPasswordReset).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ newPassword: expect.anything() }),
|
||||
'newpw'
|
||||
|
|
@ -442,8 +479,9 @@ describe('serverIdentification', () => {
|
|||
|
||||
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 vi.Mock).mockReturnValue(1);
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 } as any);
|
||||
(Utils.passwordSaltSupported as Mock).mockReturnValue(1);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 1 }));
|
||||
expect(SessionCmds.requestPasswordSalt).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ newPassword: expect.anything() }),
|
||||
undefined,
|
||||
|
|
@ -453,14 +491,16 @@ describe('serverIdentification', () => {
|
|||
|
||||
it('unknown reason → updateStatus DISCONNECTED and disconnect', () => {
|
||||
(webClient as any).options = { reason: 999 };
|
||||
serverIdentification({ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 } as any);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 's', serverVersion: '1', protocolVersion: 14, serverOptions: 0 }));
|
||||
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);
|
||||
serverIdentification(create(Event_ServerIdentificationSchema,
|
||||
{ serverName: 'myServer', serverVersion: '2.0', protocolVersion: 14, serverOptions: 0 }));
|
||||
expect(SessionPersistence.updateInfo).toHaveBeenCalledWith('myServer', '2.0');
|
||||
expect((webClient as any).options).toEqual({});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { create } from '@bufbuild/protobuf';
|
||||
import { GamePersistence } from './GamePersistence';
|
||||
|
||||
vi.mock('store', () => ({
|
||||
|
|
@ -34,19 +35,40 @@ vi.mock('store', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
import { Event_GameStateChangedSchema } from 'generated/proto/event_game_state_changed_pb';
|
||||
import { Event_MoveCardSchema } from 'generated/proto/event_move_card_pb';
|
||||
import { Event_FlipCardSchema } from 'generated/proto/event_flip_card_pb';
|
||||
import { Event_DestroyCardSchema } from 'generated/proto/event_destroy_card_pb';
|
||||
import { Event_AttachCardSchema } from 'generated/proto/event_attach_card_pb';
|
||||
import { Event_CreateTokenSchema } from 'generated/proto/event_create_token_pb';
|
||||
import { Event_SetCardAttrSchema } from 'generated/proto/event_set_card_attr_pb';
|
||||
import { Event_SetCardCounterSchema } from 'generated/proto/event_set_card_counter_pb';
|
||||
import { Event_CreateArrowSchema } from 'generated/proto/event_create_arrow_pb';
|
||||
import { Event_DeleteArrowSchema } from 'generated/proto/event_delete_arrow_pb';
|
||||
import { Event_CreateCounterSchema } from 'generated/proto/event_create_counter_pb';
|
||||
import { Event_SetCounterSchema } from 'generated/proto/event_set_counter_pb';
|
||||
import { Event_DelCounterSchema } from 'generated/proto/event_del_counter_pb';
|
||||
import { Event_DrawCardsSchema } from 'generated/proto/event_draw_cards_pb';
|
||||
import { Event_RevealCardsSchema } from 'generated/proto/event_reveal_cards_pb';
|
||||
import { Event_ShuffleSchema } from 'generated/proto/event_shuffle_pb';
|
||||
import { Event_RollDieSchema } from 'generated/proto/event_roll_die_pb';
|
||||
import { Event_DumpZoneSchema } from 'generated/proto/event_dump_zone_pb';
|
||||
import { Event_ChangeZonePropertiesSchema } from 'generated/proto/event_change_zone_properties_pb';
|
||||
import { ServerInfo_PlayerPropertiesSchema } from 'generated/proto/serverinfo_playerproperties_pb';
|
||||
|
||||
import { GameDispatch } from 'store';
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('GamePersistence', () => {
|
||||
it('gameStateChanged dispatches via GameDispatch', () => {
|
||||
const data = { playerList: [] } as any;
|
||||
const data = create(Event_GameStateChangedSchema, { playerList: [] });
|
||||
GamePersistence.gameStateChanged(5, data);
|
||||
expect(GameDispatch.gameStateChanged).toHaveBeenCalledWith(5, data);
|
||||
});
|
||||
|
||||
it('playerJoined dispatches via GameDispatch', () => {
|
||||
const data = { playerId: 1 } as any;
|
||||
const data = create(ServerInfo_PlayerPropertiesSchema, { playerId: 1 });
|
||||
GamePersistence.playerJoined(5, data);
|
||||
expect(GameDispatch.playerJoined).toHaveBeenCalledWith(5, data);
|
||||
});
|
||||
|
|
@ -57,7 +79,7 @@ describe('GamePersistence', () => {
|
|||
});
|
||||
|
||||
it('playerPropertiesChanged dispatches via GameDispatch', () => {
|
||||
const props = { playerId: 2 } as any;
|
||||
const props = create(ServerInfo_PlayerPropertiesSchema, { playerId: 2 });
|
||||
GamePersistence.playerPropertiesChanged(5, 2, props);
|
||||
expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 2, props);
|
||||
});
|
||||
|
|
@ -83,97 +105,97 @@ describe('GamePersistence', () => {
|
|||
});
|
||||
|
||||
it('cardMoved dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_MoveCardSchema, { cardId: 3 });
|
||||
GamePersistence.cardMoved(5, 1, data);
|
||||
expect(GameDispatch.cardMoved).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardFlipped dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_FlipCardSchema, { cardId: 3 });
|
||||
GamePersistence.cardFlipped(5, 1, data);
|
||||
expect(GameDispatch.cardFlipped).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardDestroyed dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_DestroyCardSchema, { cardId: 3 });
|
||||
GamePersistence.cardDestroyed(5, 1, data);
|
||||
expect(GameDispatch.cardDestroyed).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardAttached dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_AttachCardSchema, { cardId: 3 });
|
||||
GamePersistence.cardAttached(5, 1, data);
|
||||
expect(GameDispatch.cardAttached).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('tokenCreated dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_CreateTokenSchema, { cardId: 3 });
|
||||
GamePersistence.tokenCreated(5, 1, data);
|
||||
expect(GameDispatch.tokenCreated).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardAttrChanged dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_SetCardAttrSchema, { cardId: 3 });
|
||||
GamePersistence.cardAttrChanged(5, 1, data);
|
||||
expect(GameDispatch.cardAttrChanged).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardCounterChanged dispatches via GameDispatch', () => {
|
||||
const data = { cardId: 3 } as any;
|
||||
const data = create(Event_SetCardCounterSchema, { cardId: 3 });
|
||||
GamePersistence.cardCounterChanged(5, 1, data);
|
||||
expect(GameDispatch.cardCounterChanged).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('arrowCreated dispatches via GameDispatch', () => {
|
||||
const data = { arrowInfo: {} } as any;
|
||||
const data = create(Event_CreateArrowSchema, {});
|
||||
GamePersistence.arrowCreated(5, 1, data);
|
||||
expect(GameDispatch.arrowCreated).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('arrowDeleted dispatches via GameDispatch', () => {
|
||||
const data = { arrowId: 9 };
|
||||
const data = create(Event_DeleteArrowSchema, { arrowId: 9 });
|
||||
GamePersistence.arrowDeleted(5, 1, data);
|
||||
expect(GameDispatch.arrowDeleted).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('counterCreated dispatches via GameDispatch', () => {
|
||||
const data = { counterInfo: {} } as any;
|
||||
const data = create(Event_CreateCounterSchema, {});
|
||||
GamePersistence.counterCreated(5, 1, data);
|
||||
expect(GameDispatch.counterCreated).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('counterSet dispatches via GameDispatch', () => {
|
||||
const data = { counterId: 1, value: 20 };
|
||||
const data = create(Event_SetCounterSchema, { counterId: 1, value: 20 });
|
||||
GamePersistence.counterSet(5, 1, data);
|
||||
expect(GameDispatch.counterSet).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('counterDeleted dispatches via GameDispatch', () => {
|
||||
const data = { counterId: 1 };
|
||||
const data = create(Event_DelCounterSchema, { counterId: 1 });
|
||||
GamePersistence.counterDeleted(5, 1, data);
|
||||
expect(GameDispatch.counterDeleted).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardsDrawn dispatches via GameDispatch', () => {
|
||||
const data = { number: 2, cards: [] } as any;
|
||||
const data = create(Event_DrawCardsSchema, { number: 2, cards: [] });
|
||||
GamePersistence.cardsDrawn(5, 1, data);
|
||||
expect(GameDispatch.cardsDrawn).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('cardsRevealed dispatches via GameDispatch', () => {
|
||||
const data = { zoneName: 'hand', cards: [] } as any;
|
||||
const data = create(Event_RevealCardsSchema, { zoneName: 'hand', cards: [] });
|
||||
GamePersistence.cardsRevealed(5, 1, data);
|
||||
expect(GameDispatch.cardsRevealed).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('zoneShuffled dispatches via GameDispatch', () => {
|
||||
const data = { zoneName: 'deck' } as any;
|
||||
const data = create(Event_ShuffleSchema, { zoneName: 'deck' });
|
||||
GamePersistence.zoneShuffled(5, 1, data);
|
||||
expect(GameDispatch.zoneShuffled).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('dieRolled dispatches via GameDispatch', () => {
|
||||
const data = { die: 6, result: 4 } as any;
|
||||
const data = create(Event_RollDieSchema, { sides: 6, value: 4 });
|
||||
GamePersistence.dieRolled(5, 1, data);
|
||||
expect(GameDispatch.dieRolled).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
|
@ -194,13 +216,13 @@ describe('GamePersistence', () => {
|
|||
});
|
||||
|
||||
it('zoneDumped dispatches via GameDispatch', () => {
|
||||
const data = { zoneName: 'hand' } as any;
|
||||
const data = create(Event_DumpZoneSchema, { zoneName: 'hand' });
|
||||
GamePersistence.zoneDumped(5, 1, data);
|
||||
expect(GameDispatch.zoneDumped).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
||||
it('zonePropertiesChanged dispatches via GameDispatch', () => {
|
||||
const data = { zoneName: 'hand', alwaysRevealTopCard: true } as any;
|
||||
const data = create(Event_ChangeZonePropertiesSchema, { zoneName: 'hand', alwaysRevealTopCard: true });
|
||||
GamePersistence.zonePropertiesChanged(5, 1, data);
|
||||
expect(GameDispatch.zonePropertiesChanged).toHaveBeenCalledWith(5, 1, data);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,20 +13,11 @@ vi.mock('store', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../utils/NormalizeService', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
normalizeLogs: vi.fn((logs: any) => ({ normalized: logs })),
|
||||
},
|
||||
}));
|
||||
|
||||
import { ModeratorPersistence } from './ModeratorPersistence';
|
||||
import { ServerDispatch } from 'store';
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(NormalizeService.normalizeLogs as vi.Mock).mockImplementation((logs: any) => ({ normalized: logs }));
|
||||
});
|
||||
|
||||
describe('ModeratorPersistence', () => {
|
||||
|
|
@ -40,11 +31,10 @@ describe('ModeratorPersistence', () => {
|
|||
expect(ServerDispatch.banHistory).toHaveBeenCalledWith('alice', []);
|
||||
});
|
||||
|
||||
it('viewLogs normalizes logs and dispatches', () => {
|
||||
it('viewLogs dispatches raw logs', () => {
|
||||
const logs = [{ targetType: 'room' }] as any;
|
||||
ModeratorPersistence.viewLogs(logs);
|
||||
expect(NormalizeService.normalizeLogs).toHaveBeenCalledWith(logs);
|
||||
expect(ServerDispatch.viewLogs).toHaveBeenCalledWith({ normalized: logs });
|
||||
expect(ServerDispatch.viewLogs).toHaveBeenCalledWith(logs);
|
||||
});
|
||||
|
||||
it('warnHistory passes userName and warnHistory', () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { ServerDispatch } from 'store';
|
||||
import { BanHistoryItem, LogItem, WarnHistoryItem, WarnListItem } from 'types';
|
||||
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
|
||||
export class ModeratorPersistence {
|
||||
static banFromServer(userName: string): void {
|
||||
ServerDispatch.banFromServer(userName);
|
||||
|
|
@ -13,7 +11,7 @@ export class ModeratorPersistence {
|
|||
}
|
||||
|
||||
static viewLogs(logs: LogItem[]): void {
|
||||
ServerDispatch.viewLogs(NormalizeService.normalizeLogs(logs));
|
||||
ServerDispatch.viewLogs(logs);
|
||||
}
|
||||
|
||||
static warnHistory(userName: string, warnHistory: WarnHistoryItem[]): void {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
vi.mock('store', () => ({
|
||||
store: { getState: vi.fn().mockReturnValue({}) },
|
||||
RoomsDispatch: {
|
||||
clearStore: vi.fn(),
|
||||
joinRoom: vi.fn(),
|
||||
|
|
@ -13,23 +12,10 @@ vi.mock('store', () => ({
|
|||
gameCreated: vi.fn(),
|
||||
joinedGame: vi.fn(),
|
||||
},
|
||||
RoomsSelectors: {
|
||||
getRoom: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../utils/NormalizeService', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
normalizeRoomInfo: vi.fn(),
|
||||
normalizeGameObject: vi.fn(),
|
||||
normalizeUserMessage: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { RoomPersistence } from './RoomPersistence';
|
||||
import { store, RoomsDispatch, RoomsSelectors } from 'store';
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
import { RoomsDispatch } from 'store';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
@ -41,10 +27,9 @@ describe('RoomPersistence', () => {
|
|||
expect(RoomsDispatch.clearStore).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('joinRoom normalizes and dispatches', () => {
|
||||
it('joinRoom dispatches raw roomInfo', () => {
|
||||
const room = { roomId: 1 } as any;
|
||||
RoomPersistence.joinRoom(room);
|
||||
expect(NormalizeService.normalizeRoomInfo).toHaveBeenCalledWith(room);
|
||||
expect(RoomsDispatch.joinRoom).toHaveBeenCalledWith(room);
|
||||
});
|
||||
|
||||
|
|
@ -53,34 +38,19 @@ describe('RoomPersistence', () => {
|
|||
expect(RoomsDispatch.leaveRoom).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('updateRooms -> RoomsDispatch.updateRooms', () => {
|
||||
RoomPersistence.updateRooms([]);
|
||||
expect(RoomsDispatch.updateRooms).toHaveBeenCalledWith([]);
|
||||
it('updateRooms dispatches raw rooms', () => {
|
||||
const rooms = [{ roomId: 1 }] as any;
|
||||
RoomPersistence.updateRooms(rooms);
|
||||
expect(RoomsDispatch.updateRooms).toHaveBeenCalledWith(rooms);
|
||||
});
|
||||
|
||||
describe('updateGames', () => {
|
||||
it('normalizes game when gameType is missing and room exists', () => {
|
||||
const game = { gameType: null, gameTypes: [1] } as any;
|
||||
const room = { gametypeMap: { 1: 'Standard' } } as any;
|
||||
(RoomsSelectors.getRoom as vi.Mock).mockReturnValue(room);
|
||||
it('dispatches raw game list', () => {
|
||||
const game = { gameTypes: [1] } as any;
|
||||
RoomPersistence.updateGames(1, [game]);
|
||||
expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, room.gametypeMap);
|
||||
expect(RoomsDispatch.updateGames).toHaveBeenCalledWith(1, [game]);
|
||||
});
|
||||
|
||||
it('does not normalize when game already has gameType', () => {
|
||||
const game = { gameType: 'Standard' } as any;
|
||||
RoomPersistence.updateGames(1, [game]);
|
||||
expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not normalize when room is not found', () => {
|
||||
const game = { gameType: null } as any;
|
||||
(RoomsSelectors.getRoom as vi.Mock).mockReturnValue(null);
|
||||
RoomPersistence.updateGames(1, [game]);
|
||||
expect(NormalizeService.normalizeGameObject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns without error when gameList is empty', () => {
|
||||
expect(() => RoomPersistence.updateGames(1, [])).not.toThrow();
|
||||
expect(RoomsDispatch.updateGames).not.toHaveBeenCalled();
|
||||
|
|
@ -92,10 +62,9 @@ describe('RoomPersistence', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('addMessage normalizes message and dispatches', () => {
|
||||
it('addMessage dispatches without pre-normalizing', () => {
|
||||
const msg = { name: 'alice', message: 'hi' } as any;
|
||||
RoomPersistence.addMessage(1, msg);
|
||||
expect(NormalizeService.normalizeUserMessage).toHaveBeenCalledWith(msg);
|
||||
expect(RoomsDispatch.addMessage).toHaveBeenCalledWith(1, msg);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { store, RoomsDispatch, RoomsSelectors } from 'store';
|
||||
import { Game, Message, Room, User } from 'types';
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
import { RoomsDispatch } from 'store';
|
||||
import { Message, User } from 'types';
|
||||
import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
|
||||
export class RoomPersistence {
|
||||
static clearStore() {
|
||||
RoomsDispatch.clearStore();
|
||||
}
|
||||
|
||||
static joinRoom(roomInfo: Room) {
|
||||
NormalizeService.normalizeRoomInfo(roomInfo);
|
||||
static joinRoom(roomInfo: ServerInfo_Room) {
|
||||
RoomsDispatch.joinRoom(roomInfo);
|
||||
}
|
||||
|
||||
|
|
@ -16,32 +16,22 @@ export class RoomPersistence {
|
|||
RoomsDispatch.leaveRoom(roomId);
|
||||
}
|
||||
|
||||
static updateRooms(rooms: Room[]) {
|
||||
static updateRooms(rooms: ServerInfo_Room[]) {
|
||||
RoomsDispatch.updateRooms(rooms);
|
||||
}
|
||||
|
||||
static updateGames(roomId: number, gameList: Game[]) {
|
||||
static updateGames(roomId: number, gameList: ServerInfo_Game[]) {
|
||||
// Guard: the server never sends an empty gameList to signal "clear all games".
|
||||
// An empty array here means no game updates — skip the dispatch to avoid
|
||||
// unnecessarily overwriting the existing game list with an empty one.
|
||||
if (!gameList?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const game = gameList[0];
|
||||
|
||||
if (!game.gameType) {
|
||||
const room = RoomsSelectors.getRoom(store.getState(), roomId);
|
||||
|
||||
if (room) {
|
||||
const { gametypeMap } = room;
|
||||
NormalizeService.normalizeGameObject(game, gametypeMap);
|
||||
}
|
||||
}
|
||||
|
||||
RoomsDispatch.updateGames(roomId, gameList);
|
||||
}
|
||||
|
||||
static addMessage(roomId: number, message: Message) {
|
||||
NormalizeService.normalizeUserMessage(message);
|
||||
|
||||
RoomsDispatch.addMessage(roomId, message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,26 +64,15 @@ vi.mock('websocket/utils', () => ({
|
|||
sanitizeHtml: vi.fn((msg: string) => `sanitized:${msg}`),
|
||||
}));
|
||||
|
||||
vi.mock('../utils/NormalizeService', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
normalizeBannedUserError: vi.fn((r: string, t: number) => `banned:${r}:${t}`),
|
||||
normalizeGameObject: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { SessionPersistence } from './SessionPersistence';
|
||||
import { ServerDispatch, GameDispatch } from 'store';
|
||||
import { sanitizeHtml } from 'websocket/utils';
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
import { StatusEnum } from 'types';
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(sanitizeHtml as vi.Mock).mockImplementation((msg: string) => `sanitized:${msg}`);
|
||||
(NormalizeService.normalizeBannedUserError as vi.Mock).mockImplementation(
|
||||
(r: string, t: number) => `banned:${r}:${t}`
|
||||
);
|
||||
(sanitizeHtml as Mock).mockImplementation((msg: string) => `sanitized:${msg}`);
|
||||
});
|
||||
|
||||
describe('SessionPersistence', () => {
|
||||
|
|
@ -230,15 +219,14 @@ describe('SessionPersistence', () => {
|
|||
expect(ServerDispatch.registrationSuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('registrationFailed normalizes ban error when endTime is given', () => {
|
||||
it('registrationFailed passes reason and endTime to ServerDispatch', () => {
|
||||
SessionPersistence.registrationFailed('reason', 999);
|
||||
expect(NormalizeService.normalizeBannedUserError).toHaveBeenCalledWith('reason', 999);
|
||||
expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('banned:reason:999');
|
||||
expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('reason', 999);
|
||||
});
|
||||
|
||||
it('registrationFailed uses reason directly when no endTime', () => {
|
||||
it('registrationFailed passes reason only when no endTime', () => {
|
||||
SessionPersistence.registrationFailed('plain reason');
|
||||
expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('plain reason');
|
||||
expect(ServerDispatch.registrationFailed).toHaveBeenCalledWith('plain reason', undefined);
|
||||
});
|
||||
|
||||
it('registrationEmailError passes error', () => {
|
||||
|
|
@ -298,18 +286,17 @@ describe('SessionPersistence', () => {
|
|||
expect(ServerDispatch.getUserInfo).toHaveBeenCalledWith(user);
|
||||
});
|
||||
|
||||
it('getGamesOfUser normalizes game list and dispatches gamesOfUser', () => {
|
||||
it('getGamesOfUser builds gametypeMap and dispatches raw games with map', () => {
|
||||
const gt = { gameTypeId: 1, description: 'Standard' };
|
||||
const room = { gametypeList: [gt] };
|
||||
const game = { gameId: 5, roomId: 1, gameTypes: [1], description: 'My Game', started: false };
|
||||
SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] });
|
||||
expect(NormalizeService.normalizeGameObject).toHaveBeenCalledWith(game, { 1: 'Standard' });
|
||||
expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game]);
|
||||
SessionPersistence.getGamesOfUser('alice', { roomList: [room], gameList: [game] } as any);
|
||||
expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [game], { 1: 'Standard' });
|
||||
});
|
||||
|
||||
it('getGamesOfUser handles empty response', () => {
|
||||
SessionPersistence.getGamesOfUser('alice', {});
|
||||
expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', []);
|
||||
SessionPersistence.getGamesOfUser('alice', {} as any);
|
||||
expect(ServerDispatch.gamesOfUser).toHaveBeenCalledWith('alice', [], {});
|
||||
});
|
||||
|
||||
it('gameJoined dispatches via GameDispatch.gameJoined', () => {
|
||||
|
|
@ -328,8 +315,9 @@ describe('SessionPersistence', () => {
|
|||
});
|
||||
|
||||
it('playerPropertiesChanged dispatches via GameDispatch', () => {
|
||||
SessionPersistence.playerPropertiesChanged(5, 1, {} as any);
|
||||
expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, {});
|
||||
const props = { pingTime: 100 };
|
||||
SessionPersistence.playerPropertiesChanged(5, 1, { playerProperties: props } as any);
|
||||
expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, props);
|
||||
});
|
||||
|
||||
it('serverShutdown passes data', () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { GameDispatch, ServerDispatch } from 'store';
|
||||
import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types';
|
||||
import { GameEntry } from 'store/game/game.interfaces';
|
||||
|
||||
import { sanitizeHtml } from 'websocket/utils';
|
||||
import {
|
||||
GameJoinedData,
|
||||
|
|
@ -10,11 +9,10 @@ import {
|
|||
ServerShutdownData,
|
||||
UserMessageData
|
||||
} from '../events/session/interfaces';
|
||||
import NormalizeService from '../utils/NormalizeService';
|
||||
|
||||
import type { Response_GetGamesOfUser } from 'generated/proto/response_get_games_of_user_pb';
|
||||
import type { ServerInfo_Room } from 'generated/proto/serverinfo_room_pb';
|
||||
import type { ServerInfo_GameType } from 'generated/proto/serverinfo_gametype_pb';
|
||||
import type { ServerInfo_Game } from 'generated/proto/serverinfo_game_pb';
|
||||
|
||||
export class SessionPersistence {
|
||||
static initialized() {
|
||||
|
|
@ -49,7 +47,7 @@ export class SessionPersistence {
|
|||
ServerDispatch.testConnectionFailed();
|
||||
}
|
||||
|
||||
static updateBuddyList(buddyList) {
|
||||
static updateBuddyList(buddyList: User[]) {
|
||||
ServerDispatch.updateBuddyList(buddyList);
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +59,7 @@ export class SessionPersistence {
|
|||
ServerDispatch.removeFromBuddyList(userName);
|
||||
}
|
||||
|
||||
static updateIgnoreList(ignoreList) {
|
||||
static updateIgnoreList(ignoreList: User[]) {
|
||||
ServerDispatch.updateIgnoreList(ignoreList);
|
||||
}
|
||||
|
||||
|
|
@ -126,9 +124,7 @@ export class SessionPersistence {
|
|||
}
|
||||
|
||||
static registrationFailed(reason: string, endTime?: number) {
|
||||
const reasonMsg = endTime ? NormalizeService.normalizeBannedUserError(reason, endTime) : reason;
|
||||
|
||||
ServerDispatch.registrationFailed(reasonMsg);
|
||||
ServerDispatch.registrationFailed(reason, endTime);
|
||||
}
|
||||
|
||||
static registrationEmailError(error: string) {
|
||||
|
|
@ -182,11 +178,8 @@ export class SessionPersistence {
|
|||
gametypeMap[gt.gameTypeId] = gt.description;
|
||||
});
|
||||
});
|
||||
const games = (response.gameList || []).map((game: ServerInfo_Game) => {
|
||||
NormalizeService.normalizeGameObject(game, gametypeMap);
|
||||
return game;
|
||||
});
|
||||
ServerDispatch.gamesOfUser(userName, games);
|
||||
const games = response.gameList || [];
|
||||
ServerDispatch.gamesOfUser(userName, games, gametypeMap);
|
||||
}
|
||||
|
||||
static gameJoined(gameJoinedData: GameJoinedData): void {
|
||||
|
|
@ -216,7 +209,9 @@ export class SessionPersistence {
|
|||
}
|
||||
|
||||
static playerPropertiesChanged(gameId: number, playerId: number, payload: PlayerGamePropertiesData): void {
|
||||
GameDispatch.playerPropertiesChanged(gameId, playerId, payload);
|
||||
if (payload.playerProperties) {
|
||||
GameDispatch.playerPropertiesChanged(gameId, playerId, payload.playerProperties);
|
||||
}
|
||||
}
|
||||
|
||||
static serverShutdown(data: ServerShutdownData): void {
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ describe('BackendService', () => {
|
|||
expect(onSuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onSuccess with raw when responseCode is RespOk and no responseExt', () => {
|
||||
it('calls onSuccess when responseCode is RespOk and no responseExt', () => {
|
||||
const onSuccess = vi.fn();
|
||||
const raw = { responseCode: 1 };
|
||||
invokeCallback({ onSuccess }, raw);
|
||||
expect(onSuccess).toHaveBeenCalledWith(raw, raw);
|
||||
expect(onSuccess).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('calls onSuccess with nested response when responseExt is set', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { create, getExtension, setExtension } from '@bufbuild/protobuf';
|
||||
import type { GenExtension } from '@bufbuild/protobuf';
|
||||
import type { GenExtension } from '@bufbuild/protobuf/codegenv2';
|
||||
|
||||
import webClient from '../WebClient';
|
||||
import { Response_ResponseCode, type Response } from 'generated/proto/response_pb';
|
||||
|
|
@ -9,56 +9,80 @@ import { RoomCommandSchema, type RoomCommand } from 'generated/proto/room_comman
|
|||
import { ModeratorCommandSchema, type ModeratorCommand } from 'generated/proto/moderator_commands_pb';
|
||||
import { AdminCommandSchema, type AdminCommand } from 'generated/proto/admin_commands_pb';
|
||||
|
||||
export interface CommandOptions<R = unknown> {
|
||||
responseExt?: GenExtension<Response, R>;
|
||||
onSuccess?: (response: R, raw: Response) => void;
|
||||
interface CommandOptionsBase {
|
||||
onError?: (responseCode: number, raw: Response) => void;
|
||||
onResponseCode?: { [code: number]: (raw: Response) => void };
|
||||
onResponse?: (raw: Response) => void;
|
||||
}
|
||||
|
||||
export interface CommandOptionsWithResponse<R> extends CommandOptionsBase {
|
||||
responseExt: GenExtension<Response, R>;
|
||||
onSuccess?: (response: R, raw: Response) => void;
|
||||
}
|
||||
|
||||
export interface CommandOptionsWithoutResponse extends CommandOptionsBase {
|
||||
responseExt?: undefined;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
export type CommandOptions<R = unknown> = CommandOptionsWithResponse<R> | CommandOptionsWithoutResponse;
|
||||
|
||||
function hasResponseExt<R>(options: CommandOptions<R>): options is CommandOptionsWithResponse<R> {
|
||||
return options.responseExt !== undefined;
|
||||
}
|
||||
|
||||
export class BackendService {
|
||||
static sendGameCommand<V>(gameId: number, ext: GenExtension<GameCommand, V>, value: V, options: CommandOptions<V> = {}): void {
|
||||
static sendGameCommand<V, R>(gameId: number, ext: GenExtension<GameCommand, V>, value: V, options?: CommandOptions<R>): void {
|
||||
const cmd = create(GameCommandSchema);
|
||||
setExtension(cmd, ext, value);
|
||||
webClient.protobuf.sendGameCommand(gameId, cmd, (raw: Response) => {
|
||||
BackendService.handleResponse(ext, raw, options);
|
||||
if (options) {
|
||||
BackendService.handleResponse(ext.typeName, raw, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static sendSessionCommand<V>(ext: GenExtension<SessionCommand, V>, value: V, options: CommandOptions<V> = {}): void {
|
||||
static sendSessionCommand<V, R>(ext: GenExtension<SessionCommand, V>, value: V, options?: CommandOptions<R>): void {
|
||||
const cmd = create(SessionCommandSchema);
|
||||
setExtension(cmd, ext, value);
|
||||
webClient.protobuf.sendSessionCommand(cmd, raw => {
|
||||
BackendService.handleResponse(ext, raw, options);
|
||||
if (options) {
|
||||
BackendService.handleResponse(ext.typeName, raw, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static sendRoomCommand<V>(roomId: number, ext: GenExtension<RoomCommand, V>, value: V, options: CommandOptions<V> = {}): void {
|
||||
static sendRoomCommand<V, R>(roomId: number, ext: GenExtension<RoomCommand, V>, value: V, options?: CommandOptions<R>): void {
|
||||
const cmd = create(RoomCommandSchema);
|
||||
setExtension(cmd, ext, value);
|
||||
webClient.protobuf.sendRoomCommand(roomId, cmd, raw => {
|
||||
BackendService.handleResponse(ext, raw, options);
|
||||
if (options) {
|
||||
BackendService.handleResponse(ext.typeName, raw, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static sendModeratorCommand<V>(ext: GenExtension<ModeratorCommand, V>, value: V, options: CommandOptions<V> = {}): void {
|
||||
static sendModeratorCommand<V, R>(ext: GenExtension<ModeratorCommand, V>, value: V, options?: CommandOptions<R>): void {
|
||||
const cmd = create(ModeratorCommandSchema);
|
||||
setExtension(cmd, ext, value);
|
||||
webClient.protobuf.sendModeratorCommand(cmd, raw => {
|
||||
BackendService.handleResponse(ext, raw, options);
|
||||
if (options) {
|
||||
BackendService.handleResponse(ext.typeName, raw, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static sendAdminCommand<V>(ext: GenExtension<AdminCommand, V>, value: V, options: CommandOptions<V> = {}): void {
|
||||
static sendAdminCommand<V, R>(ext: GenExtension<AdminCommand, V>, value: V, options?: CommandOptions<R>): void {
|
||||
const cmd = create(AdminCommandSchema);
|
||||
setExtension(cmd, ext, value);
|
||||
webClient.protobuf.sendAdminCommand(cmd, raw => {
|
||||
BackendService.handleResponse(ext, raw, options);
|
||||
if (options) {
|
||||
BackendService.handleResponse(ext.typeName, raw, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static handleResponse<R>(ext: GenExtension<any, R>, raw: Response, options: CommandOptions<R>): void {
|
||||
private static handleResponse<R>(typeName: string, raw: Response, options: CommandOptions<R>): void {
|
||||
if (options.onResponse) {
|
||||
options.onResponse(raw);
|
||||
return;
|
||||
|
|
@ -67,11 +91,10 @@ export class BackendService {
|
|||
const { responseCode } = raw;
|
||||
|
||||
if (responseCode === Response_ResponseCode.RespOk) {
|
||||
if (options.onSuccess) {
|
||||
const response = options.responseExt
|
||||
? getExtension(raw, options.responseExt)
|
||||
: raw as unknown as R;
|
||||
options.onSuccess(response, raw);
|
||||
if (hasResponseExt(options)) {
|
||||
options.onSuccess?.(getExtension(raw, options.responseExt), raw);
|
||||
} else {
|
||||
options.onSuccess?.();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -84,7 +107,7 @@ export class BackendService {
|
|||
if (options.onError) {
|
||||
options.onError(responseCode, raw);
|
||||
} else {
|
||||
console.error(`${ext.typeName} failed with response code: ${responseCode}`);
|
||||
console.error(`${typeName} failed with response code: ${responseCode}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
vi.mock('../WebClient', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
socket: {
|
||||
checkReadyState: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import { KeepAliveService } from './KeepAliveService';
|
||||
|
||||
import webClient from '../WebClient';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export class KeepAliveService {
|
|||
this.socket = socket;
|
||||
}
|
||||
|
||||
public startPingLoop(interval: number, ping: Function): void {
|
||||
public startPingLoop(interval: number, ping: (onPong: () => void) => void): void {
|
||||
this.keepalivecb = setInterval(() => {
|
||||
// check if the previous ping got no reply
|
||||
if (this.lastPingPending) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ vi.mock('../WebClient', () => ({
|
|||
default: {},
|
||||
}));
|
||||
|
||||
import { fromBinary, toBinary, hasExtension, getExtension } from '@bufbuild/protobuf';
|
||||
import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf';
|
||||
import { ServerMessage_MessageType } from 'generated/proto/server_message_pb';
|
||||
import { ProtobufService } from './ProtobufService';
|
||||
import { ping as sessionPing } from '../commands/session';
|
||||
|
|
@ -351,36 +351,4 @@ describe('ProtobufService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('processEvent', () => {
|
||||
it('calls matching event handler with payload and raw', () => {
|
||||
const service = new ProtobufService({ socket: mockSocket } as any);
|
||||
const handler = vi.fn();
|
||||
const mockExt = {};
|
||||
const registry = [[mockExt, handler]] as any;
|
||||
const payload = { someData: 1 };
|
||||
const raw = { extra: true };
|
||||
|
||||
vi.mocked(hasExtension).mockReturnValue(true);
|
||||
vi.mocked(getExtension).mockReturnValue(payload);
|
||||
|
||||
(service as any).processEvent({}, registry, raw);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(payload, raw);
|
||||
});
|
||||
|
||||
it('stops after first matching event', () => {
|
||||
const service = new ProtobufService({ socket: mockSocket } as any);
|
||||
const handler1 = vi.fn();
|
||||
const handler2 = vi.fn();
|
||||
const registry = [[{}, handler1], [{}, handler2]] as any;
|
||||
|
||||
vi.mocked(hasExtension).mockReturnValueOnce(true).mockReturnValueOnce(false);
|
||||
vi.mocked(getExtension).mockReturnValue({ x: 1 });
|
||||
|
||||
(service as any).processEvent({}, registry, {});
|
||||
|
||||
expect(handler1).toHaveBeenCalled();
|
||||
expect(handler2).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { create, fromBinary, hasExtension, getExtension, toBinary } from '@bufbuild/protobuf';
|
||||
import type { GenExtension, Message } from '@bufbuild/protobuf';
|
||||
import type { GenExtension } from '@bufbuild/protobuf/codegenv2';
|
||||
|
||||
import type { Response } from 'generated/proto/response_pb';
|
||||
import type { RoomEvent } from 'generated/proto/room_event_pb';
|
||||
import type { SessionEvent } from 'generated/proto/session_event_pb';
|
||||
import type { GameEventContainer } from 'generated/proto/game_event_container_pb';
|
||||
import type { GameEvent } from 'generated/proto/game_event_pb';
|
||||
|
||||
import { GameEvents, RoomEvents, SessionEvents } from '../events';
|
||||
import { WebClient } from '../WebClient';
|
||||
|
|
@ -19,11 +20,57 @@ import type { RoomCommand } from 'generated/proto/room_commands_pb';
|
|||
import type { ModeratorCommand } from 'generated/proto/moderator_commands_pb';
|
||||
import type { AdminCommand } from 'generated/proto/admin_commands_pb';
|
||||
|
||||
export type ExtensionRegistry = Array<[GenExtension<any, any>, (...args: unknown[]) => void]>;
|
||||
// Per-family registry entry types. Each family hardcodes its parent message type
|
||||
// and the handler's exact secondary-argument signature, eliminating the previous
|
||||
// `...args: unknown[]` erasure.
|
||||
|
||||
type SessionRegistryEntry<V = unknown> = [
|
||||
GenExtension<SessionEvent, V>,
|
||||
(value: V) => void
|
||||
];
|
||||
export type SessionExtensionRegistry = SessionRegistryEntry[];
|
||||
|
||||
type RoomRegistryEntry<V = unknown> = [
|
||||
GenExtension<RoomEvent, V>,
|
||||
(value: V, roomEvent: RoomEvent) => void
|
||||
];
|
||||
export type RoomExtensionRegistry = RoomRegistryEntry[];
|
||||
|
||||
type GameRegistryEntry<V = unknown> = [
|
||||
GenExtension<GameEvent, V>,
|
||||
(value: V, meta: GameEventMeta) => void
|
||||
];
|
||||
export type GameExtensionRegistry = GameRegistryEntry[];
|
||||
|
||||
/**
|
||||
* Type-safe factory functions. The compiler verifies at the call site that the
|
||||
* handler's parameter types match the extension's value type and the family's
|
||||
* secondary argument type.
|
||||
*/
|
||||
export function makeSessionEntry<V>(
|
||||
ext: GenExtension<SessionEvent, V>,
|
||||
handler: (value: V) => void
|
||||
): SessionRegistryEntry {
|
||||
return [ext as GenExtension<SessionEvent, unknown>, handler as (value: unknown) => void];
|
||||
}
|
||||
|
||||
export function makeRoomEntry<V>(
|
||||
ext: GenExtension<RoomEvent, V>,
|
||||
handler: (value: V, roomEvent: RoomEvent) => void
|
||||
): RoomRegistryEntry {
|
||||
return [ext as GenExtension<RoomEvent, unknown>, handler as RoomRegistryEntry[1]];
|
||||
}
|
||||
|
||||
export function makeGameEntry<V>(
|
||||
ext: GenExtension<GameEvent, V>,
|
||||
handler: (value: V, meta: GameEventMeta) => void
|
||||
): GameRegistryEntry {
|
||||
return [ext as GenExtension<GameEvent, unknown>, handler as GameRegistryEntry[1]];
|
||||
}
|
||||
|
||||
export class ProtobufService {
|
||||
private cmdId = 0;
|
||||
private pendingCommands: { [cmdId: string]: Function } = {};
|
||||
private pendingCommands: { [cmdId: string]: (response: Response) => void } = {};
|
||||
|
||||
private webClient: WebClient;
|
||||
|
||||
|
|
@ -36,44 +83,44 @@ export class ProtobufService {
|
|||
this.pendingCommands = {};
|
||||
}
|
||||
|
||||
public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: Function) {
|
||||
public sendGameCommand(gameId: number, gameCmd: GameCommand, callback?: (raw: Response) => void) {
|
||||
const cmd = create(CommandContainerSchema, {
|
||||
gameId,
|
||||
gameCommand: [gameCmd],
|
||||
});
|
||||
this.sendCommand(cmd, (raw: Response) => callback && callback(raw));
|
||||
this.sendCommand(cmd, (raw: Response) => callback?.(raw));
|
||||
}
|
||||
|
||||
public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: Function) {
|
||||
public sendRoomCommand(roomId: number, roomCmd: RoomCommand, callback?: (raw: Response) => void) {
|
||||
const cmd = create(CommandContainerSchema, {
|
||||
roomId,
|
||||
roomCommand: [roomCmd],
|
||||
});
|
||||
this.sendCommand(cmd, raw => callback && callback(raw));
|
||||
this.sendCommand(cmd, raw => callback?.(raw));
|
||||
}
|
||||
|
||||
public sendSessionCommand(sesCmd: SessionCommand, callback?: Function) {
|
||||
public sendSessionCommand(sesCmd: SessionCommand, callback?: (raw: Response) => void) {
|
||||
const cmd = create(CommandContainerSchema, {
|
||||
sessionCommand: [sesCmd],
|
||||
});
|
||||
this.sendCommand(cmd, (raw) => callback && callback(raw));
|
||||
this.sendCommand(cmd, (raw) => callback?.(raw));
|
||||
}
|
||||
|
||||
public sendModeratorCommand(modCmd: ModeratorCommand, callback?: Function) {
|
||||
public sendModeratorCommand(modCmd: ModeratorCommand, callback?: (raw: Response) => void) {
|
||||
const cmd = create(CommandContainerSchema, {
|
||||
moderatorCommand: [modCmd],
|
||||
});
|
||||
this.sendCommand(cmd, (raw) => callback && callback(raw));
|
||||
this.sendCommand(cmd, (raw) => callback?.(raw));
|
||||
}
|
||||
|
||||
public sendAdminCommand(adminCmd: AdminCommand, callback?: Function) {
|
||||
public sendAdminCommand(adminCmd: AdminCommand, callback?: (raw: Response) => void) {
|
||||
const cmd = create(CommandContainerSchema, {
|
||||
adminCommand: [adminCmd],
|
||||
});
|
||||
this.sendCommand(cmd, (raw) => callback && callback(raw));
|
||||
this.sendCommand(cmd, (raw) => callback?.(raw));
|
||||
}
|
||||
|
||||
public sendCommand(cmd: CommandContainer, callback: Function) {
|
||||
public sendCommand(cmd: CommandContainer, callback: (raw: Response) => void) {
|
||||
this.cmdId++;
|
||||
|
||||
cmd.cmdId = BigInt(this.cmdId);
|
||||
|
|
@ -84,7 +131,7 @@ export class ProtobufService {
|
|||
}
|
||||
}
|
||||
|
||||
public sendKeepAliveCommand(pingReceived: Function) {
|
||||
public sendKeepAliveCommand(pingReceived: () => void) {
|
||||
SessionCommands.ping(pingReceived);
|
||||
}
|
||||
|
||||
|
|
@ -133,14 +180,24 @@ export class ProtobufService {
|
|||
if (!event) {
|
||||
return;
|
||||
}
|
||||
this.processEvent(event, RoomEvents, event);
|
||||
for (const [ext, handler] of RoomEvents) {
|
||||
if (hasExtension(event, ext)) {
|
||||
handler(getExtension(event, ext), event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processSessionEvent(event: SessionEvent | undefined) {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
this.processEvent(event, SessionEvents);
|
||||
for (const [ext, handler] of SessionEvents) {
|
||||
if (hasExtension(event, ext)) {
|
||||
handler(getExtension(event, ext));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processGameEvent(container: GameEventContainer | undefined): void {
|
||||
|
|
@ -161,20 +218,12 @@ export class ProtobufService {
|
|||
|
||||
for (const [ext, handler] of GameEvents) {
|
||||
if (hasExtension(event, ext)) {
|
||||
(handler as Function)(getExtension(event, ext), meta);
|
||||
handler(getExtension(event, ext), meta);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processEvent(response: Message<string>, registry: ExtensionRegistry, raw?: Message) {
|
||||
for (const [ext, handler] of registry) {
|
||||
if (hasExtension(response, ext)) {
|
||||
(handler as Function)(getExtension(response, ext), raw);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { installMockWebSocket } from '../__mocks__/helpers';
|
||||
import { Mock } from 'vitest';
|
||||
|
||||
vi.mock('../WebClient', () => ({
|
||||
WebClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../commands/session', () => ({
|
||||
updateStatus: vi.fn(),
|
||||
|
|
@ -17,8 +22,9 @@ import { SessionPersistence } from '../persistence';
|
|||
import { updateStatus } from '../commands/session';
|
||||
import { StatusEnum } from 'types';
|
||||
|
||||
let MockWS: vi.Mock;
|
||||
let MockWS: Mock;
|
||||
let mockInstance: ReturnType<typeof installMockWebSocket>['mockInstance'];
|
||||
let restoreWebSocket: ReturnType<typeof installMockWebSocket>['restore'];
|
||||
let mockWebClient: any;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -28,6 +34,7 @@ beforeEach(() => {
|
|||
const installed = installMockWebSocket();
|
||||
MockWS = installed.MockWS;
|
||||
mockInstance = installed.mockInstance;
|
||||
restoreWebSocket = installed.restore;
|
||||
|
||||
mockWebClient = {
|
||||
status: StatusEnum.CONNECTED,
|
||||
|
|
@ -37,6 +44,7 @@ beforeEach(() => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
restoreWebSocket();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
|
|
@ -106,7 +114,7 @@ describe('WebSocketService', () => {
|
|||
|
||||
describe('socket event handlers (onopen)', () => {
|
||||
it('clears the connection timeout when socket opens', () => {
|
||||
const clearSpy = vi.spyOn(global, 'clearTimeout');
|
||||
const clearSpy = vi.spyOn(globalThis, 'clearTimeout');
|
||||
createConnectedService();
|
||||
mockInstance.onopen();
|
||||
expect(clearSpy).toHaveBeenCalled();
|
||||
|
|
@ -262,7 +270,7 @@ describe('WebSocketService', () => {
|
|||
|
||||
it('calls SessionPersistence.testConnectionSuccessful on open', () => {
|
||||
createTestConnectedService();
|
||||
const timer = vi.spyOn(global, 'clearTimeout');
|
||||
vi.spyOn(globalThis, 'clearTimeout');
|
||||
mockInstance.onopen();
|
||||
expect(SessionPersistence.testConnectionSuccessful).toHaveBeenCalled();
|
||||
expect(mockInstance.close).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export class WebSocketService {
|
|||
return this.socket?.readyState === state;
|
||||
}
|
||||
|
||||
public send(message): void {
|
||||
public send(message: Uint8Array): void {
|
||||
this.socket.send(message);
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ export class WebSocketService {
|
|||
clearTimeout(connectionTimer);
|
||||
updateStatus(StatusEnum.CONNECTED, 'Connected');
|
||||
|
||||
this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: Function) => {
|
||||
this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: () => void) => {
|
||||
this.webClient.keepAlive(pingReceived);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
import NormalizeService from './NormalizeService';
|
||||
|
||||
describe('NormalizeService', () => {
|
||||
describe('normalizeRoomInfo', () => {
|
||||
it('builds gametypeMap from gametypeList', () => {
|
||||
const roomInfo: any = {
|
||||
gametypeList: [
|
||||
{ gameTypeId: 1, description: 'Standard' },
|
||||
{ gameTypeId: 2, description: 'Draft' },
|
||||
],
|
||||
gametypeMap: {},
|
||||
gameList: [],
|
||||
};
|
||||
NormalizeService.normalizeRoomInfo(roomInfo);
|
||||
expect(roomInfo.gametypeMap).toEqual({ 1: 'Standard', 2: 'Draft' });
|
||||
});
|
||||
|
||||
it('normalizes each game in gameList', () => {
|
||||
const roomInfo: any = {
|
||||
gametypeList: [{ gameTypeId: 5, description: 'Modern' }],
|
||||
gametypeMap: {},
|
||||
gameList: [{ gameTypes: [5], description: 'My Game' }],
|
||||
};
|
||||
NormalizeService.normalizeRoomInfo(roomInfo);
|
||||
expect(roomInfo.gameList[0].gameType).toBe('Modern');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeGameObject', () => {
|
||||
it('sets gameType from first element of gameTypes', () => {
|
||||
const game: any = { gameTypes: [3], description: 'Test' };
|
||||
const map: any = { 3: 'Legacy' };
|
||||
NormalizeService.normalizeGameObject(game, map);
|
||||
expect(game.gameType).toBe('Legacy');
|
||||
});
|
||||
|
||||
it('sets gameType to empty string when gameTypes is empty', () => {
|
||||
const game: any = { gameTypes: [], description: 'Test' };
|
||||
NormalizeService.normalizeGameObject(game, {});
|
||||
expect(game.gameType).toBe('');
|
||||
});
|
||||
|
||||
it('sets gameType to empty string when gameTypes is null', () => {
|
||||
const game: any = { gameTypes: null, description: 'Test' };
|
||||
NormalizeService.normalizeGameObject(game, {});
|
||||
expect(game.gameType).toBe('');
|
||||
});
|
||||
|
||||
it('sets description to empty string when description is falsy', () => {
|
||||
const game: any = { gameTypes: [], description: null };
|
||||
NormalizeService.normalizeGameObject(game, {});
|
||||
expect(game.description).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeLogs', () => {
|
||||
it('groups logs by targetType', () => {
|
||||
const logs: any[] = [
|
||||
{ targetType: 'room', msg: 'a' },
|
||||
{ targetType: 'chat', msg: 'b' },
|
||||
{ targetType: 'room', msg: 'c' },
|
||||
];
|
||||
const result = NormalizeService.normalizeLogs(logs);
|
||||
expect(result['room']).toHaveLength(2);
|
||||
expect(result['chat']).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('returns empty object for empty array', () => {
|
||||
expect(NormalizeService.normalizeLogs([])).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeUserMessage', () => {
|
||||
it('prepends username when name is present', () => {
|
||||
const message: any = { name: 'Alice', message: 'hello' };
|
||||
NormalizeService.normalizeUserMessage(message);
|
||||
expect(message.message).toBe('Alice: hello');
|
||||
});
|
||||
|
||||
it('does not modify message when name is absent', () => {
|
||||
const message: any = { name: '', message: 'hello' };
|
||||
NormalizeService.normalizeUserMessage(message);
|
||||
expect(message.message).toBe('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeBannedUserError', () => {
|
||||
it('returns permanently banned message when endTime is 0', () => {
|
||||
const result = NormalizeService.normalizeBannedUserError('', 0);
|
||||
expect(result).toBe('You are permanently banned');
|
||||
});
|
||||
|
||||
it('returns banned until date when endTime is given', () => {
|
||||
const endTime = new Date('2030-01-01').getTime();
|
||||
const result = NormalizeService.normalizeBannedUserError('', endTime);
|
||||
expect(result).toContain('You are banned until');
|
||||
expect(result).toContain(new Date(endTime).toString());
|
||||
});
|
||||
|
||||
it('appends reasonStr when provided', () => {
|
||||
const result = NormalizeService.normalizeBannedUserError('bad behavior', 0);
|
||||
expect(result).toContain('\n\nbad behavior');
|
||||
});
|
||||
|
||||
it('does not append when reasonStr is empty', () => {
|
||||
const result = NormalizeService.normalizeBannedUserError('', 0);
|
||||
expect(result).not.toContain('\n\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import { Game, GametypeMap, LogItem, LogGroups, Message, Room } from 'types';
|
||||
|
||||
export default class NormalizeService {
|
||||
// Flatten room gameTypes into map object
|
||||
static normalizeRoomInfo(roomInfo: Room): void {
|
||||
roomInfo.gametypeMap = {};
|
||||
|
||||
const { gametypeList, gametypeMap, gameList } = roomInfo;
|
||||
|
||||
gametypeList.reduce((map, type) => {
|
||||
map[type.gameTypeId] = type.description;
|
||||
return map;
|
||||
}, gametypeMap);
|
||||
|
||||
gameList.forEach((game) => NormalizeService.normalizeGameObject(game, gametypeMap));
|
||||
}
|
||||
|
||||
// Flatten gameTypes[] into gameType field
|
||||
// Default sortable values ("" || 0 || -1)
|
||||
static normalizeGameObject(game: Game, gametypeMap: GametypeMap): void {
|
||||
const { gameTypes, description } = game;
|
||||
const hasType = gameTypes && gameTypes.length;
|
||||
game.gameType = hasType ? gametypeMap[gameTypes[0]] : '';
|
||||
|
||||
game.description = description || '';
|
||||
}
|
||||
|
||||
// Flatten logs[] into object mapped by targetType (room, game, chat)
|
||||
static normalizeLogs(logs: LogItem[]): LogGroups {
|
||||
return logs.reduce((obj, log) => {
|
||||
const { targetType } = log;
|
||||
obj[targetType] = obj[targetType] || [];
|
||||
obj[targetType].push(log);
|
||||
return obj;
|
||||
}, {} as LogGroups);
|
||||
}
|
||||
|
||||
// messages sent by current user dont have their username prepended
|
||||
static normalizeUserMessage(message: Message): void {
|
||||
const { name } = message;
|
||||
|
||||
if (name) {
|
||||
message.message = `${name}: ${message.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Banned reason string is not being exposed by the server
|
||||
static normalizeBannedUserError(reasonStr: string, endTime: number): string {
|
||||
let error;
|
||||
|
||||
if (endTime) {
|
||||
error = 'You are banned until ' + new Date(endTime).toString();
|
||||
} else {
|
||||
error = 'You are permanently banned';
|
||||
}
|
||||
|
||||
if (reasonStr) {
|
||||
error += '\n\n' + reasonStr;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue