deadhead-space-freight-tycoon/packages/engine/test/ported/cleanup.test.ts

90 lines
3.3 KiB
TypeScript

// Ported from apps/server/test (CR-014 Step 14.5): the hourly cleanup job is
// now pruning on every save (Step 7.3, CR-014 §2).
import { content } from "@deadhead/content";
import { boardPeriodIndex, boardPeriodMs } from "@deadhead/sim";
import { describe, expect, it } from "vitest";
import { pruneSave } from "../../src/index";
import {
TEST_NOW,
useTestApp,
signUp,
call,
getState,
edit,
readSave,
testContract,
insertDispatch,
putBoard,
makeOwner,
} from "../support";
const testApp = useTestApp();
const RETENTION = content.config.boardRetentionPeriods;
const HOUR = 3_600_000;
describe("pruning old boards and trips on save", () => {
it("drops old boards and accepted contracts and keeps current ones", async () => {
const { app } = testApp();
const guest = await signUp(app);
const { shipId } = await makeOwner(app, guest.avatarId);
const period = boardPeriodMs(content.config);
const current = boardPeriodIndex(TEST_NOW, content.config);
const old = current - RETENTION - 1;
await putBoard(app, [testContract()], TEST_NOW - (current - old) * period);
await putBoard(app, [testContract()], TEST_NOW);
const record = await insertDispatch(app, {
shipId,
pilotId: guest.avatarId,
departAt: TEST_NOW,
});
await edit(app, (save) => {
save.acceptedContracts.push(
{ contractId: "old", periodIndex: old, dispatchId: record.id, acceptedAt: TEST_NOW },
{ contractId: "new", periodIndex: current, dispatchId: record.id, acceptedAt: TEST_NOW },
);
});
const save = await readSave(app);
pruneSave(save, TEST_NOW);
expect(save.contractBoards.map((b) => b.periodIndex)).toEqual([current]);
expect(save.acceptedContracts.map((a) => a.contractId)).toEqual(["new"]);
});
it("keeps boards inside the retention window", async () => {
const { app } = testApp();
await signUp(app);
await putBoard(app, [testContract()], TEST_NOW - RETENTION * boardPeriodMs(content.config));
const save = await readSave(app);
pruneSave(save, TEST_NOW);
expect(save.contractBoards).toHaveLength(1);
});
it("drops seen trips after the retention, keeps unseen ones and in-flight ones, and keeps goal progress", async () => {
const { app, clock } = testApp();
const guest = await signUp(app);
const { shipId } = await makeOwner(app, guest.avatarId);
const record = await insertDispatch(app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
});
clock.now = record.arrivalAt;
const landed = await getState(app);
expect(landed.summaries).toHaveLength(1);
const progressBefore = landed.goals;
// Unseen: kept however old.
clock.now += content.config.savedDispatchRetentionRealHours * HOUR + HOUR;
await getState(app);
expect((await readSave(app)).dispatches).toHaveLength(1);
// Seen and old: gone on the next save, with goal progress unchanged.
await call(app, guest, "POST", "/api/summaries/acknowledge", { dispatchIds: [record.id] });
clock.now += content.config.savedDispatchRetentionRealHours * HOUR + HOUR;
await call(app, guest, "POST", "/api/test/add-credits", { amount: 1 });
expect((await readSave(app)).dispatches).toEqual([]);
expect((await readSave(app)).stats.contractDeliveries).toBe(1);
expect((await getState(app)).goals).toEqual(progressBefore);
});
});