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>
110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
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");
|
|
});
|
|
});
|