mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
Add near 100% unit test coverage for webclient websocket layer
This commit is contained in:
parent
8cc65b8967
commit
35be723ebf
26 changed files with 3932 additions and 0 deletions
|
|
@ -0,0 +1,512 @@
|
|||
// Tests for complex session commands that call webClient directly
|
||||
// or have multiple branching callbacks.
|
||||
|
||||
jest.mock('../../services/BackendService', () => ({
|
||||
BackendService: {
|
||||
sendSessionCommand: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../persistence', () => {
|
||||
const { makeSessionPersistenceMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return {
|
||||
SessionPersistence: makeSessionPersistenceMock(),
|
||||
RoomPersistence: {},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../WebClient', () => {
|
||||
const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return { __esModule: true, default: makeWebClientMock() };
|
||||
});
|
||||
|
||||
jest.mock('../../services/ProtoController', () => {
|
||||
const { makeProtoControllerRootMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return { ProtoController: { root: makeProtoControllerRootMock() } };
|
||||
});
|
||||
|
||||
jest.mock('../../utils', () => {
|
||||
const { makeUtilsMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return makeUtilsMock();
|
||||
});
|
||||
|
||||
// Intercept all re-exported commands to avoid recursive real invocations
|
||||
jest.mock('./', () => {
|
||||
const { makeSessionBarrelMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return makeSessionBarrelMock();
|
||||
});
|
||||
|
||||
import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers';
|
||||
import { BackendService } from '../../services/BackendService';
|
||||
import { SessionPersistence } from '../../persistence';
|
||||
import webClient from '../../WebClient';
|
||||
import * as SessionIndexMocks from './';
|
||||
import { StatusEnum, WebSocketConnectReason } from 'types';
|
||||
import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils';
|
||||
|
||||
const { getLastSendOpts, invokeOnSuccess, invokeResponseCode, invokeOnError } = makeCallbackHelpers(
|
||||
BackendService.sendSessionCommand as jest.Mock
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(hashPassword as jest.Mock).mockReturnValue('hashed_pw');
|
||||
(generateSalt as jest.Mock).mockReturnValue('randSalt');
|
||||
(passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// connect.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('connect', () => {
|
||||
const { connect } = jest.requireActual('./connect');
|
||||
|
||||
it('calls updateStatus CONNECTING for LOGIN reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.LOGIN);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
|
||||
expect(webClient.connect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls updateStatus CONNECTING for REGISTER reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.REGISTER);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
|
||||
});
|
||||
|
||||
it('calls updateStatus CONNECTING for ACTIVATE_ACCOUNT reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.ACTIVATE_ACCOUNT);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
|
||||
});
|
||||
|
||||
it('calls updateStatus CONNECTING for PASSWORD_RESET_REQUEST reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET_REQUEST);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
|
||||
});
|
||||
|
||||
it('calls updateStatus CONNECTING for PASSWORD_RESET_CHALLENGE reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
|
||||
});
|
||||
|
||||
it('calls updateStatus CONNECTING for PASSWORD_RESET reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.PASSWORD_RESET);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
|
||||
});
|
||||
|
||||
it('calls testConnect for TEST_CONNECTION reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, WebSocketConnectReason.TEST_CONNECTION);
|
||||
expect(webClient.testConnect).toHaveBeenCalled();
|
||||
expect(webClient.connect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls updateStatus DISCONNECTED for unknown reason', () => {
|
||||
connect({ host: 'h', port: 1 } as any, 999 as WebSocketConnectReason);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.stringContaining('Unknown'));
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// updateStatus.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('updateStatus', () => {
|
||||
const { updateStatus } = jest.requireActual('./updateStatus');
|
||||
|
||||
it('calls SessionPersistence.updateStatus and webClient.updateStatus', () => {
|
||||
updateStatus(StatusEnum.CONNECTED, 'OK');
|
||||
expect(SessionPersistence.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED, 'OK');
|
||||
expect(webClient.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTED);
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// login.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('login', () => {
|
||||
const { login } = jest.requireActual('./login');
|
||||
|
||||
it('sends Command_Login with plain password when no salt', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_Login',
|
||||
expect.objectContaining({ userName: 'alice', password: 'pw' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('sends Command_Login with hashedPassword when salt is given', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any, 'salt');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_Login',
|
||||
expect.objectContaining({ hashedPassword: 'hashed_pw' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('uses options.hashedPassword if provided', () => {
|
||||
login({ userName: 'alice', password: 'pw', hashedPassword: 'pre_hashed' } as any, 'salt');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_Login',
|
||||
expect.objectContaining({ hashedPassword: 'pre_hashed' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('onSuccess dispatches buddy/ignore/user and calls listUsers/listRooms', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } };
|
||||
invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp });
|
||||
expect(SessionPersistence.updateBuddyList).toHaveBeenCalledWith([]);
|
||||
expect(SessionPersistence.updateIgnoreList).toHaveBeenCalledWith([]);
|
||||
expect(SessionPersistence.updateUser).toHaveBeenCalledWith({ name: 'alice' });
|
||||
expect(SessionPersistence.loginSuccessful).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.listUsers).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.listRooms).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGED_IN, 'Logged in.');
|
||||
});
|
||||
|
||||
it('onResponseCode RespClientUpdateRequired calls onLoginError', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(1);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespWrongPassword', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(2);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespUsernameInvalid', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(3);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespWouldOverwriteOldSession', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(4);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespUserIsBanned', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(5);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespRegistrationRequired', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(6);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespClientIdRequired', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(7);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespContextError', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(8);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespAccountNotActivated calls accountAwaitingActivation', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(9);
|
||||
expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled();
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onError calls onLoginError with unknown error message', () => {
|
||||
login({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeOnError(999);
|
||||
expect(SessionPersistence.loginFailed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// register.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('register', () => {
|
||||
const { register } = jest.requireActual('./register');
|
||||
|
||||
it('sends Command_Register with plain password when no salt', () => {
|
||||
register({ userName: 'alice', password: 'pw', email: 'a@b.com', country: 'US', realName: 'Al' } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_Register',
|
||||
expect.objectContaining({ userName: 'alice', password: 'pw' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('uses hashedPassword when salt is provided', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any, 'salt');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_Register',
|
||||
expect.objectContaining({ hashedPassword: 'hashed_pw' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('RespRegistrationAccepted calls login without salt and registrationSuccess', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(10);
|
||||
expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), undefined);
|
||||
expect(SessionPersistence.registrationSuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespRegistrationAccepted forwards salt to login', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any, 'mySalt');
|
||||
invokeResponseCode(10);
|
||||
expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'mySalt');
|
||||
expect(SessionPersistence.registrationSuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespRegistrationAcceptedNeedsActivation calls accountAwaitingActivation', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(11);
|
||||
expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespUserAlreadyExists calls registrationUserNameError', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(12);
|
||||
expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespUsernameInvalid calls registrationUserNameError', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(3);
|
||||
expect(SessionPersistence.registrationUserNameError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespPasswordTooShort calls registrationPasswordError', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(13);
|
||||
expect(SessionPersistence.registrationPasswordError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespEmailRequiredToRegister calls registrationRequiresEmail', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(14);
|
||||
expect(SessionPersistence.registrationRequiresEmail).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespEmailBlackListed calls registrationEmailError', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(15);
|
||||
expect(SessionPersistence.registrationEmailError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespTooManyRequests calls registrationEmailError', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(16);
|
||||
expect(SessionPersistence.registrationEmailError).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespRegistrationDisabled calls registrationFailed', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(17);
|
||||
expect(SessionPersistence.registrationFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('RespUserIsBanned calls registrationFailed with raw.reasonStr and raw.endTime', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeResponseCode(5, { reasonStr: 'bad user', endTime: 9999 });
|
||||
expect(SessionPersistence.registrationFailed).toHaveBeenCalledWith('bad user', 9999);
|
||||
});
|
||||
|
||||
it('onError calls registrationFailed', () => {
|
||||
register({ userName: 'alice', password: 'pw' } as any);
|
||||
invokeOnError();
|
||||
expect(SessionPersistence.registrationFailed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// activate.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('activate', () => {
|
||||
const { activate } = jest.requireActual('./activate');
|
||||
|
||||
it('sends Command_Activate', () => {
|
||||
activate({ userName: 'alice', token: 'tok' } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Activate', expect.any(Object), expect.any(Object));
|
||||
});
|
||||
|
||||
it('RespActivationAccepted calls accountActivationSuccess and login with salt', () => {
|
||||
activate({ userName: 'alice', token: 'tok' } as any, 'salt');
|
||||
invokeResponseCode(18);
|
||||
expect(SessionPersistence.accountActivationSuccess).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'salt');
|
||||
});
|
||||
|
||||
it('onError calls accountActivationFailed and disconnect', () => {
|
||||
activate({ userName: 'alice', token: 'tok' } as any);
|
||||
invokeOnError();
|
||||
expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// forgotPasswordChallenge.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('forgotPasswordChallenge', () => {
|
||||
const { forgotPasswordChallenge } = jest.requireActual('./forgotPasswordChallenge');
|
||||
|
||||
it('sends Command_ForgotPasswordChallenge', () => {
|
||||
forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ForgotPasswordChallenge', expect.any(Object), expect.any(Object));
|
||||
});
|
||||
|
||||
it('onSuccess calls resetPassword and disconnect', () => {
|
||||
forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any);
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.resetPassword).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onError calls resetPasswordFailed and disconnect', () => {
|
||||
forgotPasswordChallenge({ userName: 'alice', email: 'a@b.com' } as any);
|
||||
invokeOnError();
|
||||
expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// forgotPasswordRequest.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('forgotPasswordRequest', () => {
|
||||
const { forgotPasswordRequest } = jest.requireActual('./forgotPasswordRequest');
|
||||
|
||||
it('sends Command_ForgotPasswordRequest', () => {
|
||||
forgotPasswordRequest({ userName: 'alice' } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ForgotPasswordRequest', expect.any(Object), expect.any(Object));
|
||||
});
|
||||
|
||||
it('onSuccess with challengeEmail calls resetPasswordChallenge', () => {
|
||||
forgotPasswordRequest({ userName: 'alice' } as any);
|
||||
const resp = { challengeEmail: true };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_ForgotPasswordRequest.ext': resp });
|
||||
expect(SessionPersistence.resetPasswordChallenge).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onSuccess without challengeEmail calls resetPassword', () => {
|
||||
forgotPasswordRequest({ userName: 'alice' } as any);
|
||||
const resp = { challengeEmail: false };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_ForgotPasswordRequest.ext': resp });
|
||||
expect(SessionPersistence.resetPassword).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onError calls resetPasswordFailed and disconnect', () => {
|
||||
forgotPasswordRequest({ userName: 'alice' } as any);
|
||||
invokeOnError();
|
||||
expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// forgotPasswordReset.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('forgotPasswordReset', () => {
|
||||
const { forgotPasswordReset } = jest.requireActual('./forgotPasswordReset');
|
||||
|
||||
it('sends Command_ForgotPasswordReset with plain newPassword when no salt', () => {
|
||||
forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_ForgotPasswordReset',
|
||||
expect.objectContaining({ newPassword: 'newpw' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('sends hashed new password when salt provided', () => {
|
||||
forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any, 'salt');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_ForgotPasswordReset',
|
||||
expect.objectContaining({ hashedNewPassword: 'hashed_pw' }),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('onSuccess calls resetPasswordSuccess and disconnect', () => {
|
||||
forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any);
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.resetPasswordSuccess).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onError calls resetPasswordFailed and disconnect', () => {
|
||||
forgotPasswordReset({ userName: 'alice', token: 'tok', newPassword: 'newpw' } as any);
|
||||
invokeOnError();
|
||||
expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// requestPasswordSalt.ts
|
||||
// ----------------------------------------------------------------
|
||||
describe('requestPasswordSalt', () => {
|
||||
const { requestPasswordSalt } = jest.requireActual('./requestPasswordSalt');
|
||||
|
||||
it('sends Command_RequestPasswordSalt', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RequestPasswordSalt', expect.any(Object), expect.any(Object));
|
||||
});
|
||||
|
||||
it('onSuccess with LOGIN reason calls login', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any);
|
||||
const resp = { passwordSalt: 'salt123' };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp });
|
||||
expect(SessionIndexMocks.login).toHaveBeenCalledWith(expect.any(Object), 'salt123');
|
||||
});
|
||||
|
||||
it('onSuccess with ACTIVATE_ACCOUNT reason calls activate', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any);
|
||||
const resp = { passwordSalt: 'salt123' };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp });
|
||||
expect(SessionIndexMocks.activate).toHaveBeenCalledWith(expect.any(Object), 'salt123');
|
||||
});
|
||||
|
||||
it('onSuccess with PASSWORD_RESET reason calls forgotPasswordReset', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any);
|
||||
const resp = { passwordSalt: 'salt123' };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_PasswordSalt.ext': resp });
|
||||
expect(SessionIndexMocks.forgotPasswordReset).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespRegistrationRequired calls updateStatus and disconnect', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any);
|
||||
invokeResponseCode(6);
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.any(String));
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onResponseCode RespRegistrationRequired with ACTIVATE_ACCOUNT calls accountActivationFailed', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.ACTIVATE_ACCOUNT } as any);
|
||||
invokeResponseCode(6);
|
||||
expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onError calls updateStatus DISCONNECTED and disconnect', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.LOGIN } as any);
|
||||
invokeOnError();
|
||||
expect(SessionIndexMocks.updateStatus).toHaveBeenCalled();
|
||||
expect(SessionIndexMocks.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onError with PASSWORD_RESET reason calls resetPasswordFailed', () => {
|
||||
requestPasswordSalt({ userName: 'alice', reason: WebSocketConnectReason.PASSWORD_RESET } as any);
|
||||
invokeOnError();
|
||||
expect(SessionPersistence.resetPasswordFailed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
// Shared mock setup for session command tests
|
||||
|
||||
jest.mock('../../services/BackendService', () => ({
|
||||
BackendService: {
|
||||
sendSessionCommand: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../persistence', () => {
|
||||
const { makeSessionPersistenceMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return {
|
||||
SessionPersistence: makeSessionPersistenceMock(),
|
||||
RoomPersistence: { joinRoom: jest.fn() },
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../WebClient', () => {
|
||||
const { makeWebClientMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return { __esModule: true, default: makeWebClientMock() };
|
||||
});
|
||||
|
||||
jest.mock('../../services/ProtoController', () => {
|
||||
const { makeProtoControllerRootMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return { ProtoController: { root: makeProtoControllerRootMock() } };
|
||||
});
|
||||
|
||||
jest.mock('../../utils', () => {
|
||||
const { makeUtilsMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return makeUtilsMock();
|
||||
});
|
||||
|
||||
// Mock session commands barrel to allow cross-command calls while keeping real implementations
|
||||
jest.mock('./', () => {
|
||||
const actual = jest.requireActual('./');
|
||||
const { makeSessionBarrelMock } = require('../../__mocks__/sessionCommandMocks');
|
||||
return { ...actual, ...makeSessionBarrelMock() };
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers(
|
||||
BackendService.sendSessionCommand as jest.Mock
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(hashPassword as jest.Mock).mockReturnValue('hashed_pw');
|
||||
(generateSalt as jest.Mock).mockReturnValue('randSalt');
|
||||
(passwordSaltSupported as jest.Mock).mockReturnValue(0);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
describe('accountEdit', () => {
|
||||
const { accountEdit } = jest.requireActual('./accountEdit');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_AccountEdit with correct params', () => {
|
||||
accountEdit('pw', 'Alice', 'a@b.com', 'US');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_AccountEdit',
|
||||
{ passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' },
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('calls SessionPersistence.accountEditChanged on success', () => {
|
||||
accountEdit('pw', 'Alice', 'a@b.com', 'US');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.accountEditChanged).toHaveBeenCalledWith('Alice', 'a@b.com', 'US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('accountImage', () => {
|
||||
const { accountImage } = jest.requireActual('./accountImage');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_AccountImage', () => {
|
||||
const img = new Uint8Array([1, 2]);
|
||||
accountImage(img);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AccountImage', { image: img }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls SessionPersistence.accountImageChanged on success', () => {
|
||||
const img = new Uint8Array([1, 2]);
|
||||
accountImage(img);
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.accountImageChanged).toHaveBeenCalledWith(img);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accountPassword', () => {
|
||||
const { accountPassword } = jest.requireActual('./accountPassword');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_AccountPassword', () => {
|
||||
accountPassword('old', 'new', 'hashed');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_AccountPassword',
|
||||
{ oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' },
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('calls SessionPersistence.accountPasswordChange on success', () => {
|
||||
accountPassword('old', 'new', 'hashed');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.accountPasswordChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckDel', () => {
|
||||
const { deckDel } = jest.requireActual('./deckDel');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_DeckDel', () => {
|
||||
deckDel(42);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckDel', { deckId: 42 }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls deleteServerDeck on success', () => {
|
||||
deckDel(42);
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.deleteServerDeck).toHaveBeenCalledWith(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckDelDir', () => {
|
||||
const { deckDelDir } = jest.requireActual('./deckDelDir');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_DeckDelDir', () => {
|
||||
deckDelDir('/path');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckDelDir', { path: '/path' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls deleteServerDeckDir on success', () => {
|
||||
deckDelDir('/path');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.deleteServerDeckDir).toHaveBeenCalledWith('/path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckList', () => {
|
||||
const { deckList } = jest.requireActual('./deckList');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_DeckList', () => {
|
||||
deckList();
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckList', {}, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls updateServerDecks on success', () => {
|
||||
deckList();
|
||||
const resp = { folders: [] };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_DeckList.ext': resp });
|
||||
expect(SessionPersistence.updateServerDecks).toHaveBeenCalledWith(resp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckNewDir', () => {
|
||||
const { deckNewDir } = jest.requireActual('./deckNewDir');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_DeckNewDir', () => {
|
||||
deckNewDir('/path', 'dir');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_DeckNewDir', { path: '/path', dirName: 'dir' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls createServerDeckDir on success', () => {
|
||||
deckNewDir('/path', 'dir');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.createServerDeckDir).toHaveBeenCalledWith('/path', 'dir');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckUpload', () => {
|
||||
const { deckUpload } = jest.requireActual('./deckUpload');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_DeckUpload', () => {
|
||||
deckUpload('/path', 1, 'content');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
|
||||
'Command_DeckUpload',
|
||||
{ path: '/path', deckId: 1, deckList: 'content' },
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('calls uploadServerDeck on success', () => {
|
||||
deckUpload('/path', 1, 'content');
|
||||
const resp = { newFile: { id: 1 } };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_DeckUpload.ext': resp });
|
||||
expect(SessionPersistence.uploadServerDeck).toHaveBeenCalledWith('/path', resp.newFile);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disconnect', () => {
|
||||
const { disconnect } = jest.requireActual('./disconnect');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('calls webClient.disconnect', () => {
|
||||
disconnect();
|
||||
expect(webClient.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGamesOfUser', () => {
|
||||
const { getGamesOfUser } = jest.requireActual('./getGamesOfUser');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_GetGamesOfUser', () => {
|
||||
getGamesOfUser('alice');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_GetGamesOfUser', { userName: 'alice' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls getGamesOfUser on success', () => {
|
||||
getGamesOfUser('alice');
|
||||
const resp = { gameList: [] };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_GetGamesOfUser.ext': resp });
|
||||
expect(SessionPersistence.getGamesOfUser).toHaveBeenCalledWith('alice', resp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserInfo', () => {
|
||||
const { getUserInfo } = jest.requireActual('./getUserInfo');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_GetUserInfo', () => {
|
||||
getUserInfo('alice');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_GetUserInfo', { userName: 'alice' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls getUserInfo on success', () => {
|
||||
getUserInfo('alice');
|
||||
const resp = { userInfo: { name: 'alice' } };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_GetUserInfo.ext': resp });
|
||||
expect(SessionPersistence.getUserInfo).toHaveBeenCalledWith(resp.userInfo);
|
||||
});
|
||||
});
|
||||
|
||||
describe('joinRoom', () => {
|
||||
const { joinRoom } = jest.requireActual('./joinRoom');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_JoinRoom', () => {
|
||||
joinRoom(5);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_JoinRoom', { roomId: 5 }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls RoomPersistence.joinRoom on success', () => {
|
||||
joinRoom(5);
|
||||
const resp = { roomInfo: { roomId: 5 } };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_JoinRoom.ext': resp });
|
||||
expect(RoomPersistence.joinRoom).toHaveBeenCalledWith(resp.roomInfo);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listRooms (command)', () => {
|
||||
const { listRooms } = jest.requireActual('./listRooms');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_ListRooms', () => {
|
||||
listRooms();
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ListRooms', {}, {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listUsers', () => {
|
||||
const { listUsers } = jest.requireActual('./listUsers');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_ListUsers', () => {
|
||||
listUsers();
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ListUsers', {}, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls SessionPersistence.updateUsers with the user list on success', () => {
|
||||
listUsers();
|
||||
const resp = { userList: [{ name: 'Alice' }] };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_ListUsers.ext': resp });
|
||||
expect(SessionPersistence.updateUsers).toHaveBeenCalledWith([{ name: 'Alice' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message', () => {
|
||||
const { message } = jest.requireActual('./message');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_Message', () => {
|
||||
message('bob', 'hi');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Message', { userName: 'bob', message: 'hi' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls directMessageSent on success', () => {
|
||||
message('bob', 'hi');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.directMessageSent).toHaveBeenCalledWith('bob', 'hi');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ping', () => {
|
||||
const { ping } = jest.requireActual('./ping');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_Ping', () => {
|
||||
const pingReceived = jest.fn();
|
||||
ping(pingReceived);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_Ping', {}, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls pingReceived via onResponse', () => {
|
||||
const pingReceived = jest.fn();
|
||||
ping(pingReceived);
|
||||
const raw = {};
|
||||
invokeCallback('onResponse', raw);
|
||||
expect(pingReceived).toHaveBeenCalledWith(raw);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replayDeleteMatch', () => {
|
||||
const { replayDeleteMatch } = jest.requireActual('./replayDeleteMatch');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_ReplayDeleteMatch', () => {
|
||||
replayDeleteMatch(7);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayDeleteMatch', { gameId: 7 }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls replayDeleteMatch on success', () => {
|
||||
replayDeleteMatch(7);
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.replayDeleteMatch).toHaveBeenCalledWith(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replayList', () => {
|
||||
const { replayList } = jest.requireActual('./replayList');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_ReplayList', () => {
|
||||
replayList();
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayList', {}, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls replayList on success', () => {
|
||||
replayList();
|
||||
const resp = { matchList: [] };
|
||||
invokeOnSuccess(resp, { responseCode: 0, '.Response_ReplayList.ext': resp });
|
||||
expect(SessionPersistence.replayList).toHaveBeenCalledWith([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replayModifyMatch', () => {
|
||||
const { replayModifyMatch } = jest.requireActual('./replayModifyMatch');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('sends Command_ReplayModifyMatch', () => {
|
||||
replayModifyMatch(7, true);
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_ReplayModifyMatch', { gameId: 7, doNotHide: true }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('calls replayModifyMatch on success', () => {
|
||||
replayModifyMatch(7, true);
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.replayModifyMatch).toHaveBeenCalledWith(7, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addToList / addToBuddyList / addToIgnoreList', () => {
|
||||
const { addToList, addToBuddyList, addToIgnoreList } = jest.requireActual('./addToList');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('addToBuddyList sends Command_AddToList with list=buddy', () => {
|
||||
addToBuddyList('alice');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AddToList', { list: 'buddy', userName: 'alice' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('addToIgnoreList sends Command_AddToList with list=ignore', () => {
|
||||
addToIgnoreList('bob');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_AddToList', { list: 'ignore', userName: 'bob' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('onSuccess calls SessionPersistence.addToList', () => {
|
||||
addToList('buddy', 'alice');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.addToList).toHaveBeenCalledWith('buddy', 'alice');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => {
|
||||
const { removeFromList, removeFromBuddyList, removeFromIgnoreList } = jest.requireActual('./removeFromList');
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => {
|
||||
removeFromBuddyList('alice');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RemoveFromList', { list: 'buddy', userName: 'alice' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => {
|
||||
removeFromIgnoreList('bob');
|
||||
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith('Command_RemoveFromList', { list: 'ignore', userName: 'bob' }, expect.any(Object));
|
||||
});
|
||||
|
||||
it('onSuccess calls SessionPersistence.removeFromList', () => {
|
||||
removeFromList('buddy', 'alice');
|
||||
invokeOnSuccess();
|
||||
expect(SessionPersistence.removeFromList).toHaveBeenCalledWith('buddy', 'alice');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue