import { expect, it } from "bun:test"; import express, { type Request } from "express"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { config } from "../../src/config.js"; import { attachTrustedAgentContext, getLocalAgentContext, ProductContextError } from "../../src/context/localContext.js"; import { registerAnswerViewStateRoutes } from "../../src/routes/answer-view-state-routes.js"; import { AnswerViewStateStore } from "../../src/sessions/answer-view-state-store.js"; import { SessionMetadataStore } from "../../src/sessions/metadataStore.js"; import { SessionUiStateStore } from "../../src/sessions/uiStateStore.js"; import { SessionWorkspaceStore } from "../../src/sessions/workspace-store.js"; it("authenticates state reads and writes against the persisted session owner and returns safe conflicts", async () => { const originalFetch = globalThis.fetch; const originalEnvironment = config.NODE_ENV; const root = await mkdtemp(join(tmpdir(), "answer-state-routes-")); const workspaces = new SessionWorkspaceStore(join(root, "workspaces")); const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces); const states = new AnswerViewStateStore(workspaces); globalThis.fetch = (async (input: Parameters[0], init: Parameters[1]) => { const url = String(input); const headers = new Headers(init?.headers); if (url.endsWith("/api/v1/agent-auth-context")) return Response.json({ user_id: headers.get("Authorization") === "Bearer OWNER" ? "owner" : "other", project_id: headers.get("X-Project-Id"), network: "supply" }); if (url.endsWith("/api/v1/access-context")) return Response.json({ user_id: headers.get("Authorization") === "Bearer OWNER" ? "owner" : "other" }); return originalFetch(input, init); }) as typeof fetch; config.NODE_ENV = "production"; const ownerRequest = { header: (name: string) => ({ authorization: "Bearer OWNER", "x-project-id": "project" })[name.toLowerCase() as "authorization" | "x-project-id"] } as Request; await attachTrustedAgentContext(ownerRequest); const context = getLocalAgentContext(ownerRequest); const sessionId = "state-session"; await sessions.ensure({ ...context, sessionId }); await new SessionUiStateStore(join(root, "unused"), workspaces).write({ ...context, sessionId }, { sessionId, messages: [{ id: "assistant", role: "assistant", parts: [{ type: "data-answer_ui", data: { version: "answer-ui@1", block_id: "block", fallback_text: "fallback", spec: { root: "evidence", elements: { evidence: { type: "Evidence", props: { title: "Evidence", summary: "Summary", source: "source", evidence_id: null, observed_at: null, kind: "observed" }, children: [] } } } } }] }] }); const app = express(); app.use(express.json({ limit: "2mb" })); app.use(async (req, res, next) => { try { await attachTrustedAgentContext(req); next(); } catch (error) { res.status(error instanceof ProductContextError ? error.status : 503).end(); } }); registerAnswerViewStateRoutes(app, { sessions, states }); const server = app.listen(0, "127.0.0.1"); await new Promise((resolve) => server.once("listening", resolve)); const url = `http://127.0.0.1:${(server.address() as { port: number }).port}/session/${sessionId}/ui-state`; const headers = { Authorization: "Bearer OWNER", "X-Project-Id": "project", "Content-Type": "application/json" }; try { expect((await originalFetch(`${url}?message_id=assistant&block_id=block`)).status).toBe(401); for (const bad of [{ ...headers, Authorization: "Bearer OTHER" }, { ...headers, "X-Project-Id": "other" }]) { expect((await originalFetch(`${url}?message_id=assistant&block_id=block`, { headers: bad })).status).toBe(404); expect((await originalFetch(url, { method: "PUT", headers: bad, body: '{}' })).status).toBe(404); } const initial = await originalFetch(`${url}?message_id=assistant&block_id=block`, { headers }); expect(initial.headers.get("Cache-Control")).toBe("private, no-store"); const first = await initial.json() as { schema_digest: string }; const payload = { message_id: "assistant", block_id: "block", schema_digest: first.schema_digest, expected_revision: 0, request_id: "request", nodes: { evidence: { type: "Evidence", open: true } }, block: { evidenceFilter: "all" } }; expect((await originalFetch(url, { method: "PUT", headers, body: JSON.stringify(payload) })).status).toBe(200); const conflict = await originalFetch(url, { method: "PUT", headers, body: JSON.stringify({ ...payload, request_id: "new" }) }); expect(conflict.status).toBe(409); expect(await conflict.json()).toMatchObject({ code: "ANSWER_VIEW_STATE_CONFLICT", current: { revision: 1 } }); expect((await originalFetch(`${url}?message_id=assistant&block_id=missing`, { headers })).status).toBe(404); expect((await originalFetch(`${url}?message_id=assistant&block_id=block&unexpected=x`, { headers })).status).toBe(400); await writeFile(workspaces.path({ ...context, sessionId }, "answer-view-state.json"), JSON.stringify({ damaged: `${root}/private-form-content` })); for (const response of [ await originalFetch(`${url}?message_id=assistant&block_id=block`, { headers }), await originalFetch(url, { method: "PUT", headers, body: JSON.stringify(payload) }), ]) { expect(response.status).toBe(503); expect(await response.json()).toEqual({ code: "ANSWER_VIEW_STATE_UNAVAILABLE", detail: "answer view state unavailable" }); } await sessions.remove((await sessions.get(context, sessionId))!); expect((await originalFetch(url, { method: "PUT", headers, body: JSON.stringify(payload) })).status).toBe(404); } finally { globalThis.fetch = originalFetch; config.NODE_ENV = originalEnvironment; await new Promise((resolve) => server.close(() => resolve())); await rm(root, { recursive: true, force: true }); } });