import { Builder, ByteBuffer } from 'flatbuffers'; import { describe, expect, it } from 'vitest'; import * as fb from '../gen/fbs/scrabblefb'; import { decodeFriendList, decodeGameList, decodeInvitation, decodeLinkResult, decodeSession, decodeStats, encodeSubmitPlay, encodeTarget, } from './codec'; describe('codec', () => { it('encodes a SubmitPlayRequest the gateway can read', () => { const buf = encodeSubmitPlay('g1', 'H', [ { row: 7, col: 7, letter: 'A', blank: false }, { row: 7, col: 8, letter: 'B', blank: true }, ]); const r = fb.SubmitPlayRequest.getRootAsSubmitPlayRequest(new ByteBuffer(buf)); expect(r.gameId()).toBe('g1'); expect(r.dir()).toBe('H'); expect(r.tilesLength()).toBe(2); expect(r.tiles(0)?.letter()).toBe('A'); expect(r.tiles(1)?.blank()).toBe(true); }); it('decodes a Session', () => { const b = new Builder(64); const token = b.createString('tok'); const uid = b.createString('u1'); const name = b.createString('Me'); fb.Session.startSession(b); fb.Session.addToken(b, token); fb.Session.addUserId(b, uid); fb.Session.addIsGuest(b, true); fb.Session.addDisplayName(b, name); b.finish(fb.Session.endSession(b)); expect(decodeSession(b.asUint8Array())).toEqual({ token: 'tok', userId: 'u1', isGuest: true, displayName: 'Me', }); }); it('decodes a GameList including nested seat display names', () => { const b = new Builder(256); const aid = b.createString('a1'); const dn = b.createString('Ann'); fb.SeatView.startSeatView(b); fb.SeatView.addSeat(b, 1); fb.SeatView.addAccountId(b, aid); fb.SeatView.addScore(b, 13); fb.SeatView.addHintsUsed(b, 0); fb.SeatView.addIsWinner(b, false); fb.SeatView.addDisplayName(b, dn); const seat = fb.SeatView.endSeatView(b); const seats = fb.GameView.createSeatsVector(b, [seat]); const id = b.createString('g1'); const variant = b.createString('english'); const dv = b.createString('v1'); const status = b.createString('active'); const er = b.createString(''); fb.GameView.startGameView(b); fb.GameView.addId(b, id); fb.GameView.addVariant(b, variant); fb.GameView.addDictVersion(b, dv); fb.GameView.addStatus(b, status); fb.GameView.addPlayers(b, 2); fb.GameView.addToMove(b, 0); fb.GameView.addTurnTimeoutSecs(b, 86400); fb.GameView.addMoveCount(b, 4); fb.GameView.addEndReason(b, er); fb.GameView.addSeats(b, seats); const game = fb.GameView.endGameView(b); const games = fb.GameList.createGamesVector(b, [game]); fb.GameList.startGameList(b); fb.GameList.addGames(b, games); b.finish(fb.GameList.endGameList(b)); const gl = decodeGameList(b.asUint8Array()); expect(gl.games).toHaveLength(1); expect(gl.games[0].id).toBe('g1'); expect(gl.games[0].seats[0].displayName).toBe('Ann'); expect(gl.games[0].seats[0].score).toBe(13); }); it('encodes a TargetRequest', () => { const r = fb.TargetRequest.getRootAsTargetRequest(new ByteBuffer(encodeTarget('a-1'))); expect(r.accountId()).toBe('a-1'); }); it('decodes a StatsView', () => { const b = new Builder(64); fb.StatsView.startStatsView(b); fb.StatsView.addWins(b, 7); fb.StatsView.addLosses(b, 4); fb.StatsView.addDraws(b, 1); fb.StatsView.addMaxGamePoints(b, 420); fb.StatsView.addMaxWordPoints(b, 90); b.finish(fb.StatsView.endStatsView(b)); expect(decodeStats(b.asUint8Array())).toEqual({ wins: 7, losses: 4, draws: 1, maxGamePoints: 420, maxWordPoints: 90, }); }); it('decodes a FriendList of account refs', () => { const b = new Builder(128); const id = b.createString('a-1'); const dn = b.createString('Ann'); fb.AccountRef.startAccountRef(b); fb.AccountRef.addAccountId(b, id); fb.AccountRef.addDisplayName(b, dn); const ref = fb.AccountRef.endAccountRef(b); const vec = fb.FriendList.createFriendsVector(b, [ref]); fb.FriendList.startFriendList(b); fb.FriendList.addFriends(b, vec); b.finish(fb.FriendList.endFriendList(b)); expect(decodeFriendList(b.asUint8Array())).toEqual([{ accountId: 'a-1', displayName: 'Ann' }]); }); it('decodes a merge_required LinkResult without a session', () => { const b = new Builder(128); const status = b.createString('merge_required'); const sid = b.createString('b-1'); const sname = b.createString('Ann'); fb.LinkResult.startLinkResult(b); fb.LinkResult.addStatus(b, status); fb.LinkResult.addSecondaryUserId(b, sid); fb.LinkResult.addSecondaryDisplayName(b, sname); fb.LinkResult.addSecondaryGames(b, 7); fb.LinkResult.addSecondaryFriends(b, 3); b.finish(fb.LinkResult.endLinkResult(b)); expect(decodeLinkResult(b.asUint8Array())).toEqual({ status: 'merge_required', secondaryUserId: 'b-1', secondaryDisplayName: 'Ann', secondaryGames: 7, secondaryFriends: 3, session: null, }); }); it('decodes a merged LinkResult carrying a switched session', () => { const b = new Builder(128); const token = b.createString('tok-9'); const uid = b.createString('a-1'); const dn = b.createString('Kaya'); fb.Session.startSession(b); fb.Session.addToken(b, token); fb.Session.addUserId(b, uid); fb.Session.addIsGuest(b, false); fb.Session.addDisplayName(b, dn); const sess = fb.Session.endSession(b); const status = b.createString('merged'); fb.LinkResult.startLinkResult(b); fb.LinkResult.addStatus(b, status); fb.LinkResult.addSession(b, sess); b.finish(fb.LinkResult.endLinkResult(b)); const r = decodeLinkResult(b.asUint8Array()); expect(r.status).toBe('merged'); expect(r.session).toEqual({ token: 'tok-9', userId: 'a-1', isGuest: false, displayName: 'Kaya' }); }); it('decodes an Invitation with inviter and invitees', () => { const b = new Builder(256); const iid = b.createString('u-1'); const idn = b.createString('Me'); fb.AccountRef.startAccountRef(b); fb.AccountRef.addAccountId(b, iid); fb.AccountRef.addDisplayName(b, idn); const inviter = fb.AccountRef.endAccountRef(b); const aid = b.createString('inv-1'); const adn = b.createString('Friend'); const resp = b.createString('pending'); fb.InvitationInvitee.startInvitationInvitee(b); fb.InvitationInvitee.addAccountId(b, aid); fb.InvitationInvitee.addDisplayName(b, adn); fb.InvitationInvitee.addSeat(b, 1); fb.InvitationInvitee.addResponse(b, resp); const invitee = fb.InvitationInvitee.endInvitationInvitee(b); const invitees = fb.Invitation.createInviteesVector(b, [invitee]); const id = b.createString('i-1'); const variant = b.createString('english'); const dropout = b.createString('remove'); const status = b.createString('pending'); const gid = b.createString(''); fb.Invitation.startInvitation(b); fb.Invitation.addId(b, id); fb.Invitation.addInviter(b, inviter); fb.Invitation.addInvitees(b, invitees); fb.Invitation.addVariant(b, variant); fb.Invitation.addTurnTimeoutSecs(b, 86400); fb.Invitation.addHintsAllowed(b, true); fb.Invitation.addHintsPerPlayer(b, 1); fb.Invitation.addDropoutTiles(b, dropout); fb.Invitation.addStatus(b, status); fb.Invitation.addGameId(b, gid); b.finish(fb.Invitation.endInvitation(b)); const inv = decodeInvitation(b.asUint8Array()); expect(inv.id).toBe('i-1'); expect(inv.inviter).toEqual({ accountId: 'u-1', displayName: 'Me' }); expect(inv.invitees).toHaveLength(1); expect(inv.invitees[0]).toEqual({ accountId: 'inv-1', displayName: 'Friend', seat: 1, response: 'pending' }); expect(inv.variant).toBe('english'); }); });