// Shared mock setup for session command tests vi.mock('../../WebClient', async () => { const { makeWebClientMock } = await import('../../__mocks__/sessionCommandMocks'); return { WebClient: { instance: makeWebClientMock() } }; }); vi.mock('../../utils', async () => { const { makeUtilsMock } = await import('../../__mocks__/sessionCommandMocks'); return makeUtilsMock(); }); // Mock session commands barrel to allow cross-command calls while keeping real implementations vi.mock('./', async () => { const actual = await vi.importActual('./'); const { makeSessionBarrelMock } = await import('../../__mocks__/sessionCommandMocks'); return { ...(actual as Record), ...makeSessionBarrelMock() }; }); import { Mock } from 'vitest'; import { makeCallbackHelpers } from '../../__mocks__/callbackHelpers'; import { WebClient } from '../../WebClient'; import { hashPassword, generateSalt, passwordSaltSupported } from '../../utils'; import { accountEdit } from './accountEdit'; import { accountImage } from './accountImage'; import { accountPassword } from './accountPassword'; import { deckDel } from './deckDel'; import { deckDelDir } from './deckDelDir'; import { deckList } from './deckList'; import { deckNewDir } from './deckNewDir'; import { deckUpload } from './deckUpload'; import { disconnect } from './disconnect'; import { getGamesOfUser } from './getGamesOfUser'; import { getUserInfo } from './getUserInfo'; import { joinRoom } from './joinRoom'; import { listRooms } from './listRooms'; import { listUsers } from './listUsers'; import { message } from './message'; import { ping } from './ping'; import { replayDeleteMatch } from './replayDeleteMatch'; import { replayList } from './replayList'; import { replayModifyMatch } from './replayModifyMatch'; import { addToList, addToBuddyList, addToIgnoreList } from './addToList'; import { removeFromList, removeFromBuddyList, removeFromIgnoreList } from './removeFromList'; import { replayGetCode } from './replayGetCode'; import { replaySubmitCode } from './replaySubmitCode'; import { Data } from '@app/types'; const { invokeOnSuccess, invokeCallback } = makeCallbackHelpers( WebClient.instance.protobuf.sendSessionCommand as Mock, 2 ); beforeEach(() => { (hashPassword as Mock).mockReturnValue('hashed_pw'); (generateSalt as Mock).mockReturnValue('randSalt'); (passwordSaltSupported as Mock).mockReturnValue(0); }); // ---------------------------------------------------------------- describe('accountEdit', () => { it('sends Command_AccountEdit with correct params', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_AccountEdit_ext, expect.objectContaining({ passwordCheck: 'pw', realName: 'Alice', email: 'a@b.com', country: 'US' }), expect.any(Object) ); }); it('calls WebClient.instance.response.session.accountEditChanged on success', () => { accountEdit('pw', 'Alice', 'a@b.com', 'US'); invokeOnSuccess(); expect(WebClient.instance.response.session.accountEditChanged).toHaveBeenCalledWith('Alice', 'a@b.com', 'US'); }); }); describe('accountImage', () => { it('sends Command_AccountImage', () => { const img = new Uint8Array([1, 2]); accountImage(img); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_AccountImage_ext, expect.objectContaining({ image: img }), expect.any(Object) ); }); it('calls WebClient.instance.response.session.accountImageChanged on success', () => { const img = new Uint8Array([1, 2]); accountImage(img); invokeOnSuccess(); expect(WebClient.instance.response.session.accountImageChanged).toHaveBeenCalledWith(img); }); }); describe('accountPassword', () => { it('sends Command_AccountPassword', () => { accountPassword('old', 'new', 'hashed'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_AccountPassword_ext, expect.objectContaining({ oldPassword: 'old', newPassword: 'new', hashedNewPassword: 'hashed' }), expect.any(Object) ); }); it('calls WebClient.instance.response.session.accountPasswordChange on success', () => { accountPassword('old', 'new', 'hashed'); invokeOnSuccess(); expect(WebClient.instance.response.session.accountPasswordChange).toHaveBeenCalled(); }); }); describe('deckDel', () => { it('sends Command_DeckDel', () => { deckDel(42); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_DeckDel_ext, expect.objectContaining({ deckId: 42 }), expect.any(Object) ); }); it('calls deleteServerDeck on success', () => { deckDel(42); invokeOnSuccess(); expect(WebClient.instance.response.session.deleteServerDeck).toHaveBeenCalledWith(42); }); }); describe('deckDelDir', () => { it('sends Command_DeckDelDir', () => { deckDelDir('/path'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_DeckDelDir_ext, expect.objectContaining({ path: '/path' }), expect.any(Object) ); }); it('calls deleteServerDeckDir on success', () => { deckDelDir('/path'); invokeOnSuccess(); expect(WebClient.instance.response.session.deleteServerDeckDir).toHaveBeenCalledWith('/path'); }); }); describe('deckList', () => { it('sends Command_DeckList', () => { deckList(); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_DeckList_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_DeckList_ext }) ); }); it('calls updateServerDecks on success', () => { deckList(); const root = { items: [] }; invokeOnSuccess({ root }, { responseCode: 0 }); expect(WebClient.instance.response.session.updateServerDecks).toHaveBeenCalledWith({ root }); }); }); describe('deckNewDir', () => { it('sends Command_DeckNewDir', () => { deckNewDir('/path', 'dir'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_DeckNewDir_ext, expect.objectContaining({ path: '/path', dirName: 'dir' }), expect.any(Object) ); }); it('calls createServerDeckDir on success', () => { deckNewDir('/path', 'dir'); invokeOnSuccess(); expect(WebClient.instance.response.session.createServerDeckDir).toHaveBeenCalledWith('/path', 'dir'); }); }); describe('deckUpload', () => { it('sends Command_DeckUpload', () => { deckUpload('/path', 1, 'content'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_DeckUpload_ext, expect.objectContaining({ path: '/path', deckId: 1, deckList: 'content' }), expect.objectContaining({ responseExt: Data.Response_DeckUpload_ext }) ); }); it('calls uploadServerDeck on success', () => { deckUpload('/path', 1, 'content'); const resp = { newFile: { id: 1 } }; invokeOnSuccess(resp, { responseCode: 0 }); expect(WebClient.instance.response.session.uploadServerDeck).toHaveBeenCalledWith('/path', resp.newFile); }); }); describe('disconnect', () => { it('calls WebClient.instance.disconnect', () => { disconnect(); expect(WebClient.instance.disconnect).toHaveBeenCalled(); }); }); describe('getGamesOfUser', () => { it('sends Command_GetGamesOfUser', () => { getGamesOfUser('alice'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_GetGamesOfUser_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_GetGamesOfUser_ext }) ); }); it('calls getGamesOfUser on success', () => { getGamesOfUser('alice'); const resp = { gameList: [] }; invokeOnSuccess(resp, { responseCode: 0 }); expect(WebClient.instance.response.session.getGamesOfUser).toHaveBeenCalledWith('alice', resp); }); }); describe('getUserInfo', () => { it('sends Command_GetUserInfo', () => { getUserInfo('alice'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_GetUserInfo_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_GetUserInfo_ext }) ); }); it('calls getUserInfo on success', () => { getUserInfo('alice'); const resp = { userInfo: { name: 'alice' } }; invokeOnSuccess(resp, { responseCode: 0 }); expect(WebClient.instance.response.session.getUserInfo).toHaveBeenCalledWith(resp.userInfo); }); }); describe('joinRoom', () => { it('sends Command_JoinRoom', () => { joinRoom(5); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_JoinRoom_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_JoinRoom_ext }) ); }); it('calls WebClient.instance.response.room.joinRoom on success', () => { joinRoom(5); const resp = { roomInfo: { roomId: 5 } }; invokeOnSuccess(resp, { responseCode: 0 }); expect(WebClient.instance.response.room.joinRoom).toHaveBeenCalledWith(resp.roomInfo); }); }); describe('listRooms (command)', () => { it('sends Command_ListRooms', () => { listRooms(); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith(Data.Command_ListRooms_ext, expect.any(Object)); }); }); describe('listUsers', () => { it('sends Command_ListUsers', () => { listUsers(); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_ListUsers_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_ListUsers_ext }) ); }); it('calls WebClient.instance.response.session.updateUsers with the user list on success', () => { listUsers(); const resp = { userList: [{ name: 'Alice' }] }; invokeOnSuccess(resp, { responseCode: 0 }); expect(WebClient.instance.response.session.updateUsers).toHaveBeenCalledWith([{ name: 'Alice' }]); }); }); describe('message', () => { it('sends Command_Message', () => { message('bob', 'hi'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_Message_ext, expect.objectContaining({ userName: 'bob', message: 'hi' }) ); }); }); describe('ping', () => { it('sends Command_Ping', () => { const pingReceived = vi.fn(); ping(pingReceived); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_Ping_ext, expect.any(Object), expect.any(Object) ); }); it('calls pingReceived via onResponse', () => { const pingReceived = vi.fn(); ping(pingReceived); invokeCallback('onResponse', {}); expect(pingReceived).toHaveBeenCalled(); }); }); describe('replayDeleteMatch', () => { it('sends Command_ReplayDeleteMatch', () => { replayDeleteMatch(7); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_ReplayDeleteMatch_ext, expect.objectContaining({ gameId: 7 }), expect.any(Object) ); }); it('calls replayDeleteMatch on success', () => { replayDeleteMatch(7); invokeOnSuccess(); expect(WebClient.instance.response.session.replayDeleteMatch).toHaveBeenCalledWith(7); }); }); describe('replayList', () => { it('sends Command_ReplayList', () => { replayList(); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_ReplayList_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_ReplayList_ext }) ); }); it('calls replayList on success', () => { replayList(); const resp = { matchList: [] }; invokeOnSuccess(resp, { responseCode: 0 }); expect(WebClient.instance.response.session.replayList).toHaveBeenCalledWith([]); }); }); describe('replayModifyMatch', () => { it('sends Command_ReplayModifyMatch', () => { replayModifyMatch(7, true); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_ReplayModifyMatch_ext, expect.objectContaining({ gameId: 7, doNotHide: true }), expect.any(Object) ); }); it('calls replayModifyMatch on success', () => { replayModifyMatch(7, true); invokeOnSuccess(); expect(WebClient.instance.response.session.replayModifyMatch).toHaveBeenCalledWith(7, true); }); }); describe('addToList / addToBuddyList / addToIgnoreList', () => { it('addToBuddyList sends Command_AddToList with list=buddy', () => { addToBuddyList('alice'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_AddToList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) ); }); it('addToIgnoreList sends Command_AddToList with list=ignore', () => { addToIgnoreList('bob'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_AddToList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) ); }); it('onSuccess calls WebClient.instance.response.session.addToList', () => { addToList('buddy', 'alice'); invokeOnSuccess(); expect(WebClient.instance.response.session.addToList).toHaveBeenCalledWith('buddy', 'alice'); }); }); describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => { it('removeFromBuddyList sends Command_RemoveFromList with list=buddy', () => { removeFromBuddyList('alice'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_RemoveFromList_ext, expect.objectContaining({ list: 'buddy' }), expect.any(Object) ); }); it('removeFromIgnoreList sends Command_RemoveFromList with list=ignore', () => { removeFromIgnoreList('bob'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_RemoveFromList_ext, expect.objectContaining({ list: 'ignore' }), expect.any(Object) ); }); it('onSuccess calls WebClient.instance.response.session.removeFromList', () => { removeFromList('buddy', 'alice'); invokeOnSuccess(); expect(WebClient.instance.response.session.removeFromList).toHaveBeenCalledWith('buddy', 'alice'); }); }); describe('replayGetCode', () => { it('sends Command_ReplayGetCode with gameId and responseExt', () => { replayGetCode(42, vi.fn()); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_ReplayGetCode_ext, expect.any(Object), expect.objectContaining({ responseExt: Data.Response_ReplayGetCode_ext }) ); }); it('calls onCodeReceived with replayCode on success', () => { const onCodeReceived = vi.fn(); replayGetCode(42, onCodeReceived); invokeOnSuccess({ replayCode: 'abc123-xyz' }); expect(onCodeReceived).toHaveBeenCalledWith('abc123-xyz'); }); }); describe('replaySubmitCode', () => { it('sends Command_ReplaySubmitCode with replayCode', () => { replaySubmitCode('42-abc123'); expect(WebClient.instance.protobuf.sendSessionCommand).toHaveBeenCalledWith( Data.Command_ReplaySubmitCode_ext, expect.objectContaining({ replayCode: '42-abc123' }), expect.any(Object) ); }); it('forwards onSuccess callback', () => { const onSuccess = vi.fn(); replaySubmitCode('42-abc123', onSuccess); invokeOnSuccess(); expect(onSuccess).toHaveBeenCalled(); }); it('forwards onError callback', () => { const onError = vi.fn(); replaySubmitCode('42-abc123', undefined, onError); invokeCallback('onError', 404); expect(onError).toHaveBeenCalledWith(404); }); });