import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { contractBoardResponseSchema } from "@deadhead/schema"; import { eq } from "drizzle-orm"; import pg from "pg"; import { describe, expect, it } from "vitest"; import { contractBoards, dispatches } from "../src/db/schema"; import { useTestDatabase } from "./db"; import { call, getState, insertDispatch, putBoard, signUpOwner, testContract, useTestApp, } from "./game"; import { testConfig } from "./helpers"; const database = useTestDatabase(); const testApp = useTestApp(database); const migration = readFileSync( join(dirname(fileURLToPath(import.meta.url)), "../drizzle/0005_cr007_goods.sql"), "utf8", ); /** Runs the CR-007 backfill in one session (it uses a pg_temp helper). */ async function backfill() { const client = new pg.Client({ connectionString: testConfig().databaseUrl }); await client.connect(); try { for (const statement of migration.split("--> statement-breakpoint")) { await client.query(statement); } } finally { await client.end(); } } /** A contract as stored before CR-007: no good, shipper or Priority fields. */ function legacy(overrides: Parameters[0] = {}) { const { goodId, shipper, priority, deliveryWindowGameHours, ...old } = testContract(overrides); void [goodId, shipper, priority, deliveryWindowGameHours]; return old as ReturnType; } describe("CR-007 backfill migration", () => { it("gives stored boards and in-flight contracts a good, shipper and no Priority, once", async () => { const { app, clock } = testApp(); const owner = await signUpOwner(app, database().db); const db = database().db; await putBoard( db, owner.playerId, [ legacy({ id: "earth:1:0", cargoTypeId: "standard" }), legacy({ id: "earth:1:1", cargoTypeId: "high-value", originId: "moon" }), ], clock.now, ); const record = await insertDispatch(db, { playerId: owner.playerId, shipId: owner.shipId, pilotId: owner.avatarId, departAt: clock.now, contract: legacy({ cargoTypeId: "dangerous" }), }); await backfill(); const [board] = await db.select().from(contractBoards); expect(board!.contracts.map((c) => [c.goodId, c.shipper, c.priority])).toEqual([ ["consumer-goods", "Pacific Orbital Logistics", false], ["electronics", "Shackleton Mining Co.", false], ]); const [row] = await db.select().from(dispatches).where(eq(dispatches.id, record.id)); expect(row!.record.contract).toMatchObject({ goodId: "industrial-chemicals", priority: false, deliveryWindowGameHours: null, }); // A second run changes nothing. await backfill(); const [again] = await db.select().from(contractBoards); expect(again!.contracts).toEqual(board!.contracts); // The API accepts the backfilled rows. const state = await getState(app, owner); expect(state.activeDispatches[0]!.contract!.goodId).toBe("industrial-chemicals"); const response = await call(app, owner, "GET", "/api/locations/earth/board"); expect(response.statusCode).toBe(200); contractBoardResponseSchema.parse(response.json()); }); });