// Ported from apps/server/test (CR-014 Step 14.5): now runs on the engine. import { content } from "@deadhead/content"; import { contractBoardResponseSchema, errorResponseSchema } from "@deadhead/schema"; import { boardPeriodMs, validateDispatch } from "@deadhead/sim"; import { describe, expect, it } from "vitest"; import { TEST_NOW, useTestApp, call, edit, testContract, insertDispatch, signUpOwner, type Guest, } from "../support"; const testApp = useTestApp(); async function board(guest: Guest, locationId: string) { const response = await call(testApp().app, guest, "GET", `/api/locations/${locationId}/board`); expect(response.statusCode).toBe(200); return contractBoardResponseSchema.parse(response.json()); } describe("location contract boards", () => { it("returns the Moon's board for a player with no Moon bay", async () => { const owner = await signUpOwner(testApp().app); const { contracts } = await board(owner, "moon"); expect(contracts).toHaveLength(content.config.contractBoardSize); for (const c of contracts) expect(c.originId).toBe("moon"); }); it("returns the Earth board of a player with an Earth bay too", async () => { const owner = await signUpOwner(testApp().app); const { contracts, periodEndsAt } = await board(owner, "earth"); expect(contracts).toHaveLength(content.config.contractBoardSize); expect(periodEndsAt).toBeGreaterThan(testApp().clock.now); }); it("returns the same stored board within a period, even after a discovery", async () => { const { clock } = testApp(); const owner = await signUpOwner(testApp().app); const first = await board(owner, "moon"); await edit(testApp().app, (save) => { save.discoveredLocations.push({ locationId: "mars", discoveredAt: TEST_NOW }); }); clock.advance(boardPeriodMs(content.config) / 2 - 1); expect((await board(owner, "moon")).contracts).toEqual(first.contracts); }); it("returns a new board after the period rolls over", async () => { const { clock } = testApp(); const owner = await signUpOwner(testApp().app); const first = await board(owner, "earth"); clock.now = first.periodEndsAt; const next = await board(owner, "earth"); expect(next.periodEndsAt).toBe(first.periodEndsAt + boardPeriodMs(content.config)); expect(next.contracts.map((c) => c.id)).not.toEqual(first.contracts.map((c) => c.id)); }); it("refuses Mars before it is discovered, and opens it after a delivery discovers it", async () => { const { app, clock } = testApp(); const owner = await signUpOwner(app); const before = await call(app, owner, "GET", "/api/locations/mars/board"); expect(before.statusCode).toBe(409); expect(errorResponseSchema.parse(before.json()).error.code).toBe("location-undiscovered"); // A delivery to Mars (placed directly; the Courier's range is not the point here). const record = await insertDispatch(testApp().app, { shipId: owner.shipId, pilotId: owner.avatarId, departAt: clock.now, contract: testContract({ destinationId: "mars" }), }); clock.now = record.arrivalAt; const { contracts } = await board(owner, "mars"); for (const c of contracts) expect(c.originId).toBe("mars"); }); it("returns not-found for an unknown location", async () => { const owner = await signUpOwner(testApp().app); const response = await call(testApp().app, owner, "GET", "/api/locations/pluto/board"); expect(response.statusCode).toBe(404); }); it("fills a lone Courier's Earth board with jobs it can fly (CR-006)", async () => { const { app, clock } = testApp(); const owner = await signUpOwner(app); const { contracts } = await board(owner, "earth"); const flyable = contracts.filter( (contract) => validateDispatch( { ship: { id: owner.shipId, modelId: "courier", locationId: "earth", inFlight: false, tank: { fuelFu: content.shipModel("courier")!.tankCapacityFu, avgPrice: 16 }, }, pilot: { id: owner.avatarId, isAvatar: true, assignedShipId: owner.shipId, inFlight: false, skills: {}, restEndsAt: null, }, contract, tutorial: false, now: clock.now, }, content, ).ok, ); expect(contracts).toHaveLength(content.config.contractBoardSize); expect(flyable.length).toBeGreaterThanOrEqual(5); }); });