111 lines
4.3 KiB
TypeScript
111 lines
4.3 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import {
|
|
contractBoardResponseSchema,
|
|
dispatchResponseSchema,
|
|
errorResponseSchema,
|
|
} from "@deadhead/schema";
|
|
import { MS_PER_HOUR, travelTimeGameHours } from "@deadhead/sim";
|
|
import { describe, expect, it } from "vitest";
|
|
import { useTestDatabase } from "./db";
|
|
import { call, getState, signUp, useTestApp, type Guest } from "./game";
|
|
|
|
const database = useTestDatabase();
|
|
const testApp = useTestApp(database);
|
|
|
|
async function tutorialBoard(guest: Guest) {
|
|
const response = await call(testApp().app, guest, "GET", "/api/tutorial/board");
|
|
return contractBoardResponseSchema.parse(response.json()).contracts;
|
|
}
|
|
|
|
function flyTutorial(guest: Guest, contractId: string) {
|
|
return call(testApp().app, guest, "POST", "/api/dispatches", { kind: "tutorial", contractId });
|
|
}
|
|
|
|
describe("tutorial dispatch", () => {
|
|
it("dispatches the avatar in the loaner, charging no fuel", async () => {
|
|
const { app, clock } = testApp();
|
|
const guest = await signUp(app);
|
|
const [contract] = await tutorialBoard(guest);
|
|
|
|
const response = await flyTutorial(guest, contract!.id);
|
|
expect(response.statusCode).toBe(200);
|
|
const { dispatch } = dispatchResponseSchema.parse(response.json());
|
|
|
|
expect(dispatch).toMatchObject({
|
|
tutorial: true,
|
|
status: "in-flight",
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
pilotId: guest.avatarId,
|
|
fuelCost: 0,
|
|
departureAt: clock.now,
|
|
expectedPayout: contract!.payout,
|
|
});
|
|
const travelMs =
|
|
(travelTimeGameHours(
|
|
content.shipModel("company-courier")!,
|
|
dispatch.distanceMillionKm,
|
|
contract!.massTonnes,
|
|
content.config,
|
|
) *
|
|
MS_PER_HOUR) /
|
|
content.config.timeCompression;
|
|
expect(dispatch.arrivalAt - dispatch.departureAt).toBe(Math.round(travelMs));
|
|
|
|
const state = await getState(app, guest);
|
|
expect(state.company.credits).toBe(content.config.startingCredits);
|
|
expect(state.tutorial).toEqual({ loanerLocationId: "earth", runInFlight: true });
|
|
expect(state.pilots[0]).toMatchObject({ status: "in-flight", locationId: null });
|
|
expect(state.activeDispatches.map((d) => d.id)).toEqual([dispatch.id]);
|
|
expect((await tutorialBoard(guest)).map((c) => c.id)).not.toContain(contract!.id);
|
|
});
|
|
|
|
it("rejects a second tutorial run while one is in flight", async () => {
|
|
const { app } = testApp();
|
|
const guest = await signUp(app);
|
|
const [first, second] = await tutorialBoard(guest);
|
|
await flyTutorial(guest, first!.id);
|
|
|
|
const response = await flyTutorial(guest, second!.id);
|
|
expect(response.statusCode).toBe(409);
|
|
expect(errorResponseSchema.parse(response.json()).error.code).toBe("tutorial-run-in-flight");
|
|
});
|
|
|
|
it("pays exactly the tutorial share on arrival and moves the loaner and avatar to the Moon", async () => {
|
|
const { app, clock } = testApp();
|
|
const guest = await signUp(app);
|
|
const [contract] = await tutorialBoard(guest);
|
|
const { dispatch } = dispatchResponseSchema.parse(
|
|
(await flyTutorial(guest, contract!.id)).json(),
|
|
);
|
|
|
|
clock.now = dispatch.arrivalAt;
|
|
const state = await getState(app, guest);
|
|
expect(state.company.credits).toBe(content.config.startingCredits + contract!.payout);
|
|
expect(state.pilots[0]!.xp).toBeGreaterThan(0);
|
|
expect(state.pilots[0]).toMatchObject({ status: "idle", locationId: "moon" });
|
|
expect(state.tutorial).toEqual({ loanerLocationId: "moon", runInFlight: false });
|
|
|
|
const next = await tutorialBoard(guest);
|
|
expect(next.length).toBeGreaterThan(0);
|
|
for (const c of next) expect([c.originId, c.destinationId]).toEqual(["moon", "earth"]);
|
|
});
|
|
|
|
it("rejects unknown and already-taken contracts", async () => {
|
|
const { app, clock } = testApp();
|
|
const guest = await signUp(app);
|
|
const [contract] = await tutorialBoard(guest);
|
|
|
|
const unknown = await flyTutorial(guest, "tutorial:earth:0:999");
|
|
expect(unknown.statusCode).toBe(404);
|
|
expect(errorResponseSchema.parse(unknown.json()).error.code).toBe("unknown-contract");
|
|
|
|
const { dispatch } = dispatchResponseSchema.parse(
|
|
(await flyTutorial(guest, contract!.id)).json(),
|
|
);
|
|
clock.now = dispatch.arrivalAt;
|
|
const again = await flyTutorial(guest, contract!.id);
|
|
expect(errorResponseSchema.parse(again.json()).error.code).toBe("contract-already-accepted");
|
|
});
|
|
});
|