deadhead-space-freight-tycoon/apps/server/test/end-tutorial.test.ts

109 lines
3.8 KiB
TypeScript

import { content } from "@deadhead/content";
import {
contractBoardResponseSchema,
dispatchResponseSchema,
errorResponseSchema,
} from "@deadhead/schema";
import { eq } from "drizzle-orm";
import { describe, expect, it } from "vitest";
import { pilots, players } from "../src/db/schema";
import { setCredits, useTestDatabase } from "./db";
import { call, getState, signUp, useTestApp, type Guest } from "./game";
const database = useTestDatabase();
const testApp = useTestApp(database);
async function buyShipAndEarthBay(guest: Guest) {
const { app } = testApp();
expect((await call(app, guest, "POST", "/api/ships", { modelId: "courier" })).statusCode).toBe(
200,
);
expect((await call(app, guest, "POST", "/api/bays", { locationId: "earth" })).statusCode).toBe(
200,
);
}
describe("end of tutorial", () => {
it("ends when the player owns a ship and an Earth bay, moving the avatar to Earth unassigned", async () => {
const { app } = testApp();
const { db } = database();
const guest = await signUp(app);
await setCredits(db, guest.playerId, 1_000_000);
// Put the loaner and avatar at the Moon.
await db
.update(players)
.set({ loanerLocationId: "moon" })
.where(eq(players.id, guest.playerId));
await db.update(pilots).set({ locationId: "moon" }).where(eq(pilots.id, guest.avatarId));
await call(app, guest, "POST", "/api/ships", { modelId: "courier" });
expect((await getState(app, guest)).tutorial).not.toBeNull();
await call(app, guest, "POST", "/api/bays", { locationId: "earth" });
const state = await getState(app, guest);
expect(state.tutorial).toBeNull();
expect(state.pilots[0]).toMatchObject({
isAvatar: true,
status: "idle",
locationId: "earth",
assignedShipId: null,
});
const board = await call(app, guest, "GET", "/api/tutorial/board");
expect(board.statusCode).toBe(409);
expect(errorResponseSchema.parse(board.json()).error.code).toBe("not-in-tutorial");
});
it("waits for an in-flight tutorial run to land before ending", async () => {
const { app, clock } = testApp();
const guest = await signUp(app);
await setCredits(database().db, guest.playerId, 1_000_000);
const [contract] = contractBoardResponseSchema.parse(
(await call(app, guest, "GET", "/api/tutorial/board")).json(),
).contracts;
const { dispatch } = dispatchResponseSchema.parse(
(
await call(app, guest, "POST", "/api/dispatches", {
kind: "tutorial",
contractId: contract!.id,
})
).json(),
);
await buyShipAndEarthBay(guest);
expect((await getState(app, guest)).tutorial).toMatchObject({
loanerLocationId: "earth",
runInFlight: true,
});
clock.now = dispatch.arrivalAt;
const state = await getState(app, guest);
expect(state.tutorial).toBeNull();
// The run still paid out, and the avatar was brought back to Earth.
expect(state.company.credits).toBe(
1_000_000 -
content.shipModel("courier")!.price -
content.location("earth")!.bayLeasePrice +
contract!.payout,
);
expect(state.pilots[0]).toMatchObject({
locationId: "earth",
status: "idle",
assignedShipId: null,
});
});
it("does not end with only a ship, or only an Earth bay", async () => {
const { app } = testApp();
const shipOnly = await signUp(app);
await setCredits(database().db, shipOnly.playerId, 1_000_000);
await call(app, shipOnly, "POST", "/api/ships", { modelId: "courier" });
expect((await getState(app, shipOnly)).tutorial).not.toBeNull();
const bayOnly = await signUp(app);
await setCredits(database().db, bayOnly.playerId, 1_000_000);
await call(app, bayOnly, "POST", "/api/bays", { locationId: "earth" });
expect((await getState(app, bayOnly)).tutorial).not.toBeNull();
});
});