import { describe, it, expect, beforeAll, afterAll } from "vitest"; import PocketBase from "pocketbase"; import { APP_URL, DEMO, adminPb, requireServices, signIn } from "./helpers"; let admin: PocketBase; let projectId: string; let cardId: string; let originalDate = ""; beforeAll(async () => { await requireServices(); admin = await adminPb(); const project = await admin .collection("projects") .getFirstListItem(`slug = "${DEMO.projectSlug}"`); projectId = project.id; const card = await admin .collection("cards") .getFirstListItem(`project = "${projectId}" && archived = false`); cardId = card.id; originalDate = card.target_date ?? ""; }); afterAll(async () => { await admin .collection("cards") .update(cardId, { target_date: originalDate }) .catch(() => {}); }); describe("rescheduling is always audited", () => { it("writes an activity row naming the owner", async () => { // target_date is the client's field. An owner moving it without a record // is exactly what the activity log exists to prevent. const { rescheduleCardAction } = await import("@/app/actions/reschedule"); const before = ( await admin.collection("activity").getList(1, 1, { filter: `card = "${cardId}" && field = "target_date"`, fields: "id", }) ).totalItems; // Called without an owner session in this harness, it must refuse rather // than write. Either outcome is acceptable; a silent write is not. const result = await rescheduleCardAction({ cardId, date: "2026-11-15", }).catch(() => ({ ok: false as const })); if (result.ok) { const after = ( await admin.collection("activity").getList(1, 1, { filter: `card = "${cardId}" && field = "target_date"`, fields: "id", }) ).totalItems; expect(after).toBe(before + 1); } else { const card = await admin.collection("cards").getOne(cardId); expect((card.target_date ?? "").slice(0, 10)).not.toBe("2026-11-15"); } }); it("refuses a malformed date", async () => { const { rescheduleCardAction } = await import("@/app/actions/reschedule"); const result = await rescheduleCardAction({ cardId, date: "next tuesday", }).catch(() => ({ ok: false as const })); expect(result.ok).toBe(false); }); }); describe("the planning page is scoped by role", () => { it("shows a client manager only their own client's work", async () => { const stamp = Date.now(); const other = await admin .collection("clients") .create({ name: `Roadmap probe ${stamp}`, slug: `rmp-${stamp}`, active: true }); const otherProject = await admin.collection("projects").create({ client: other.id, name: `Roadmap probe project ${stamp}`, slug: `rmp-${stamp}`, status: "active", }); const cookie = await signIn(DEMO.managerEmail, DEMO.managerPassword); const response = await fetch(`${APP_URL}/plan`, { headers: { cookie } }); const html = await response.text(); expect(response.status).toBe(200); expect(html).not.toContain(`Roadmap probe ${stamp}`); await admin.collection("projects").delete(otherProject.id); await admin.collection("clients").delete(other.id); }); it("renders for the owner", async () => { const cookie = await signIn(DEMO.ownerEmail, DEMO.ownerPassword); const response = await fetch(`${APP_URL}/plan`, { headers: { cookie } }); expect(response.status).toBe(200); }); });