115 lines
4.4 KiB
TypeScript
115 lines
4.4 KiB
TypeScript
// 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 },
|
|
pilot: {
|
|
id: owner.avatarId,
|
|
isAvatar: true,
|
|
assignedShipId: owner.shipId,
|
|
inFlight: false,
|
|
skills: {},
|
|
restEndsAt: null,
|
|
},
|
|
contract,
|
|
tutorial: false,
|
|
credits: 1_000_000,
|
|
now: clock.now,
|
|
},
|
|
content,
|
|
).ok,
|
|
);
|
|
expect(contracts).toHaveLength(content.config.contractBoardSize);
|
|
expect(flyable.length).toBeGreaterThanOrEqual(5);
|
|
});
|
|
});
|