feat: upgrade investigation and presentation runtime
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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<typeof fetch>[0], init: Parameters<typeof fetch>[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<void>((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<void>((resolve) => server.close(() => resolve()));
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user