deadhead-space-freight-tycoon/apps/server/test/goods-backfill.test.ts

97 lines
3.2 KiB
TypeScript

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<typeof testContract>[0] = {}) {
const { goodId, shipper, priority, deliveryWindowGameHours, ...old } = testContract(overrides);
void [goodId, shipper, priority, deliveryWindowGameHours];
return old as ReturnType<typeof testContract>;
}
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());
});
});