MEGAINFRA-WEBAPP/src/app/api/chat/threads/route.test.ts
DEls 9a6aed50a4 feat(management): add admin planning dashboards
Add turnover, project, retention, and year comparison pages from the supplied workbook, protected by named admin access. Include the local chart gallery, grouped monthly hover, smooth comparison toggle, and monthly income minus cost series.

Require named PocketBase sign-in in every environment and remove the shared PIN bypass. Update access documentation and business-route test fixtures.
2026-10-09 10:16:50 +02:00

73 lines
2.7 KiB
TypeScript

import "@/test-utils/named-admin-session";
import { getCurrentUser } from "@/lib/auth/session";
import { describe, it, expect, vi, beforeEach } from "vitest";
const getFullListUsersMock = vi.fn();
const getFullListThreadsMock = vi.fn();
const createThreadMock = vi.fn();
const filterMock = vi.fn((expr: string, params: Record<string, unknown>) => {
let out = expr;
for (const [k, v] of Object.entries(params)) out = out.replace(`{:${k}}`, String(v));
return out;
});
vi.mock("@/lib/pb", () => ({
pbAdmin: vi.fn(async () => ({
filter: filterMock,
collection: (name: string) => {
if (name === "users") return { getFullList: getFullListUsersMock };
if (name === "ask_threads") {
return { getFullList: getFullListThreadsMock, create: createThreadMock };
}
throw new Error(`unexpected collection in test: ${name}`);
},
})),
}));
import { GET, POST } from "./route";
function req(method: string, headers: Record<string, string> = {}, body?: unknown): Request {
return new Request("http://x/api/chat/threads", {
method,
headers,
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
});
}
describe("/api/chat/threads", () => {
beforeEach(() => {
getFullListUsersMock.mockReset();
getFullListThreadsMock.mockReset();
createThreadMock.mockReset();
getFullListUsersMock.mockResolvedValue([{ id: "u1", name: "Dashboard" }]);
});
it("GET requires a named admin session", async () => {
vi.mocked(getCurrentUser).mockResolvedValueOnce(null);
const res = await GET(req("GET"));
expect(res.status).toBe(403);
});
it("GET lists the actor's threads newest first", async () => {
getFullListThreadsMock.mockResolvedValue([{ id: "t1", title: "Hi" }]);
const res = await GET(req("GET", { "x-ask-user": "u1" }));
expect(await res.json()).toEqual({ threads: [{ id: "t1", title: "Hi" }] });
expect(getFullListThreadsMock.mock.calls[0][0]).toMatchObject({
filter: "owner = u1",
sort: "-last_message_at,-created",
});
});
it("POST creates a thread owned by the actor", async () => {
createThreadMock.mockResolvedValue({ id: "t9", title: "New thread" });
const res = await POST(req("POST", { "x-ask-user": "u1" }));
expect(await res.json()).toEqual({ id: "t9", title: "New thread" });
expect(createThreadMock.mock.calls[0][0]).toMatchObject({ owner: "u1", archived: false });
});
it("POST uses a supplied title", async () => {
createThreadMock.mockResolvedValue({ id: "t9", title: "Fuel report" });
const res = await POST(req("POST", { "x-ask-user": "u1" }, { title: "Fuel report" }));
expect((await res.json()).title).toBe("Fuel report");
});
});