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; beforeAll(async () => { await requireServices(); admin = await adminPb(); const project = await admin .collection("projects") .getFirstListItem(`slug = "${DEMO.projectSlug}"`); projectId = project.id; }); describe("logWorkAction", () => { const created: string[] = []; afterAll(async () => { for (const id of created) { await admin.collection("commits").delete(id).catch(() => {}); } }); it("refuses without a valid owner session", async () => { // The action reads the session cookie via next/headers, which is not // available outside a request in this harness — it must return a failure // rather than silently succeed with no session. const { logWorkAction } = await import("@/app/actions/log-work"); const result = await logWorkAction({ projectId, clientSlug: DEMO.clientSlug, projectSlug: DEMO.projectSlug, date: "2026-09-19", hours: 2, message: "Should never be written", }); expect(result.ok).toBe(false); const rows = await admin.collection("commits").getFullList({ filter: `project = "${projectId}" && subject = "Should never be written"`, }); expect(rows).toHaveLength(0); }); }); describe("computeProjectMetrics via recomputeMetrics", () => { let commitId: string; afterAll(async () => { if (commitId) await admin.collection("commits").delete(commitId).catch(() => {}); const { recomputeMetrics } = await import("@/lib/metrics/store"); await recomputeMetrics(projectId).catch(() => {}); }); it("adds a manual entry's declared minutes to estimatedMinutes", async () => { const { recomputeMetrics } = await import("@/lib/metrics/store"); const base = await recomputeMetrics(projectId); // fresh baseline, not a possibly-stale cache const commit = await admin.collection("commits").create({ project: projectId, sha: `manual-test-${Date.now()}`, short_sha: "manual", message: "Integration test entry", subject: "Integration test entry", type: "", scope: "", authored_at: "2026-09-19 12:00:00.000Z", is_merge: false, additions: 0, deletions: 0, card: "", link_source: "manual", url: "", summary: "Integration test entry", summary_version: 0, summary_attempts: 0, source: "manual", manual_minutes: 90, }); commitId = commit.id; const after = await recomputeMetrics(projectId); expect(after.manualMinutes).toBe(base.manualMinutes + 90); expect(after.estimatedMinutes).toBe(base.estimatedMinutes + 90); // flat, not clustered }); }); describe("the Log work UI is owner-only", () => { it("shows the button to the owner but not to a client manager", async () => { const ownerCookie = await signIn(DEMO.ownerEmail, DEMO.ownerPassword); const ownerHtml = await fetch( `${APP_URL}/p/${DEMO.clientSlug}/${DEMO.projectSlug}/dashboard`, { headers: { cookie: ownerCookie } } ).then((r) => r.text()); expect(ownerHtml).toContain("Log work"); const managerCookie = await signIn(DEMO.managerEmail, DEMO.managerPassword); const managerHtml = await fetch( `${APP_URL}/p/${DEMO.clientSlug}/${DEMO.projectSlug}/dashboard`, { headers: { cookie: managerCookie } } ).then((r) => r.text()); expect(managerHtml).not.toContain("Log work"); }); });