// Vitest unit coverage for `sync/order-load.ts`. Builds FBS // `UserGamesOrderGetResponse` payloads by hand and verifies the // decoder produces the expected `OrderCommand[]`. import { Builder } from "flatbuffers"; import { describe, expect, test, vi } from "vitest"; import type { GalaxyClient } from "../src/api/galaxy-client"; import { uuidToHiLo } from "../src/api/game-state"; import { UUID } from "../src/proto/galaxy/fbs/common"; import { CommandItem, CommandPayload, CommandPlanetProduce, CommandPlanetRename, CommandPlanetRouteRemove, CommandPlanetRouteSet, PlanetProduction, PlanetRouteLoadType, UserGamesOrder, UserGamesOrderGet, UserGamesOrderGetResponse, } from "../src/proto/galaxy/fbs/order"; import { fetchOrder, OrderLoadError } from "../src/sync/order-load"; const GAME_ID = "11111111-2222-3333-4444-555555555555"; function mockClient( executeCommand: ( messageType: string, payload: Uint8Array, ) => Promise<{ resultCode: string; payloadBytes: Uint8Array }>, ): GalaxyClient { return { executeCommand } as unknown as GalaxyClient; } function buildResponse( commands: { id: string; planetNumber: number; name: string }[], updatedAt: number, found = true, ): Uint8Array { const builder = new Builder(256); let orderOffset = 0; if (found) { const itemOffsets = commands.map((c) => { const cmdIdOffset = builder.createString(c.id); const nameOffset = builder.createString(c.name); const inner = CommandPlanetRename.createCommandPlanetRename( builder, BigInt(c.planetNumber), nameOffset, ); CommandItem.startCommandItem(builder); CommandItem.addCmdId(builder, cmdIdOffset); CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRename); CommandItem.addPayload(builder, inner); return CommandItem.endCommandItem(builder); }); const commandsVec = UserGamesOrder.createCommandsVector(builder, itemOffsets); const [hi, lo] = uuidToHiLo(GAME_ID); const gameIdOffset = UUID.createUUID(builder, hi, lo); UserGamesOrder.startUserGamesOrder(builder); UserGamesOrder.addGameId(builder, gameIdOffset); UserGamesOrder.addUpdatedAt(builder, BigInt(updatedAt)); UserGamesOrder.addCommands(builder, commandsVec); orderOffset = UserGamesOrder.endUserGamesOrder(builder); } UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder); UserGamesOrderGetResponse.addFound(builder, found); if (orderOffset !== 0) { UserGamesOrderGetResponse.addOrder(builder, orderOffset); } const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse(builder); builder.finish(offset); return builder.asUint8Array(); } describe("fetchOrder", () => { test("decodes a found response into typed commands", async () => { const responsePayload = buildResponse( [{ id: "cmd-1", planetNumber: 7, name: "Earth" }], 42, ); const exec = vi.fn(async (messageType: string) => { expect(messageType).toBe("user.games.order.get"); return { resultCode: "ok", payloadBytes: responsePayload }; }); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toHaveLength(1); const cmd = result.commands[0]!; expect(cmd.kind).toBe("planetRename"); if (cmd.kind !== "planetRename") return; expect(cmd.id).toBe("cmd-1"); expect(cmd.planetNumber).toBe(7); expect(cmd.name).toBe("Earth"); expect(result.updatedAt).toBe(42); }); test("found=false surfaces as an empty draft", async () => { const responsePayload = buildResponse([], 0, false); const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: responsePayload, })); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toEqual([]); expect(result.updatedAt).toBe(0); }); test("rejects negative turn before issuing a request", async () => { const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: new Uint8Array(), })); await expect(fetchOrder(mockClient(exec), GAME_ID, -1)).rejects.toBeInstanceOf( OrderLoadError, ); expect(exec).not.toHaveBeenCalled(); }); test("throws OrderLoadError on non-ok resultCode", async () => { const exec = vi.fn(async () => ({ resultCode: "internal_error", payloadBytes: new TextEncoder().encode( JSON.stringify({ code: "boom", message: "down" }), ), })); await expect(fetchOrder(mockClient(exec), GAME_ID, 5)).rejects.toMatchObject({ name: "OrderLoadError", resultCode: "internal_error", code: "boom", }); }); test("decodes a CommandPlanetProduce envelope into setProductionType", async () => { const builder = new Builder(256); const cmdIdOffset = builder.createString("cmd-prod"); const subjectOffset = builder.createString("Scout"); const inner = CommandPlanetProduce.createCommandPlanetProduce( builder, BigInt(17), PlanetProduction.SHIP, subjectOffset, ); CommandItem.startCommandItem(builder); CommandItem.addCmdId(builder, cmdIdOffset); CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetProduce); CommandItem.addPayload(builder, inner); const item = CommandItem.endCommandItem(builder); const commandsVec = UserGamesOrder.createCommandsVector(builder, [item]); const [hi, lo] = uuidToHiLo(GAME_ID); const gameIdOffset = UUID.createUUID(builder, hi, lo); UserGamesOrder.startUserGamesOrder(builder); UserGamesOrder.addGameId(builder, gameIdOffset); UserGamesOrder.addUpdatedAt(builder, BigInt(13)); UserGamesOrder.addCommands(builder, commandsVec); const orderOffset = UserGamesOrder.endUserGamesOrder(builder); UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder); UserGamesOrderGetResponse.addFound(builder, true); UserGamesOrderGetResponse.addOrder(builder, orderOffset); const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse( builder, ); builder.finish(offset); const responsePayload = builder.asUint8Array(); const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: responsePayload, })); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toHaveLength(1); const cmd = result.commands[0]!; expect(cmd.kind).toBe("setProductionType"); if (cmd.kind !== "setProductionType") return; expect(cmd.id).toBe("cmd-prod"); expect(cmd.planetNumber).toBe(17); expect(cmd.productionType).toBe("SHIP"); expect(cmd.subject).toBe("Scout"); expect(result.updatedAt).toBe(13); }); test("skips a CommandPlanetProduce with PlanetProduction.UNKNOWN", async () => { const builder = new Builder(256); const cmdIdOffset = builder.createString("cmd-unknown"); const subjectOffset = builder.createString(""); const inner = CommandPlanetProduce.createCommandPlanetProduce( builder, BigInt(0), PlanetProduction.UNKNOWN, subjectOffset, ); CommandItem.startCommandItem(builder); CommandItem.addCmdId(builder, cmdIdOffset); CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetProduce); CommandItem.addPayload(builder, inner); const item = CommandItem.endCommandItem(builder); const commandsVec = UserGamesOrder.createCommandsVector(builder, [item]); const [hi, lo] = uuidToHiLo(GAME_ID); const gameIdOffset = UUID.createUUID(builder, hi, lo); UserGamesOrder.startUserGamesOrder(builder); UserGamesOrder.addGameId(builder, gameIdOffset); UserGamesOrder.addUpdatedAt(builder, BigInt(0)); UserGamesOrder.addCommands(builder, commandsVec); const orderOffset = UserGamesOrder.endUserGamesOrder(builder); UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder); UserGamesOrderGetResponse.addFound(builder, true); UserGamesOrderGetResponse.addOrder(builder, orderOffset); const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse( builder, ); builder.finish(offset); const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: builder.asUint8Array(), })); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toEqual([]); }); test("decodes CommandPlanetRouteSet into setCargoRoute", async () => { const builder = new Builder(256); const cmdIdOffset = builder.createString("cmd-route-set"); const inner = CommandPlanetRouteSet.createCommandPlanetRouteSet( builder, BigInt(11), BigInt(22), PlanetRouteLoadType.MAT, ); CommandItem.startCommandItem(builder); CommandItem.addCmdId(builder, cmdIdOffset); CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRouteSet); CommandItem.addPayload(builder, inner); const item = CommandItem.endCommandItem(builder); const commandsVec = UserGamesOrder.createCommandsVector(builder, [item]); const [hi, lo] = uuidToHiLo(GAME_ID); const gameIdOffset = UUID.createUUID(builder, hi, lo); UserGamesOrder.startUserGamesOrder(builder); UserGamesOrder.addGameId(builder, gameIdOffset); UserGamesOrder.addUpdatedAt(builder, BigInt(7)); UserGamesOrder.addCommands(builder, commandsVec); const orderOffset = UserGamesOrder.endUserGamesOrder(builder); UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder); UserGamesOrderGetResponse.addFound(builder, true); UserGamesOrderGetResponse.addOrder(builder, orderOffset); const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse( builder, ); builder.finish(offset); const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: builder.asUint8Array(), })); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toHaveLength(1); const cmd = result.commands[0]!; expect(cmd.kind).toBe("setCargoRoute"); if (cmd.kind !== "setCargoRoute") return; expect(cmd.id).toBe("cmd-route-set"); expect(cmd.sourcePlanetNumber).toBe(11); expect(cmd.destinationPlanetNumber).toBe(22); expect(cmd.loadType).toBe("MAT"); }); test("decodes CommandPlanetRouteRemove into removeCargoRoute", async () => { const builder = new Builder(256); const cmdIdOffset = builder.createString("cmd-route-remove"); const inner = CommandPlanetRouteRemove.createCommandPlanetRouteRemove( builder, BigInt(33), PlanetRouteLoadType.EMP, ); CommandItem.startCommandItem(builder); CommandItem.addCmdId(builder, cmdIdOffset); CommandItem.addPayloadType( builder, CommandPayload.CommandPlanetRouteRemove, ); CommandItem.addPayload(builder, inner); const item = CommandItem.endCommandItem(builder); const commandsVec = UserGamesOrder.createCommandsVector(builder, [item]); const [hi, lo] = uuidToHiLo(GAME_ID); const gameIdOffset = UUID.createUUID(builder, hi, lo); UserGamesOrder.startUserGamesOrder(builder); UserGamesOrder.addGameId(builder, gameIdOffset); UserGamesOrder.addUpdatedAt(builder, BigInt(8)); UserGamesOrder.addCommands(builder, commandsVec); const orderOffset = UserGamesOrder.endUserGamesOrder(builder); UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder); UserGamesOrderGetResponse.addFound(builder, true); UserGamesOrderGetResponse.addOrder(builder, orderOffset); const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse( builder, ); builder.finish(offset); const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: builder.asUint8Array(), })); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toHaveLength(1); const cmd = result.commands[0]!; expect(cmd.kind).toBe("removeCargoRoute"); if (cmd.kind !== "removeCargoRoute") return; expect(cmd.sourcePlanetNumber).toBe(33); expect(cmd.loadType).toBe("EMP"); }); test("skips a CommandPlanetRouteSet with PlanetRouteLoadType.UNKNOWN", async () => { const builder = new Builder(256); const cmdIdOffset = builder.createString("cmd-unknown-load"); const inner = CommandPlanetRouteSet.createCommandPlanetRouteSet( builder, BigInt(1), BigInt(2), PlanetRouteLoadType.UNKNOWN, ); CommandItem.startCommandItem(builder); CommandItem.addCmdId(builder, cmdIdOffset); CommandItem.addPayloadType(builder, CommandPayload.CommandPlanetRouteSet); CommandItem.addPayload(builder, inner); const item = CommandItem.endCommandItem(builder); const commandsVec = UserGamesOrder.createCommandsVector(builder, [item]); const [hi, lo] = uuidToHiLo(GAME_ID); const gameIdOffset = UUID.createUUID(builder, hi, lo); UserGamesOrder.startUserGamesOrder(builder); UserGamesOrder.addGameId(builder, gameIdOffset); UserGamesOrder.addUpdatedAt(builder, BigInt(0)); UserGamesOrder.addCommands(builder, commandsVec); const orderOffset = UserGamesOrder.endUserGamesOrder(builder); UserGamesOrderGetResponse.startUserGamesOrderGetResponse(builder); UserGamesOrderGetResponse.addFound(builder, true); UserGamesOrderGetResponse.addOrder(builder, orderOffset); const offset = UserGamesOrderGetResponse.endUserGamesOrderGetResponse( builder, ); builder.finish(offset); const exec = vi.fn(async () => ({ resultCode: "ok", payloadBytes: builder.asUint8Array(), })); const result = await fetchOrder(mockClient(exec), GAME_ID, 5); expect(result.commands).toEqual([]); }); test("posts a well-formed UserGamesOrderGet payload", async () => { let captured: Uint8Array | null = null; const exec = vi.fn(async (_messageType, payload: Uint8Array) => { captured = payload; return { resultCode: "ok", payloadBytes: buildResponse([], 0, false), }; }); await fetchOrder(mockClient(exec), GAME_ID, 9); expect(captured).not.toBeNull(); const decoded = UserGamesOrderGet.getRootAsUserGamesOrderGet( new (await import("flatbuffers")).ByteBuffer(captured!), ); expect(Number(decoded.turn())).toBe(9); const id = decoded.gameId(); expect(id).not.toBeNull(); }); });