109 lines
3.8 KiB
TypeScript
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).toEqual({
|
|
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();
|
|
});
|
|
});
|