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 cookie: string; let projectId: string; let inboxId: string; let doneId: string; const createdCards: 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}"`); projectId = project.id; inboxId = ( await admin .collection("columns") .getFirstListItem(`project = "${projectId}" && is_inbox = true`) ).id; doneId = ( await admin .collection("columns") .getFirstListItem(`project = "${projectId}" && is_done = true`) ).id; }); afterAll(async () => { for (const id of createdCards) { await admin.collection("cards").delete(id).catch(() => {}); } }); const brief = { title: "Add a POS at the counter", summary: "Till staff need to take card payments.", subtasks: ["Choose a card reader", "Wire it to the till"], openQuestions: ["Which reader is in use today?"], area: "", degraded: false, }; describe("a brief becomes an Inbox card with sub-tasks", () => { it("creates the card, the subtasks and the activity row", async () => { const response = await fetch(`${APP_URL}/api/client/intake`, { method: "POST", headers: { "content-type": "application/json", cookie }, body: JSON.stringify({ project: projectId, brief, transcript: [{ question: "What would you like?", answer: "A POS." }], sizeBand: "medium", }), }); expect(response.status).toBe(201); const { card } = await response.json(); createdCards.push(card.id); const created = await admin.collection("cards").getOne(card.id); expect(created.column).toBe(inboxId); expect(created.accepted).toBe(false); expect(created.created_by_role).toBe("client"); expect(created.title).toBe(brief.title); expect(created.size_band).toBe("medium"); expect(created.number).toBeGreaterThan(0); const subtasks = await admin .collection("subtasks") .getFullList({ filter: `card = "${card.id}"`, sort: "position" }); expect(subtasks.map((s) => s.title)).toEqual(brief.subtasks); const activity = await admin .collection("activity") .getFullList({ filter: `card = "${card.id}"` }); expect(activity.length).toBeGreaterThan(0); }); }); describe("the server decides the fields that matter", () => { it("ignores an escalating brief entirely", async () => { // The whole security property in one test: whatever the body claims, the // card lands in the Inbox at medium priority, unaccepted. const response = await fetch(`${APP_URL}/api/client/intake`, { method: "POST", headers: { "content-type": "application/json", cookie }, body: JSON.stringify({ project: projectId, brief: { ...brief, priority: "critical", accepted: true, column: doneId }, transcript: [], sizeBand: "large", // Top-level attempts too, not only inside the brief. priority: "critical", accepted: true, column: doneId, created_by_role: "owner", }), }); expect(response.status).toBe(201); const { card } = await response.json(); createdCards.push(card.id); const created = await admin.collection("cards").getOne(card.id); expect(created.column).toBe(inboxId); expect(created.priority).toBe("medium"); expect(created.accepted).toBe(false); expect(created.created_by_role).toBe("client"); }); it("rejects an unknown size band rather than storing it", async () => { const response = await fetch(`${APP_URL}/api/client/intake`, { method: "POST", headers: { "content-type": "application/json", cookie }, body: JSON.stringify({ project: projectId, brief, transcript: [], sizeBand: "enormous", }), }); const { card } = await response.json(); createdCards.push(card.id); const created = await admin.collection("cards").getOne(card.id); expect(created.size_band).toBe(""); }); }); describe("the intake route is scoped like every other client route", () => { it("refuses another client's project", async () => { const stamp = Date.now(); const other = await admin .collection("clients") .create({ name: "Intake scope probe", slug: `isp-${stamp}`, active: true }); const otherProject = await admin.collection("projects").create({ client: other.id, name: "Probe", slug: `isp-${stamp}`, status: "active", }); const response = await fetch(`${APP_URL}/api/client/intake`, { method: "POST", headers: { "content-type": "application/json", cookie }, body: JSON.stringify({ project: otherProject.id, brief, transcript: [] }), }); expect(response.status).toBe(404); await admin.collection("projects").delete(otherProject.id); await admin.collection("clients").delete(other.id); }); it("refuses an unauthenticated request", async () => { const response = await fetch(`${APP_URL}/api/client/intake`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ project: projectId, brief, transcript: [] }), }); expect(response.status).toBe(401); }); });