// Helpers that build FlatBuffers payloads for the lobby Playwright // suite. Mirrors what `pkg/transcoder/lobby.go` produces in production, // so the forged response goes through the same TS decoder the lobby // page uses. import { Builder } from "flatbuffers"; import { AccountResponse, AccountView, EntitlementSnapshot, } from "../../../src/proto/galaxy/fbs/user"; import { ApplicationSubmitResponse, ApplicationSummary, GameCreateResponse, GameSummary, InviteDeclineResponse, InviteRedeemResponse, InviteSummary, MyApplicationsListResponse, MyGamesListResponse, MyInvitesListResponse, PublicGamesListResponse, } from "../../../src/proto/galaxy/fbs/lobby"; export interface GameFixture { gameId: string; gameName: string; gameType: string; status: string; ownerUserId?: string; minPlayers?: number; maxPlayers?: number; enrollmentEndsAtMs?: bigint; createdAtMs?: bigint; updatedAtMs?: bigint; } export interface ApplicationFixture { applicationId: string; gameId: string; applicantUserId: string; raceName: string; status: string; createdAtMs?: bigint; decidedAtMs?: bigint; } export interface InviteFixture { inviteId: string; gameId: string; inviterUserId: string; invitedUserId?: string; code?: string; raceName: string; status: string; createdAtMs?: bigint; expiresAtMs?: bigint; decidedAtMs?: bigint; } const DEFAULT_TIME_MS = 1_780_000_000_000n; function encodeGame(builder: Builder, game: GameFixture): number { const gameId = builder.createString(game.gameId); const gameName = builder.createString(game.gameName); const gameType = builder.createString(game.gameType); const status = builder.createString(game.status); const ownerUserId = builder.createString(game.ownerUserId ?? ""); GameSummary.startGameSummary(builder); GameSummary.addGameId(builder, gameId); GameSummary.addGameName(builder, gameName); GameSummary.addGameType(builder, gameType); GameSummary.addStatus(builder, status); GameSummary.addOwnerUserId(builder, ownerUserId); GameSummary.addMinPlayers(builder, game.minPlayers ?? 2); GameSummary.addMaxPlayers(builder, game.maxPlayers ?? 8); GameSummary.addEnrollmentEndsAtMs(builder, game.enrollmentEndsAtMs ?? DEFAULT_TIME_MS); GameSummary.addCreatedAtMs(builder, game.createdAtMs ?? DEFAULT_TIME_MS); GameSummary.addUpdatedAtMs(builder, game.updatedAtMs ?? DEFAULT_TIME_MS); return GameSummary.endGameSummary(builder); } function encodeApplication(builder: Builder, app: ApplicationFixture): number { const applicationId = builder.createString(app.applicationId); const gameId = builder.createString(app.gameId); const applicantUserId = builder.createString(app.applicantUserId); const raceName = builder.createString(app.raceName); const status = builder.createString(app.status); ApplicationSummary.startApplicationSummary(builder); ApplicationSummary.addApplicationId(builder, applicationId); ApplicationSummary.addGameId(builder, gameId); ApplicationSummary.addApplicantUserId(builder, applicantUserId); ApplicationSummary.addRaceName(builder, raceName); ApplicationSummary.addStatus(builder, status); ApplicationSummary.addCreatedAtMs(builder, app.createdAtMs ?? DEFAULT_TIME_MS); ApplicationSummary.addDecidedAtMs(builder, app.decidedAtMs ?? 0n); return ApplicationSummary.endApplicationSummary(builder); } function encodeInvite(builder: Builder, invite: InviteFixture): number { const inviteId = builder.createString(invite.inviteId); const gameId = builder.createString(invite.gameId); const inviterUserId = builder.createString(invite.inviterUserId); const invitedUserId = builder.createString(invite.invitedUserId ?? ""); const code = builder.createString(invite.code ?? ""); const raceName = builder.createString(invite.raceName); const status = builder.createString(invite.status); InviteSummary.startInviteSummary(builder); InviteSummary.addInviteId(builder, inviteId); InviteSummary.addGameId(builder, gameId); InviteSummary.addInviterUserId(builder, inviterUserId); InviteSummary.addInvitedUserId(builder, invitedUserId); InviteSummary.addCode(builder, code); InviteSummary.addRaceName(builder, raceName); InviteSummary.addStatus(builder, status); InviteSummary.addCreatedAtMs(builder, invite.createdAtMs ?? DEFAULT_TIME_MS); InviteSummary.addExpiresAtMs(builder, invite.expiresAtMs ?? DEFAULT_TIME_MS); InviteSummary.addDecidedAtMs(builder, invite.decidedAtMs ?? 0n); return InviteSummary.endInviteSummary(builder); } export function buildMyGamesListPayload(games: GameFixture[]): Uint8Array { const builder = new Builder(256); const offsets = games.map((g) => encodeGame(builder, g)); const items = MyGamesListResponse.createItemsVector(builder, offsets); MyGamesListResponse.startMyGamesListResponse(builder); MyGamesListResponse.addItems(builder, items); builder.finish(MyGamesListResponse.endMyGamesListResponse(builder)); return builder.asUint8Array(); } export function buildPublicGamesListPayload( games: GameFixture[], page = 1, pageSize = 50, ): Uint8Array { const builder = new Builder(256); const offsets = games.map((g) => encodeGame(builder, g)); const items = PublicGamesListResponse.createItemsVector(builder, offsets); PublicGamesListResponse.startPublicGamesListResponse(builder); PublicGamesListResponse.addItems(builder, items); PublicGamesListResponse.addPage(builder, page); PublicGamesListResponse.addPageSize(builder, pageSize); PublicGamesListResponse.addTotal(builder, games.length); builder.finish(PublicGamesListResponse.endPublicGamesListResponse(builder)); return builder.asUint8Array(); } export function buildMyApplicationsListPayload( applications: ApplicationFixture[], ): Uint8Array { const builder = new Builder(256); const offsets = applications.map((a) => encodeApplication(builder, a)); const items = MyApplicationsListResponse.createItemsVector(builder, offsets); MyApplicationsListResponse.startMyApplicationsListResponse(builder); MyApplicationsListResponse.addItems(builder, items); builder.finish(MyApplicationsListResponse.endMyApplicationsListResponse(builder)); return builder.asUint8Array(); } export function buildMyInvitesListPayload(invites: InviteFixture[]): Uint8Array { const builder = new Builder(256); const offsets = invites.map((i) => encodeInvite(builder, i)); const items = MyInvitesListResponse.createItemsVector(builder, offsets); MyInvitesListResponse.startMyInvitesListResponse(builder); MyInvitesListResponse.addItems(builder, items); builder.finish(MyInvitesListResponse.endMyInvitesListResponse(builder)); return builder.asUint8Array(); } export function buildGameCreateResponsePayload(game: GameFixture): Uint8Array { const builder = new Builder(256); const summary = encodeGame(builder, game); GameCreateResponse.startGameCreateResponse(builder); GameCreateResponse.addGame(builder, summary); builder.finish(GameCreateResponse.endGameCreateResponse(builder)); return builder.asUint8Array(); } export function buildApplicationSubmitResponsePayload( application: ApplicationFixture, ): Uint8Array { const builder = new Builder(128); const app = encodeApplication(builder, application); ApplicationSubmitResponse.startApplicationSubmitResponse(builder); ApplicationSubmitResponse.addApplication(builder, app); builder.finish(ApplicationSubmitResponse.endApplicationSubmitResponse(builder)); return builder.asUint8Array(); } export function buildInviteRedeemResponsePayload(invite: InviteFixture): Uint8Array { const builder = new Builder(128); const summary = encodeInvite(builder, invite); InviteRedeemResponse.startInviteRedeemResponse(builder); InviteRedeemResponse.addInvite(builder, summary); builder.finish(InviteRedeemResponse.endInviteRedeemResponse(builder)); return builder.asUint8Array(); } export function buildInviteDeclineResponsePayload(invite: InviteFixture): Uint8Array { const builder = new Builder(128); const summary = encodeInvite(builder, invite); InviteDeclineResponse.startInviteDeclineResponse(builder); InviteDeclineResponse.addInvite(builder, summary); builder.finish(InviteDeclineResponse.endInviteDeclineResponse(builder)); return builder.asUint8Array(); } export interface AccountFixture { userId: string; email: string; userName: string; displayName: string; } export function buildAccountResponsePayload(account: AccountFixture): Uint8Array { const builder = new Builder(256); const planCode = builder.createString("free"); const source = builder.createString("internal"); const reasonCode = builder.createString(""); EntitlementSnapshot.startEntitlementSnapshot(builder); EntitlementSnapshot.addPlanCode(builder, planCode); EntitlementSnapshot.addIsPaid(builder, false); EntitlementSnapshot.addSource(builder, source); EntitlementSnapshot.addReasonCode(builder, reasonCode); EntitlementSnapshot.addStartsAtMs(builder, 0n); EntitlementSnapshot.addEndsAtMs(builder, 0n); EntitlementSnapshot.addUpdatedAtMs(builder, 0n); const entitlement = EntitlementSnapshot.endEntitlementSnapshot(builder); const userId = builder.createString(account.userId); const email = builder.createString(account.email); const userName = builder.createString(account.userName); const displayName = builder.createString(account.displayName); const preferredLanguage = builder.createString("en"); const timeZone = builder.createString("UTC"); const declaredCountry = builder.createString(""); AccountView.startAccountView(builder); AccountView.addUserId(builder, userId); AccountView.addEmail(builder, email); AccountView.addUserName(builder, userName); AccountView.addDisplayName(builder, displayName); AccountView.addPreferredLanguage(builder, preferredLanguage); AccountView.addTimeZone(builder, timeZone); AccountView.addDeclaredCountry(builder, declaredCountry); AccountView.addEntitlement(builder, entitlement); AccountView.addCreatedAtMs(builder, 0n); AccountView.addUpdatedAtMs(builder, 0n); const view = AccountView.endAccountView(builder); AccountResponse.startAccountResponse(builder); AccountResponse.addAccount(builder, view); builder.finish(AccountResponse.endAccountResponse(builder)); return builder.asUint8Array(); }