Initial curated projectbrain source from 8a7b1e0651dbdab97d744cbd2e653ab3970ac6c5
This commit is contained in:
commit
5aa3e8d685
700 files changed
+132801
No files matched your search
@@ -0,0 +1,98 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { buildApp } from "../src/app.js";
|
||||
import { loadEnv } from "../src/config/env.js";
|
||||
|
||||
const ownerPassword = "correct-horse-battery-staple";
|
||||
const appEnv = () => loadEnv({
|
||||
NODE_ENV: "test",
|
||||
LOG_LEVEL: "silent",
|
||||
PROJECTBRAIN_DATA_SOURCE: "fixture",
|
||||
PROJECTBRAIN_OWNER_USERNAME: "owner",
|
||||
PROJECTBRAIN_OWNER_PASSWORD: ownerPassword,
|
||||
PROJECTBRAIN_AUTH_SECRET: "test-secret-with-at-least-thirty-two-characters",
|
||||
PROJECTBRAIN_SESSION_TTL_HOURS: "1",
|
||||
PROJECTBRAIN_TRUST_PROXY: "false",
|
||||
});
|
||||
|
||||
const apps: Awaited<ReturnType<typeof buildApp>>[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(apps.splice(0).map((app) => app.close()));
|
||||
});
|
||||
|
||||
describe("capture and review state machines", () => {
|
||||
it("classifies once, queues once, and keeps final review decisions immutable", async () => {
|
||||
const app = await buildApp({ env: appEnv() });
|
||||
apps.push(app);
|
||||
const login = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/auth/login",
|
||||
payload: { username: "owner", password: ownerPassword },
|
||||
});
|
||||
const sessionCookie = login.cookies.find((item) => item.name === "projectbrain_session");
|
||||
const cookie = `${sessionCookie!.name}=${sessionCookie!.value}`;
|
||||
const csrfToken = login.json().data.csrfToken as string;
|
||||
let operation = 0;
|
||||
const mutate = (method: "POST" | "PATCH", url: string, payload: Record<string, unknown> = {}) =>
|
||||
app.inject({
|
||||
method,
|
||||
url,
|
||||
payload,
|
||||
headers: {
|
||||
cookie,
|
||||
"x-csrf-token": csrfToken,
|
||||
"idempotency-key": `review-state-machine-${++operation}`,
|
||||
},
|
||||
});
|
||||
|
||||
const capture = await mutate("POST", "/api/projects/widefrog/capture-items", {
|
||||
type: "note",
|
||||
content: "A decision that must pass through one governed review.",
|
||||
source: "state-machine-test",
|
||||
});
|
||||
expect(capture.statusCode).toBe(201);
|
||||
const captureId = capture.json().data.id as string;
|
||||
|
||||
const prematureQueue = await mutate("POST", `/api/projects/widefrog/capture-items/${captureId}/send-to-review`);
|
||||
expect(prematureQueue.statusCode).toBe(409);
|
||||
expect(prematureQueue.json().error.code).toBe("CAPTURE_NOT_CLASSIFIED");
|
||||
|
||||
expect((await mutate("PATCH", `/api/projects/widefrog/capture-items/${captureId}/classify`)).statusCode).toBe(200);
|
||||
const queued = await mutate("POST", `/api/projects/widefrog/capture-items/${captureId}/send-to-review`);
|
||||
expect(queued.statusCode).toBe(201);
|
||||
const reviewId = queued.json().data.createdEntityId as string;
|
||||
|
||||
const duplicateQueue = await mutate("POST", `/api/projects/widefrog/capture-items/${captureId}/send-to-review`);
|
||||
expect(duplicateQueue.statusCode).toBe(409);
|
||||
expect(duplicateQueue.json().error.code).toBe("CAPTURE_ALREADY_IN_REVIEW");
|
||||
const reclassify = await mutate("PATCH", `/api/projects/widefrog/capture-items/${captureId}/classify`);
|
||||
expect(reclassify.statusCode).toBe(409);
|
||||
expect(reclassify.json().error.code).toBe("CAPTURE_ALREADY_IN_REVIEW");
|
||||
|
||||
const edited = await mutate("PATCH", `/api/projects/widefrog/review-queue/${reviewId}`, {
|
||||
description: "Edited review content that still needs a final decision.",
|
||||
});
|
||||
expect(edited.statusCode).toBe(200);
|
||||
expect(edited.json().data.description).toContain("Edited review content");
|
||||
|
||||
const invalidMerge = await mutate("POST", `/api/projects/widefrog/review-queue/${reviewId}/merge`, {
|
||||
targetEntityId: "does-not-exist",
|
||||
});
|
||||
expect(invalidMerge.statusCode).toBe(409);
|
||||
expect(invalidMerge.json().error.code).toBe("REVIEW_TARGET_NOT_FOUND");
|
||||
|
||||
const unsupportedConversion = await mutate("POST", `/api/projects/widefrog/review-queue/${reviewId}/convert`, {
|
||||
target: "decision",
|
||||
});
|
||||
expect(unsupportedConversion.statusCode).toBe(501);
|
||||
expect(unsupportedConversion.json().error.code).toBe("DATABASE_MODE_REQUIRED");
|
||||
|
||||
expect((await mutate("POST", `/api/projects/widefrog/review-queue/${reviewId}/accept`)).statusCode).toBe(200);
|
||||
const overwrite = await mutate("POST", `/api/projects/widefrog/review-queue/${reviewId}/reject`);
|
||||
expect(overwrite.statusCode).toBe(409);
|
||||
expect(overwrite.json().error.code).toBe("REVIEW_ALREADY_DECIDED");
|
||||
const editFinal = await mutate("PATCH", `/api/projects/widefrog/review-queue/${reviewId}`, { notes: "overwrite" });
|
||||
expect(editFinal.statusCode).toBe(409);
|
||||
expect(editFinal.json().error.code).toBe("REVIEW_ALREADY_DECIDED");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user