| 1 | import { describe, expect, it } from "vitest"; |
| 2 | import { COMPUTE_RETENTION_DAYS, NOTIFICATION_RETENTION_DAYS, pruneExpired } from "../../src/lib/retention.js"; |
| 3 | import { testD1 } from "./stubs/d1.js"; |
| 4 | |
| 5 | describe("retention", () => { |
| 6 | it("deletes expired sessions, old notifications and old compute records, and keeps the rest", async () => { |
| 7 | const DB = testD1(); |
| 8 | const at = new Date("2026-10-14T10:00:00Z"); |
| 9 | const ago = (d: number) => new Date(at.getTime() - d * 86400_000).toISOString(); |
| 10 | DB.sqlite.prepare("INSERT INTO accounts (id, login, kind, status, created_at) VALUES ('u', 'u', 'user', 'active', ?)").run(ago(1000)); |
| 11 | const session = DB.sqlite.prepare("INSERT INTO sessions (id, account_id, method, created_at, last_seen_at, expires_at) VALUES (?, 'u', 'passkey', ?, ?, ?)"); |
| 12 | session.run("expired", ago(40), ago(10), ago(2)); |
| 13 | session.run("just-expired", ago(40), ago(1), ago(0.5)); |
| 14 | session.run("live", ago(1), ago(0), ago(-29)); |
| 15 | const note = DB.sqlite.prepare("INSERT INTO notifications (id, account_id, kind, title, url, created_at) VALUES (?, 'u', 'issues', 't', 'u', ?)"); |
| 16 | note.run("old", ago(NOTIFICATION_RETENTION_DAYS + 1)); |
| 17 | note.run("recent", ago(NOTIFICATION_RETENTION_DAYS - 1)); |
| 18 | const usage = DB.sqlite.prepare("INSERT INTO compute_usage (id, account_id, repo_id, kind, started_at, ended_at) VALUES (?, 'u', 'r', 'job', ?, ?)"); |
| 19 | usage.run("old-job", ago(COMPUTE_RETENTION_DAYS + 2), ago(COMPUTE_RETENTION_DAYS + 1)); |
| 20 | usage.run("recent-job", ago(30), ago(30)); |
| 21 | usage.run("running", ago(0), null); |
| 22 | expect(await pruneExpired(DB, at)).toEqual({ sessions: 1, notifications: 1, compute_usage: 1, early_access: 0, early_access_attempts: 0 }); |
| 23 | const ids = (t: string) => (DB.sqlite.prepare(`SELECT id FROM ${t} ORDER BY id`).all() as Array<{ id: string }>).map((r) => r.id); |
| 24 | expect(ids("sessions")).toEqual(["just-expired", "live"]); |
| 25 | expect(ids("notifications")).toEqual(["recent"]); |
| 26 | expect(ids("compute_usage")).toEqual(["recent-job", "running"]); |
| 27 | }); |
| 28 | |
| 29 | it("deletes a bounded batch at a time", async () => { |
| 30 | const DB = testD1(); |
| 31 | const at = new Date("2026-10-14T10:00:00Z"); |
| 32 | DB.sqlite.prepare("INSERT INTO accounts (id, login, kind, status, created_at) VALUES ('u', 'u', 'user', 'active', '2020-01-01T00:00:00Z')").run(); |
| 33 | const note = DB.sqlite.prepare("INSERT INTO notifications (id, account_id, kind, title, url, created_at) VALUES (?, 'u', 'issues', 't', 'u', '2020-01-01T00:00:00Z')"); |
| 34 | for (let i = 0; i < 5; i++) note.run(`n${i}`); |
| 35 | expect((await pruneExpired(DB, at, 3)).notifications).toBe(3); |
| 36 | expect((await pruneExpired(DB, at, 3)).notifications).toBe(2); |
| 37 | }); |
| 38 | }); |