import assert from "node:assert/strict"; import test from "node:test"; import { purgeClosedBugReport } from "../lib/bug-retention"; import { LinearSupportClient } from "../lib/linear-support-client"; import type { BugReportRecord } from "../shared/bug-reports"; const id = "00000000-0000-4000-8000-000000000001", team = "00000000-0000-4000-8000-000000000002"; const closedAt = new Date(1000).toISOString(), due = 1000 + 3 * 86400000; const report = { id, state: "done", closed_at: closedAt, delete_after: new Date(due).toISOString(), linear_done_synced: true, } as BugReportRecord; test("report retention never purges early, open reports, or an older closure generation", async () => { for (const [record, now] of [ [report, due - 1], [{ ...report, state: "open" }, due], [{ ...report, closed_at: new Date(2000).toISOString() }, due], [null, due], ] as const) { await purgeClosedBugReport(id, closedAt, { record: async () => record as BugReportRecord | null, now: () => now, linear: { issue: async () => { throw new Error("Must not query Linear"); }, remove: async () => assert.fail(), }, begin: async () => { assert.fail(); }, reopen: async () => assert.fail(), removeFiles: async () => assert.fail(), finish: async () => assert.fail(), }); } }); test("reopening a Linear issue cancels local cleanup and completed reports purge in a retry-safe order", async () => { for (const state of ["started", "completed"]) { const actions: string[] = []; await purgeClosedBugReport(id, closedAt, { record: async () => report, now: () => due, linear: { issue: async () => ({ id, identifier: "TEST-1", team: { id: team }, state: { id: team, name: state, type: state }, }), remove: async () => { actions.push("linear"); }, }, begin: async () => { actions.push("lock"); return true; }, reopen: async () => { actions.push("reopen"); }, removeFiles: async (paths) => { assert.deepEqual(paths, [`${id}/input`, `${id}/screenshot.webp`]); actions.push("storage"); }, finish: async () => { actions.push("database"); }, }); assert.deepEqual( actions, state === "started" ? ["reopen"] : ["lock", "linear", "storage", "database"], ); } }); test("failed external deletion preserves the local retry record", async () => { for (const failure of ["linear", "storage"]) { let finished = false; await assert.rejects( purgeClosedBugReport(id, closedAt, { record: async () => ({ ...report, purge_started_at: closedAt }), now: () => due, linear: { issue: async () => assert.fail(), remove: async () => { if (failure === "linear") throw new Error("retry"); }, }, begin: async () => true, reopen: async () => assert.fail(), removeFiles: async () => { if (failure === "storage") throw new Error("retry"); }, finish: async () => { finished = true; }, }), /retry/, ); assert.equal(finished, false); } }); test("Linear Done uses the report team's Done state, validates acknowledgment and deletes idempotently", async () => { let current = "started", deleted = false; const calls: string[] = []; const client = new LinearSupportClient( "private", team, undefined, async (_url, init) => { const { query, variables } = JSON.parse(String(init?.body)); calls.push(query.split("(")[0]); if (query.startsWith("query SupportIssue")) return Response.json({ data: { issue: deleted ? null : { id, identifier: "TEST-1", team: { id: team }, state: { id: current, name: current === "done" ? "Done" : "In Progress", type: current === "done" ? "completed" : "started", }, }, }, }); if (query.startsWith("query SupportDoneState")) { assert.equal(variables.team, team); return Response.json({ data: { team: { states: { nodes: [{ id: "done", name: "Done", type: "completed" }], }, }, }, }); } if (query.startsWith("mutation SupportDone")) { assert.equal(variables.input.stateId, "done"); current = "done"; return Response.json({ data: { issueUpdate: { success: true, issue: { id, state: { id: "done" } }, }, }, }); } if (query.startsWith("mutation SupportDelete")) { deleted = true; return Response.json({ data: { issueDelete: { success: true } } }); } throw new Error("Unexpected request"); }, ); await client.done(id); await client.done(id); await client.remove(id); await client.remove(id); assert.equal(calls.filter((c) => c === "mutation SupportDone").length, 1); assert.equal(calls.filter((c) => c === "mutation SupportDelete").length, 1); const wrongTeam = new LinearSupportClient( "secret", team, undefined, async () => Response.json({ data: { issue: { id, team: { id: "other" } } } }), ); await assert.rejects(wrongTeam.remove(id), /team mismatch/); const failed = new LinearSupportClient( "secret", team, undefined, async (_url, init) => Response.json({ data: JSON.parse(String(init?.body)).query.startsWith("query") ? { issue: { id, team: { id: team } } } : { issueDelete: { success: false } }, }), ); await assert.rejects(failed.remove(id), /not confirmed/); });