// 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); }); });