kanban-dev-webapp/tests/integration/summarise.test.ts
Dave b96f38a259 fix(test): tighten log-work assertions, repair summarise.test.ts for source exclusion, add owner-only UI check
Critical fixes to test suite:

1. Fix time bomb in summarise.test.ts: Task 4 changed summaryStatus filter to exclude source="manual",
   so the assertion would fail once any manual entry exists. Updated test to count only non-manual
   commits and added coverage proving manual entries are excluded.

2. Tighten log-work metrics assertions from toBeGreaterThanOrEqual to exact equality (toBe).
   The old assertions would miss if manual entries were incorrectly fed into session clustering.
   Use a fresh baseline recompute to catch regression of Task 2's manual exclusion fix.

3. Add recomputeMetrics call in afterAll to restore cache after test cleanup,
   preventing inflated dashboard metrics on the demo project.

4. Fix fixture link_source from "none" to "manual" to match actual logWorkAction behavior
   (Task 6 review fix).

5. Add UI coverage: "Log work" button is owner-only, verifying the security model.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-19 13:44:34 +02:00

127 lines
3.8 KiB
TypeScript

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.");
});
});