import { content } from "@deadhead/content"; import { errorResponseSchema, shipResponseSchema } from "@deadhead/schema"; import { checkRoute, effectiveStats, gameToReal, realToGame, wouldStrand } from "@deadhead/sim"; import { eq } from "drizzle-orm"; import { describe, expect, it } from "vitest"; import { discoveredLocations, pilots, ships } from "../src/db/schema"; import { setCredits, TEST_NOW, useTestDatabase } from "./db"; import { call, getState, signUpOwner, useTestApp } from "./game"; const database = useTestDatabase(); const testApp = useTestApp(database); describe("Lighter and Clipper (Step 10L.6)", () => { for (const modelId of ["lighter", "clipper"]) { it(`buys a ${modelId} at the chosen HQ`, async () => { const { app } = testApp(); const owner = await signUpOwner(app, database().db); const price = content.shipModel(modelId)!.price; await setCredits(database().db, owner.playerId, price); const response = await call(app, owner, "POST", "/api/ships", { modelId, bayId: owner.earthBayId, }); expect(response.statusCode).toBe(200); const { ship } = shipResponseSchema.parse(response.json()); expect(ship).toMatchObject({ modelId, status: "idle", locationId: "earth" }); expect((await getState(app, owner)).company.credits).toBe(0); }); } it("refuses a deadhead that would strand the ship, changing nothing", async () => { const { app, clock } = testApp(); const owner = await signUpOwner(app, database().db); const db = database().db; await setCredits(db, owner.playerId, 100_000); // A Clipper at the Moon, 1,420 game days in: Mars is in range from the Moon // (198 × 1.3), but from Mars (198 × 1.15) nothing is when it gets there. const departure = realToGame(TEST_NOW, content.config) + 1420 * 24 * 3_600_000; clock.now = gameToReal(departure, content.config); await db .update(ships) .set({ modelId: "clipper", locationId: "moon" }) .where(eq(ships.id, owner.shipId)); await db.update(pilots).set({ locationId: "moon" }).where(eq(pilots.id, owner.avatarId)); await db .insert(discoveredLocations) .values({ playerId: owner.playerId, locationId: "mars", discoveredAt: TEST_NOW }); // The scenario as the sim sees it. const clipper = effectiveStats(content.shipModel("clipper")!, {}, content); const [moon, mars] = [content.location("moon")!, content.location("mars")!]; expect(checkRoute(clipper, moon, mars, realToGame(clock.now, content.config)).ok).toBe(true); expect( wouldStrand( clipper, moon, mars, 0, realToGame(clock.now, content.config), undefined, content, ), ).toBe(true); const before = await getState(app, owner); const response = await call(app, owner, "POST", "/api/dispatches", { kind: "reposition", shipId: owner.shipId, pilotId: owner.avatarId, destinationId: "mars", }); expect(response.statusCode).toBe(409); expect(errorResponseSchema.parse(response.json()).error.code).toBe("would-strand"); const after = await getState(app, owner); expect(after.company.credits).toBe(before.company.credits); expect(after.ships).toEqual(before.ships); expect(after.activeDispatches).toEqual([]); }); });