feat: expand intelligent investigation workspace
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createAgentApiClient } from "./client";
|
||||
import type { AnswerViewStateWrite } from "./answer-view-state";
|
||||
|
||||
const remote = { version: "answer-view-state@1", message_id: "message", block_id: "block", revision: 2,
|
||||
schema_digest: "digest", nodes: {}, block: { evidenceFilter: "all" }, updated_at: null };
|
||||
const write: AnswerViewStateWrite = { message_id: "message", block_id: "block", expected_revision: 2, schema_digest: "digest", request_id: "intent", nodes: {}, block: { evidenceFilter: "all" } };
|
||||
const token = (subject: string) => `header.${btoa(JSON.stringify({ iss: "https://identity.local", sub: subject }))}.signature`;
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
describe("authorized answer view state API", () => {
|
||||
it("keeps the message and block identity and uses the configured auth/project context", async () => {
|
||||
const fetcher = vi.fn().mockImplementation(async () => Response.json(remote)); vi.stubGlobal("fetch", fetcher);
|
||||
const api = createAgentApiClient("https://agent.local", { getAccessToken: async () => token("alice"), getProjectId: async () => "project" });
|
||||
await expect(api.getAnswerViewState("session/one", "message", "block")).resolves.toEqual(remote);
|
||||
await api.putAnswerViewState("session/one", write);
|
||||
expect(fetcher.mock.calls[0][0]).toBe("https://agent.local/api/v1/session/session%2Fone/ui-state?message_id=message&block_id=block");
|
||||
expect(fetcher.mock.calls[1][0]).toBe("https://agent.local/api/v1/session/session%2Fone/ui-state");
|
||||
expect(fetcher.mock.calls[1][1].body).toBe(JSON.stringify(write));
|
||||
for (const [, init] of fetcher.mock.calls) {
|
||||
expect(new Headers(init.headers).get("Authorization")).toBe(`Bearer ${token("alice")}`);
|
||||
expect(new Headers(init.headers).get("X-Project-Id")).toBe("project");
|
||||
}
|
||||
});
|
||||
it("retains structured conflict details for the explicit user choices", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn(async () => Response.json({ code: "ANSWER_VIEW_STATE_CONFLICT", detail: "new revision", current: remote }, { status: 409 })));
|
||||
await expect(createAgentApiClient("https://agent.local").putAnswerViewState("session", write)).rejects.toMatchObject({
|
||||
status: 409, code: "ANSWER_VIEW_STATE_CONFLICT", current: remote,
|
||||
});
|
||||
});
|
||||
it("rejects writes if a mutable token provider changes subject in the same project", async () => {
|
||||
const fetcher = vi.fn(async () => Response.json(remote)); vi.stubGlobal("fetch", fetcher);
|
||||
let subject = "alice";
|
||||
const api = createAgentApiClient("https://agent.local", { getAccessToken: async () => token(subject), getProjectId: async () => "same-project" });
|
||||
await api.getAnswerViewState("session", "message", "block"); subject = "bob";
|
||||
await expect(api.putAnswerViewState("session", write)).rejects.toMatchObject({ code: "ANSWER_VIEW_IDENTITY_CHANGED" });
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("rejects a late read if identity changes while the request is in flight", async () => {
|
||||
let finish!: (response: Response) => void;
|
||||
const fetcher = vi.fn(() => new Promise<Response>((resolve) => { finish = resolve; })); vi.stubGlobal("fetch", fetcher);
|
||||
let subject = "alice";
|
||||
const api = createAgentApiClient("https://agent.local", { getAccessToken: async () => token(subject) });
|
||||
const read = api.getAnswerViewState("session", "message", "block");
|
||||
await vi.waitFor(() => expect(fetcher).toHaveBeenCalledTimes(1)); subject = "bob"; finish(Response.json(remote));
|
||||
await expect(read).rejects.toMatchObject({ code: "ANSWER_VIEW_IDENTITY_CHANGED" });
|
||||
});
|
||||
it("rejects remote state containing unknown executable fields", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn(async () => Response.json({ ...remote, nodes: { x: { type: "Evidence", open: true, script: "bad" } } })));
|
||||
await expect(createAgentApiClient("https://agent.local").getAnswerViewState("session", "message", "block")).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user