From aea000f654c05549d3071b1c79355c2eb420a5be Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 16 Sep 2026 16:18:40 +0200 Subject: [PATCH] test(ai): merge exclusion, graceful degradation, summary persistence --- tests/integration/summarise.test.ts | 97 +++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/integration/summarise.test.ts diff --git a/tests/integration/summarise.test.ts b/tests/integration/summarise.test.ts new file mode 100644 index 0000000..694b131 --- /dev/null +++ b/tests/integration/summarise.test.ts @@ -0,0 +1,97 @@ +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 status = await summaryStatus(projectId); + + 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; + + // status.total counts non-merge commits only. + expect(status.total).toBe(all - merges); + expect(merges).toBeGreaterThan(0); + }); +}); + +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."); + }); +});