battle-viewer e2e: mock user.games.battle ConnectRPC command
Tests · UI / test (push) Has been cancelled
Tests · Integration / integration (pull_request) Successful in 1m46s
Tests · Go / test (pull_request) Successful in 2m0s
Tests · UI / test (pull_request) Successful in 2m23s

Phase 28 moved the battle fetch off the REST passthrough onto the
signed envelope, so the Playwright spec's `page.route(...)` against
the old REST path no longer intercepts anything and the viewer
times out waiting for data. Update the spec to:

- Build a FlatBuffers `BattleReport` payload in
  `fixtures/battle-fbs.ts` (mirrors `report-fbs.ts`'s pattern).
- Add a `user.games.battle` case to the ExecuteCommand mock that
  decodes the FBS `GameBattleRequest`, returns the encoded report
  when the battle_id matches the seeded one, and surfaces a
  canonical `not_found` resultCode otherwise.
- Drop the obsolete REST route stubs.
- Drive the negative-path test with a real UUID that does not match
  the seeded one, so the gateway-side switch is the source of the
  404 (the old `missing-uuid` literal was no longer a valid wire
  shape for the UUID decoder).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-16 12:55:15 +02:00
parent ebd156ece2
commit 166baf4be0
2 changed files with 164 additions and 27 deletions
+36 -27
View File
@@ -1,9 +1,9 @@
// Phase 27 — Playwright coverage for the Battle Viewer.
//
// Mocks both the Connect-RPC `user.games.report` (so the report
// renders battles + bombings) and the REST forwarder
// `/api/v1/user/games/{game_id}/battles/{turn}/{battle_id}` (so the
// viewer page loads its `BattleReport` without an engine).
// Mocks the Connect-RPC `user.games.report` (report with battles +
// bombings) and `user.games.battle` (Phase 28 migration; the viewer
// fetches the full BattleReport through the signed envelope instead
// of the old REST passthrough).
// Drives three flows:
// 1. Reports view → click battle UUID → viewer renders.
// 2. Playback controls: play / step back.
@@ -18,6 +18,7 @@ import { expect, test, type Page } from "@playwright/test";
import { ExecuteCommandRequestSchema } from "../../src/proto/galaxy/gateway/v1/edge_gateway_pb";
import { UUID } from "../../src/proto/galaxy/fbs/common";
import { GameReportRequest } from "../../src/proto/galaxy/fbs/report";
import { GameBattleRequest } from "../../src/proto/galaxy/fbs/battle";
import {
buildOrderResponsePayload,
@@ -25,6 +26,10 @@ import {
} from "./fixtures/order-fbs";
import { buildMyGamesListPayload, type GameFixture } from "./fixtures/lobby-fbs";
import { buildReportPayload } from "./fixtures/report-fbs";
import {
buildBattlePayload,
type BattleFixture,
} from "./fixtures/battle-fbs";
import { forgeExecuteCommandResponseJson } from "./fixtures/sign-response";
const GAME_ID = "00000000-0000-0000-0000-000000000010";
@@ -33,13 +38,14 @@ const SESSION_ID = "device-session-battle";
const RACE_A = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
const RACE_B = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb";
const SAMPLE_BATTLE = {
const SAMPLE_BATTLE: BattleFixture = {
id: BATTLE_ID,
planet: 1,
planetName: "Earth",
races: { "0": RACE_A, "1": RACE_B },
ships: {
"10": {
ships: [
{
key: 10,
race: "Earthlings",
className: "Cruiser",
tech: { WEAPONS: 1 },
@@ -49,7 +55,8 @@ const SAMPLE_BATTLE = {
loadQuantity: 0,
inBattle: true,
},
"20": {
{
key: 20,
race: "Bajori",
className: "Hawk",
tech: { SHIELDS: 1 },
@@ -59,7 +66,7 @@ const SAMPLE_BATTLE = {
loadQuantity: 0,
inBattle: true,
},
},
],
protocol: [
{ a: 0, sa: 10, d: 1, sd: 20, x: false },
{ a: 0, sa: 10, d: 1, sd: 20, x: true },
@@ -160,6 +167,23 @@ async function mockGatewayAndBattle(page: Page): Promise<void> {
case "user.games.order.get":
payload = buildOrderGetResponsePayload(GAME_ID, [], Date.now(), false);
break;
case "user.games.battle": {
const fb = GameBattleRequest.getRootAsGameBattleRequest(
new ByteBuffer(req.payloadBytes),
);
const id = fb.battleId();
if (
id !== null &&
id.hi() === BigInt("0x1111111111111111") &&
id.lo() === BigInt("0x1111111111111111")
) {
payload = buildBattlePayload(SAMPLE_BATTLE);
} else {
resultCode = "not_found";
payload = new Uint8Array();
}
break;
}
default:
resultCode = "internal_error";
payload = new Uint8Array();
@@ -179,23 +203,6 @@ async function mockGatewayAndBattle(page: Page): Promise<void> {
},
);
await page.route(
`**/api/v1/user/games/${GAME_ID}/battles/1/${BATTLE_ID}`,
async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(SAMPLE_BATTLE),
});
},
);
await page.route(
`**/api/v1/user/games/${GAME_ID}/battles/1/missing-uuid`,
async (route) => {
await route.fulfill({ status: 404 });
},
);
}
async function bootSession(page: Page): Promise<void> {
@@ -267,7 +274,9 @@ test.describe("Phase 27 battle viewer", () => {
await mockGatewayAndBattle(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/battle/missing-uuid?turn=1`);
await page.goto(
`/games/${GAME_ID}/battle/22222222-2222-2222-2222-222222222222?turn=1`,
);
await expect(page.getByTestId("battle-not-found")).toBeVisible();
});