2026-09-26 08:00:14 +00:00
|
|
|
import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
|
|
|
|
|
import PocketBase from "pocketbase";
|
|
|
|
|
|
|
|
|
|
import { APP_URL, DEMO, adminPb, requireServices } from "./helpers";
|
|
|
|
|
|
|
|
|
|
const TOKEN = process.env.AGENT_API_TOKEN;
|
|
|
|
|
|
|
|
|
|
let admin: PocketBase;
|
|
|
|
|
let projectId: string;
|
|
|
|
|
let inboxColumnId: string;
|
|
|
|
|
let backlogColumnId: string;
|
|
|
|
|
|
|
|
|
|
function authed(path: string, init: RequestInit = {}) {
|
|
|
|
|
return fetch(`${APP_URL}${path}`, {
|
|
|
|
|
...init,
|
|
|
|
|
headers: { ...(init.headers ?? {}), authorization: `Bearer ${TOKEN}` },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
await requireServices();
|
|
|
|
|
if (!TOKEN) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"AGENT_API_TOKEN must be set in the environment running this test (see .env.local)."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
admin = await adminPb();
|
|
|
|
|
const project = await admin
|
|
|
|
|
.collection("projects")
|
|
|
|
|
.getFirstListItem(`slug = "${DEMO.projectSlug}"`);
|
|
|
|
|
projectId = project.id;
|
|
|
|
|
|
|
|
|
|
const inbox = await admin
|
|
|
|
|
.collection("columns")
|
|
|
|
|
.getFirstListItem(`project = "${projectId}" && is_inbox = true`);
|
|
|
|
|
inboxColumnId = inbox.id;
|
|
|
|
|
|
|
|
|
|
const backlog = await admin
|
|
|
|
|
.collection("columns")
|
|
|
|
|
.getFirstListItem(`project = "${projectId}" && is_inbox = false && is_done = false`);
|
|
|
|
|
backlogColumnId = backlog.id;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("authentication", () => {
|
|
|
|
|
it("refuses a request with no token", async () => {
|
|
|
|
|
const response = await fetch(`${APP_URL}/api/agent/projects`);
|
|
|
|
|
expect(response.status).toBe(401);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("refuses a request with the wrong token", async () => {
|
|
|
|
|
const response = await fetch(`${APP_URL}/api/agent/projects`, {
|
|
|
|
|
headers: { authorization: "Bearer not-the-real-token" },
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(401);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts a request with the right token", async () => {
|
|
|
|
|
const response = await authed("/api/agent/projects");
|
|
|
|
|
expect(response.status).toBe(200);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("GET /api/agent/projects", () => {
|
|
|
|
|
it("includes the demo project with its client", async () => {
|
|
|
|
|
const response = await authed("/api/agent/projects");
|
|
|
|
|
const projects = await response.json();
|
|
|
|
|
const found = projects.find((p: { id: string }) => p.id === projectId);
|
|
|
|
|
expect(found).toBeTruthy();
|
|
|
|
|
expect(found.client.slug).toBe(DEMO.clientSlug);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("GET /api/agent/projects/:projectId/cards", () => {
|
|
|
|
|
let cardId: string;
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
if (cardId) await admin.collection("cards").delete(cardId).catch(() => {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("lists a newly created Inbox card, and the ?column=inbox filter excludes non-Inbox cards", async () => {
|
|
|
|
|
const stamp = Date.now();
|
|
|
|
|
const card = await admin.collection("cards").create({
|
|
|
|
|
project: projectId,
|
|
|
|
|
column: inboxColumnId,
|
|
|
|
|
title: `Agent API probe ${stamp}`,
|
|
|
|
|
description: "",
|
|
|
|
|
position: 999999,
|
|
|
|
|
priority: "medium",
|
|
|
|
|
created_by_role: "owner",
|
|
|
|
|
accepted: false,
|
|
|
|
|
archived: false,
|
|
|
|
|
});
|
|
|
|
|
cardId = card.id;
|
|
|
|
|
|
|
|
|
|
const all = await authed(`/api/agent/projects/${projectId}/cards`).then((r) => r.json());
|
|
|
|
|
expect(all.some((c: { id: string }) => c.id === cardId)).toBe(true);
|
|
|
|
|
|
|
|
|
|
const inboxOnly = await authed(`/api/agent/projects/${projectId}/cards?column=inbox`).then(
|
|
|
|
|
(r) => r.json()
|
|
|
|
|
);
|
|
|
|
|
expect(inboxOnly.every((c: { column: { is_inbox: boolean } }) => c.column.is_inbox)).toBe(
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
expect(inboxOnly.some((c: { id: string }) => c.id === cardId)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("404s for an unknown project", async () => {
|
|
|
|
|
const response = await authed("/api/agent/projects/does-not-exist/cards");
|
|
|
|
|
expect(response.status).toBe(404);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("card detail, comments, subtasks, and move", () => {
|
|
|
|
|
let cardId: string;
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
const card = await admin.collection("cards").create({
|
|
|
|
|
project: projectId,
|
|
|
|
|
column: inboxColumnId,
|
|
|
|
|
title: `Agent API detail probe ${Date.now()}`,
|
|
|
|
|
description: "A card for the agent API's own integration tests.",
|
|
|
|
|
position: 999998,
|
|
|
|
|
priority: "medium",
|
|
|
|
|
created_by_role: "owner",
|
|
|
|
|
accepted: false,
|
|
|
|
|
archived: false,
|
|
|
|
|
});
|
|
|
|
|
cardId = card.id;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
await admin.collection("cards").delete(cardId).catch(() => {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("GET returns the card with empty subtasks/comments", async () => {
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}`);
|
|
|
|
|
const detail = await response.json();
|
|
|
|
|
expect(detail.id).toBe(cardId);
|
|
|
|
|
expect(detail.subtasks).toEqual([]);
|
|
|
|
|
expect(detail.comments).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("POST comments prefixes the body and attributes it to the owner", async () => {
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}/comments`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ body: "Here is the plan." }),
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(201);
|
|
|
|
|
|
|
|
|
|
const stored = await admin.collection("comments").getFullList({
|
|
|
|
|
filter: `card = "${cardId}"`,
|
|
|
|
|
});
|
|
|
|
|
expect(stored).toHaveLength(1);
|
|
|
|
|
expect(stored[0].body.startsWith("🤖 Drafted by your agent:")).toBe(true);
|
|
|
|
|
expect(stored[0].body).toContain("Here is the plan.");
|
|
|
|
|
|
|
|
|
|
const ownerUser = await admin.collection("users").getFirstListItem('role = "owner"');
|
|
|
|
|
expect(stored[0].author).toBe(ownerUser.id);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects an empty comment body", async () => {
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}/comments`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ body: " " }),
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("POST subtasks creates them in order after any existing ones", async () => {
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}/subtasks`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ titles: ["Step one", "Step two", "Step three"] }),
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(201);
|
|
|
|
|
|
|
|
|
|
const stored = await admin.collection("subtasks").getFullList({
|
|
|
|
|
filter: `card = "${cardId}"`,
|
|
|
|
|
sort: "position",
|
|
|
|
|
});
|
|
|
|
|
expect(stored.map((s) => s.title)).toEqual(["Step one", "Step two", "Step three"]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects a subtasks request with no usable titles", async () => {
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}/subtasks`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ titles: [" ", ""] }),
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("PATCH move rejects an unknown column name and lists the real ones", async () => {
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}/move`, {
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ column: "Not A Real Column" }),
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(400);
|
|
|
|
|
const body = await response.json();
|
|
|
|
|
expect(body.error).toContain("Not A Real Column");
|
2026-09-26 08:05:27 +00:00
|
|
|
|
|
|
|
|
// The whole point of this error is that a caller can self-correct from it —
|
|
|
|
|
// assert it actually lists a real column name, not just echoes the bad input.
|
|
|
|
|
const columns = await admin.collection("columns").getFullList({
|
|
|
|
|
filter: `project = "${projectId}"`,
|
|
|
|
|
});
|
|
|
|
|
for (const column of columns) {
|
|
|
|
|
expect(body.error).toContain(column.name);
|
|
|
|
|
}
|
2026-09-26 08:00:14 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("PATCH move to a working column accepts the card and logs it", async () => {
|
|
|
|
|
const backlog = await admin.collection("columns").getOne(backlogColumnId);
|
|
|
|
|
|
|
|
|
|
const response = await authed(`/api/agent/cards/${cardId}/move`, {
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ column: backlog.name }),
|
|
|
|
|
});
|
|
|
|
|
expect(response.status).toBe(200);
|
|
|
|
|
const body = await response.json();
|
|
|
|
|
expect(body.accepted).toBe(true);
|
|
|
|
|
|
|
|
|
|
const updated = await admin.collection("cards").getOne(cardId);
|
|
|
|
|
expect(updated.column).toBe(backlogColumnId);
|
|
|
|
|
expect(updated.accepted).toBe(true);
|
|
|
|
|
expect(updated.start_date).not.toBe("");
|
|
|
|
|
|
|
|
|
|
const activity = await admin.collection("activity").getFullList({
|
|
|
|
|
filter: `card = "${cardId}" && action = "card_accepted"`,
|
|
|
|
|
});
|
|
|
|
|
expect(activity.length).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-09-26 08:23:30 +00:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|