2026-09-16 14:18:40 +00:00
|
|
|
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;
|
|
|
|
|
|
2026-09-19 11:44:34 +00:00
|
|
|
const before = await summaryStatus(projectId);
|
|
|
|
|
// status.total counts non-merge, non-manual commits only.
|
|
|
|
|
expect(before.total).toBe(all - merges);
|
2026-09-16 14:18:40 +00:00
|
|
|
expect(merges).toBeGreaterThan(0);
|
2026-09-19 11:44:34 +00:00
|
|
|
|
|
|
|
|
// 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(() => {});
|
|
|
|
|
}
|
2026-09-16 14:18:40 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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.");
|
|
|
|
|
});
|
|
|
|
|
});
|