test(agent-api): auth-refusal on all six routes, case-insensitive move,

no-double-accept, Inbox-never-accepts, and 404s

Adds the whole-branch-review test gaps: a single parametrised test looping
over all 6 (method, path, body) combinations asserting 401 for missing and
wrong tokens plus no-write-occurred for the 3 write routes; a move-route
case-insensitivity assertion (real column name uppercased); a negative test
that moving an already-accepted card again does not re-log card_accepted;
a negative test that moving a card into Inbox never sets accepted: true;
and 404 checks for a nonexistent card id across GET/POST/POST/PATCH.

Each new describe block creates and cleans up its own dedicated fixture
card(s), since the existing cardId fixtures from earlier describes in this
file are already deleted by their own afterAll by the time these run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-09-26 10:23:30 +02:00
parent 76a012bb43
commit 0f2ce3fed8

View file

@ -236,3 +236,277 @@ describe("card detail, comments, subtasks, and move", () => {
expect(activity.length).toBeGreaterThan(0);
});
});
describe("authentication refuses all six routes", () => {
let authCardId: string;
beforeAll(async () => {
const card = await admin.collection("cards").create({
project: projectId,
column: inboxColumnId,
title: `Agent API auth-refusal probe ${Date.now()}`,
description: "",
position: 999997,
priority: "medium",
created_by_role: "owner",
accepted: false,
archived: false,
});
authCardId = card.id;
});
afterAll(async () => {
if (authCardId) await admin.collection("cards").delete(authCardId).catch(() => {});
});
it("returns 401 for a missing or wrong token on every route, and writes nothing", async () => {
const backlog = await admin.collection("columns").getOne(backlogColumnId);
type RouteCase = {
method: string;
path: string;
body?: unknown;
assertNoWrite?: () => Promise<void>;
};
const routes: RouteCase[] = [
{ method: "GET", path: "/api/agent/projects" },
{ method: "GET", path: `/api/agent/projects/${projectId}/cards` },
{ method: "GET", path: `/api/agent/cards/${authCardId}` },
{
method: "POST",
path: `/api/agent/cards/${authCardId}/comments`,
body: { body: "should never be created" },
assertNoWrite: async () => {
const comments = await admin.collection("comments").getFullList({
filter: `card = "${authCardId}"`,
});
expect(comments).toHaveLength(0);
},
},
{
method: "POST",
path: `/api/agent/cards/${authCardId}/subtasks`,
body: { titles: ["should never be created"] },
assertNoWrite: async () => {
const subtasks = await admin.collection("subtasks").getFullList({
filter: `card = "${authCardId}"`,
});
expect(subtasks).toHaveLength(0);
},
},
{
method: "PATCH",
path: `/api/agent/cards/${authCardId}/move`,
body: { column: backlog.name },
assertNoWrite: async () => {
const card = await admin.collection("cards").getOne(authCardId);
expect(card.column).toBe(inboxColumnId);
},
},
];
for (const route of routes) {
const baseHeaders: HeadersInit | undefined = route.body
? { "content-type": "application/json" }
: undefined;
const requestBody = route.body ? JSON.stringify(route.body) : undefined;
const noToken = await fetch(`${APP_URL}${route.path}`, {
method: route.method,
headers: baseHeaders,
body: requestBody,
});
expect(noToken.status, `${route.method} ${route.path} (no token)`).toBe(401);
const wrongToken = await fetch(`${APP_URL}${route.path}`, {
method: route.method,
headers: { ...(baseHeaders ?? {}), authorization: "Bearer not-the-real-token" },
body: requestBody,
});
expect(wrongToken.status, `${route.method} ${route.path} (wrong token)`).toBe(401);
if (route.assertNoWrite) await route.assertNoWrite();
}
});
});
describe("PATCH /api/agent/cards/:cardId/move column-name case-insensitivity", () => {
let cardId: string;
beforeAll(async () => {
const card = await admin.collection("cards").create({
project: projectId,
column: inboxColumnId,
title: `Agent API case-insensitivity probe ${Date.now()}`,
description: "",
position: 999996,
priority: "medium",
created_by_role: "owner",
accepted: false,
archived: false,
});
cardId = card.id;
});
afterAll(async () => {
if (cardId) await admin.collection("cards").delete(cardId).catch(() => {});
});
it("resolves the destination column regardless of case", async () => {
const backlog = await admin.collection("columns").getOne(backlogColumnId);
const differentCase =
backlog.name === backlog.name.toUpperCase()
? backlog.name.toLowerCase()
: backlog.name.toUpperCase();
expect(differentCase).not.toBe(backlog.name);
const response = await authed(`/api/agent/cards/${cardId}/move`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ column: differentCase }),
});
expect(response.status).toBe(200);
const updated = await admin.collection("cards").getOne(cardId);
expect(updated.column).toBe(backlogColumnId);
});
});
describe("PATCH /api/agent/cards/:cardId/move does not double-log acceptance", () => {
let cardId: string;
let secondWorkingColumnId: string;
beforeAll(async () => {
const secondWorking = await admin
.collection("columns")
.getFirstListItem(
`project = "${projectId}" && is_inbox = false && is_done = false && id != "${backlogColumnId}"`
);
secondWorkingColumnId = secondWorking.id;
const card = await admin.collection("cards").create({
project: projectId,
column: inboxColumnId,
title: `Agent API double-accept probe ${Date.now()}`,
description: "",
position: 999995,
priority: "medium",
created_by_role: "owner",
accepted: false,
archived: false,
});
cardId = card.id;
});
afterAll(async () => {
if (cardId) await admin.collection("cards").delete(cardId).catch(() => {});
});
it("does not add a second card_accepted row when an already-accepted card moves again", async () => {
const backlog = await admin.collection("columns").getOne(backlogColumnId);
const secondWorking = await admin.collection("columns").getOne(secondWorkingColumnId);
const firstMove = await authed(`/api/agent/cards/${cardId}/move`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ column: backlog.name }),
});
expect(firstMove.status).toBe(200);
const firstBody = await firstMove.json();
expect(firstBody.accepted).toBe(true);
const activityAfterFirst = await admin.collection("activity").getFullList({
filter: `card = "${cardId}" && action = "card_accepted"`,
});
expect(activityAfterFirst.length).toBe(1);
const secondMove = await authed(`/api/agent/cards/${cardId}/move`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ column: secondWorking.name }),
});
expect(secondMove.status).toBe(200);
const activityAfterSecond = await admin.collection("activity").getFullList({
filter: `card = "${cardId}" && action = "card_accepted"`,
});
expect(activityAfterSecond.length).toBe(activityAfterFirst.length);
});
});
describe("PATCH /api/agent/cards/:cardId/move into Inbox never accepts", () => {
let cardId: string;
beforeAll(async () => {
const card = await admin.collection("cards").create({
project: projectId,
column: backlogColumnId,
title: `Agent API inbox-move probe ${Date.now()}`,
description: "",
position: 999994,
priority: "medium",
created_by_role: "owner",
accepted: false,
archived: false,
});
cardId = card.id;
});
afterAll(async () => {
if (cardId) await admin.collection("cards").delete(cardId).catch(() => {});
});
it("never sets accepted true when moving a card into the Inbox", async () => {
const inbox = await admin.collection("columns").getOne(inboxColumnId);
const response = await authed(`/api/agent/cards/${cardId}/move`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ column: inbox.name }),
});
expect(response.status).toBe(200);
const body = await response.json();
expect(body.accepted).toBe(false);
const updated = await admin.collection("cards").getOne(cardId);
expect(updated.column).toBe(inboxColumnId);
expect(updated.accepted).toBe(false);
});
});
describe("404s for a nonexistent card id", () => {
const bogusId = "does-not-exist-xyz";
it("GET card detail 404s", async () => {
const response = await authed(`/api/agent/cards/${bogusId}`);
expect(response.status).toBe(404);
});
it("POST comments 404s", async () => {
const response = await authed(`/api/agent/cards/${bogusId}/comments`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ body: "test" }),
});
expect(response.status).toBe(404);
});
it("POST subtasks 404s", async () => {
const response = await authed(`/api/agent/cards/${bogusId}/subtasks`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ titles: ["test"] }),
});
expect(response.status).toBe(404);
});
it("PATCH move 404s", async () => {
const response = await authed(`/api/agent/cards/${bogusId}/move`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ column: "Backlog" }),
});
expect(response.status).toBe(404);
});
});