Files
ThothII/frontend/src/api/sessions.test.ts

282 lines
10 KiB
TypeScript

import { http, HttpResponse } from "msw";
import { server } from "../test/msw";
import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures";
import { createSession, getMe, listSessions, prewarmRuntime, resumeSession } from "./sessions";
import {
renameSession, setSessionGroup, archiveSession, unarchiveSession,
deleteSession, getSessionDocuments,
} from "./sessions";
import { workspacePreferences } from "../workspaces/preferences";
test("createSession seeds ephemeral selections and posts them", async () => {
localStorage.clear();
let body: unknown = null;
server.use(
http.get("/api/settings", () => HttpResponse.json({
workspace: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
})),
http.get("/api/workspaces", () => HttpResponse.json([{
...workspaceSummaryFixture("psd-clinical", {
displayName: "PSD Clinical",
revision: workspaceRevisionFixture("psd-clinical"),
}),
}])),
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
workspace: canonicalWorkspaceFixture("psd-clinical"),
revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/snapshot" },
})),
http.post("/api/sessions", async ({ request }) => {
body = await request.json();
return HttpResponse.json({ id: "s1" });
}),
);
expect(await createSession({ question: "q" })).toEqual({ id: "s1" });
expect(body).toEqual({
question: "q", workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
});
expect(workspacePreferences.load()).toEqual({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
});
expect(localStorage.getItem("thothii.workspace-registry.v1.preferences")).toBeNull();
});
test("createSession does not POST when a selected summary aliases another workspace name", async () => {
workspacePreferences.save({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
});
let posted = false;
server.use(
http.get("/api/workspaces", () => HttpResponse.json([{
...workspaceSummaryFixture("psd-clinical", {
displayName: "PSD Clinical",
revision: workspaceRevisionFixture("psd-clinical"),
}),
name: "other-workspace",
}])),
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
workspace: canonicalWorkspaceFixture("psd-clinical"),
revision: {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/snapshot",
},
})),
http.post("/api/sessions", () => {
posted = true;
return HttpResponse.json({ id: "s1" });
}),
);
await expect(createSession({ question: "q" })).rejects.toMatchObject({
message: "Could not load workspace registry. Please retry.",
});
expect(posted).toBe(false);
});
test.each([
["historical state", { state: "operational" }],
["an unknown revision field", { generation: 1 }],
["a malformed revision", { commit: "not-a-commit" }],
])("createSession does not POST when the selected summary revision has %s", async (_case, revisionPatch) => {
workspacePreferences.save({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
});
const revision = {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40),
snapshotPath: "/snapshot", ...revisionPatch,
};
let posted = false;
server.use(
http.get("/api/workspaces", () => HttpResponse.json([{
...workspaceSummaryFixture("psd-clinical", {
displayName: "PSD Clinical",
revision: revision as any,
}),
}])),
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
workspace: canonicalWorkspaceFixture("psd-clinical"),
revision: {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/snapshot",
},
})),
http.post("/api/sessions", () => {
posted = true;
return HttpResponse.json({ id: "s1" });
}),
);
await expect(createSession({ question: "q" })).rejects.toMatchObject({
message: "Could not load workspace registry. Please retry.",
});
expect(posted).toBe(false);
});
test("createSession replaces a stale ephemeral workspace with the current installation default", async () => {
workspacePreferences.save({
workspaceId: "retired-workspace", provider: "zai", model: "glm-5.2", thinking: "low",
});
let body: unknown;
server.use(
http.get("/api/settings", () => HttpResponse.json({ workspace: "psd-clinical" })),
http.get("/api/workspaces", () => HttpResponse.json([{
...workspaceSummaryFixture("psd-clinical", {
displayName: "PSD Clinical",
revision: workspaceRevisionFixture("psd-clinical"),
}),
}])),
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
workspace: canonicalWorkspaceFixture("psd-clinical"),
revision: {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/snapshot",
},
})),
http.post("/api/sessions", async ({ request }) => {
body = await request.json();
return HttpResponse.json({ id: "s1" });
}),
);
await expect(createSession({ question: "q" })).resolves.toEqual({ id: "s1" });
expect(body).toEqual({
question: "q", workspaceId: "psd-clinical",
provider: "zai", model: "glm-5.2", thinking: "low",
});
expect(workspacePreferences.load()).toEqual({
workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
});
});
test("createSession preserves a local legacy selection when the registry is empty", async () => {
workspacePreferences.save({
workspaceId: "legacy-workspace", provider: "zai", model: "glm-5.2", thinking: "low",
});
let body: unknown;
server.use(
http.get("/api/workspaces", () => HttpResponse.json([])),
http.post("/api/sessions", async ({ request }) => {
body = await request.json();
return HttpResponse.json({ id: "s1" });
}),
);
await expect(createSession({ question: "q" })).resolves.toEqual({ id: "s1" });
expect(body).toEqual({
question: "q", workspaceId: "legacy-workspace",
provider: "zai", model: "glm-5.2", thinking: "low",
});
});
test("createSession rejects a workspace summary that omits the canonical revision", async () => {
localStorage.clear();
let posted = false;
server.use(
http.get("/api/settings", () => HttpResponse.json({
workspace: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low",
})),
http.get("/api/workspaces", () => HttpResponse.json([{
...workspaceSummaryFixture("psd-clinical", {
displayName: "PSD Clinical",
configurationState: "configuration_required",
}),
}])),
http.post("/api/sessions", async () => {
posted = true;
return HttpResponse.json({ id: "s1" });
}),
);
await expect(createSession({ question: "q" })).rejects.toMatchObject({
message: "Could not load workspace registry. Please retry.",
});
expect(posted).toBe(false);
});
test.each([202, 204])("prewarmRuntime accepts a body-less %s response", async (status) => {
let called = false;
server.use(
http.post("/api/runtime/prewarm", () => {
called = true;
return new HttpResponse(null, { status });
}),
);
await expect(prewarmRuntime()).resolves.toBeUndefined();
expect(called).toBe(true);
});
test("listSessions defaults to the current user's scope", async () => {
let scope: string | null = null;
server.use(http.get("/api/sessions", ({ request }) => {
scope = new URL(request.url).searchParams.get("scope");
return HttpResponse.json([{ id: "s1", status: "open", question: "q", summary: null, created_at: "t", updated_at: null, author: null }]);
}));
const rows = await listSessions();
expect(rows[0].id).toBe("s1");
expect(scope).toBe("mine");
});
test("listSessions requests the selected administrator scope", async () => {
let scope: string | null = null;
server.use(http.get("/api/sessions", ({ request }) => {
scope = new URL(request.url).searchParams.get("scope");
return HttpResponse.json([]);
}));
await listSessions("all");
expect(scope).toBe("all");
});
test("getMe fetches the typed authenticated principal", async () => {
server.use(http.get("/api/me", () =>
HttpResponse.json({ issuer: "portal", subject: "42", displayName: "Alice", isAdmin: true }),
));
await expect(getMe()).resolves.toEqual({
issuer: "portal", subject: "42", displayName: "Alice", isAdmin: true,
});
});
test("resumeSession returns the typed runtime disposition", async () => {
server.use(http.post("/api/sessions/s1/resume", () =>
HttpResponse.json({ id: "s1", alreadyActive: false })));
const result: { id: string; alreadyActive: boolean } = await resumeSession("s1");
expect(result).toEqual({ id: "s1", alreadyActive: false });
});
test("renameSession POSTs {name}", async () => {
let body: unknown = null;
server.use(http.post("/api/sessions/s1/rename", async ({ request }) => {
body = await request.json();
return new HttpResponse(null, { status: 204 });
}));
await renameSession("s1", "Nome");
expect(body).toEqual({ name: "Nome" });
});
test("setSessionGroup POSTs {group}", async () => {
let body: unknown = null;
server.use(http.post("/api/sessions/s1/group", async ({ request }) => {
body = await request.json();
return new HttpResponse(null, { status: 204 });
}));
await setSessionGroup("s1", "G");
expect(body).toEqual({ group: "G" });
});
test("archive / unarchive / delete hit the right verbs+paths", async () => {
const hits: string[] = [];
server.use(
http.post("/api/sessions/s1/archive", () => { hits.push("archive"); return new HttpResponse(null, { status: 204 }); }),
http.post("/api/sessions/s1/unarchive", () => { hits.push("unarchive"); return new HttpResponse(null, { status: 204 }); }),
http.delete("/api/sessions/s1", () => { hits.push("delete"); return new HttpResponse(null, { status: 204 }); }),
);
await archiveSession("s1");
await unarchiveSession("s1");
await deleteSession("s1");
expect(hits).toEqual(["archive", "unarchive", "delete"]);
});
test("getSessionDocuments GETs the array", async () => {
server.use(http.get("/api/sessions/s1/documents", () =>
HttpResponse.json([{ phase: "—", key: "question", title: "t", format: "text", content: "q" }]),
));
const docs = await getSessionDocuments("s1");
expect(docs[0].key).toBe("question");
});