import { describe, it, expect, beforeAll, afterAll } from "vitest"; import PocketBase from "pocketbase"; import { DEMO, adminPb, requireServices } from "./helpers"; let admin: PocketBase; let projectId: string; const created: string[] = []; beforeAll(async () => { await requireServices(); admin = await adminPb(); projectId = ( await admin.collection("projects").getFirstListItem(`slug = "${DEMO.projectSlug}"`) ).id; }); afterAll(async () => { for (const id of created) { await admin.collection("commits").delete(id).catch(() => {}); } }); describe("merge commits are never summarised", () => { it("excludes them from the pending set", async () => { const { summaryStatus } = await import("@/lib/ai/summarise"); const merges = ( await admin .collection("commits") .getList(1, 1, { filter: `project = "${projectId}" && is_merge = true`, fields: "id" }) ).totalItems; const all = ( await admin .collection("commits") .getList(1, 1, { filter: `project = "${projectId}"`, fields: "id" }) ).totalItems; const before = await summaryStatus(projectId); // status.total counts non-merge, non-manual commits only. expect(before.total).toBe(all - merges); expect(merges).toBeGreaterThan(0); // A manual entry must not move this count at all. const manual = await admin.collection("commits").create({ project: projectId, sha: `manual-summarise-test-${Date.now()}`, short_sha: "manual", message: "Summarise-exclusion probe", subject: "Summarise-exclusion probe", type: "", scope: "", authored_at: "2026-09-19 12:00:00.000Z", is_merge: false, additions: 0, deletions: 0, card: "", link_source: "manual", url: "", summary: "Summarise-exclusion probe", summary_version: 0, summary_attempts: 0, source: "manual", manual_minutes: 30, }); try { const after = await summaryStatus(projectId); expect(after.total).toBe(before.total); } finally { await admin.collection("commits").delete(manual.id).catch(() => {}); } }); }); describe("summarisation degrades rather than failing", () => { it("returns a skipped result when no provider is configured", async () => { const settings = await admin .collection("settings") .getFirstListItem("id != ''") .catch(() => null); const previousKey = settings?.ai_api_key ?? ""; if (settings) { await admin.collection("settings").update(settings.id, { ai_api_key: "" }); } const { summariseProject } = await import("@/lib/ai/summarise"); const result = await summariseProject(projectId); // Must not throw. The dashboard and board depend on this. expect(result.skipped).toBe(true); expect(result.summarised).toBe(0); if (settings && previousKey) { await admin.collection("settings").update(settings.id, { ai_api_key: previousKey }); } }); }); describe("a stored summary survives re-ingestion", () => { it("is not cleared when the same commit arrives again", async () => { const commit = await admin.collection("commits").create({ project: projectId, sha: `summary-probe-${Date.now()}`, short_sha: "sumprb", message: "feat(probe): a thing", subject: "a thing", type: "feat", scope: "probe", authored_at: new Date().toISOString(), is_merge: false, link_source: "none", summary: "Did a thing for the client.", summary_version: 1, summary_attempts: 0, }); created.push(commit.id); const { relinkProject } = await import("@/lib/linking/engine"); await relinkProject(projectId); const after = await admin.collection("commits").getOne(commit.id); expect(after.summary).toBe("Did a thing for the client."); }); });