import { describe, it, expect, beforeAll } from "vitest"; import PocketBase from "pocketbase"; import { APP_URL, DEMO, adminPb, requireServices, signIn } from "./helpers"; let admin: PocketBase; let cookie: string; let cardId: string; beforeAll(async () => { await requireServices(); admin = await adminPb(); cookie = await signIn(DEMO.managerEmail, DEMO.managerPassword); const project = await admin .collection("projects") .getFirstListItem(`slug = "${DEMO.projectSlug}"`); cardId = ( await admin.collection("cards").getFirstListItem(`project = "${project.id}"`) ).id; }); async function patch(body: Record) { return fetch(`${APP_URL}/api/client/cards/${cardId}`, { method: "PATCH", headers: { "content-type": "application/json", cookie }, body: JSON.stringify(body), }); } async function activityCount(): Promise { const rows = await admin .collection("activity") .getFullList({ filter: `card = "${cardId}"` }); return rows.length; } /** Spec §9 test 5 */ describe("every client-originated change is audited", () => { it("writes one row with the correct old and new values", async () => { await admin.collection("cards").update(cardId, { priority: "medium" }); const before = await activityCount(); const response = await patch({ priority: "high" }); expect(response.status).toBe(200); // Exactly one new row overall — not "at least one", which would pass even // if the route wrote a row per field it merely looked at. expect(await activityCount()).toBe(before + 1); const rows = await admin.collection("activity").getFullList({ filter: `card = "${cardId}" && field = "priority"`, sort: "-created", }); expect(rows[0].action).toBe("priority_changed"); expect(rows[0].old_value).toBe("medium"); expect(rows[0].new_value).toBe("high"); expect(rows[0].actor).not.toBe(""); }); it("writes no row when the value is unchanged", async () => { await admin.collection("cards").update(cardId, { priority: "low" }); const before = await activityCount(); const response = await patch({ priority: "low" }); expect(response.status).toBe(200); expect(await activityCount()).toBe(before); }); it("writes one row per field when two change at once", async () => { await admin .collection("cards") .update(cardId, { priority: "medium", target_date: "2026-10-01" }); const before = await activityCount(); const response = await patch({ priority: "critical", target_date: "2026-12-24", }); expect(response.status).toBe(200); expect(await activityCount()).toBe(before + 2); }); it("returns 400 when the body contains no editable fields", async () => { const response = await patch({ title: "nope", column: "whatever" }); expect(response.status).toBe(400); }); });