83 lines
5.4 KiB
TypeScript
83 lines
5.4 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import express from "express";
|
|
|
|
import type { OperationalTaskClient } from "../../src/product/operational-task-client.js";
|
|
import { registerTaskPublicationBridge } from "../../src/routes/task-publication-bridge.js";
|
|
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
|
|
|
describe("task publication bridge", () => {
|
|
it("requires a trusted execution context and injects task identity without browser capabilities", async () => {
|
|
const taskId = "50000000-0000-4000-8000-000000000001";
|
|
const requestId = "60000000-0000-4000-8000-000000000001";
|
|
const contexts = ["task-publication-execution", "task-publication-collaboration", "task-publication-ordinary"];
|
|
const published: Array<{ kind: string; args: unknown[] }> = [];
|
|
contexts.forEach((sessionId, index) => setRuntimeSessionContext({
|
|
sessionId, clientSessionId: `client-${index}`, actorKey: "actor-1", projectKey: "project-1", traceId: "test",
|
|
taskId, purpose: ["task_execution", "task_collaboration", "ordinary_chat"][index] as "task_execution",
|
|
}));
|
|
const app = express();
|
|
app.use(express.json());
|
|
registerTaskPublicationBridge(app, { internalToken: "internal-test", client: {
|
|
publishArtifacts: async (...args: unknown[]) => {
|
|
published.push({ kind: "artifacts", args });
|
|
return { progress_revision: 1 };
|
|
},
|
|
publishPresentation: async (...args: unknown[]) => {
|
|
published.push({ kind: "presentation", args });
|
|
return { recorded: true, event_seq: 8, run_id: "55000000-0000-4000-8000-000000000001", execution_generation: 1 };
|
|
},
|
|
} as unknown as Pick<OperationalTaskClient, "publishArtifacts" | "publishPresentation"> });
|
|
const server = app.listen(0, "127.0.0.1");
|
|
await new Promise<void>((resolve) => server.once("listening", resolve));
|
|
const base = `http://127.0.0.1:${(server.address() as { port: number }).port}`;
|
|
const post = (kind: string, body: unknown, extraHeaders: Record<string, string> = {}) => fetch(`${base}/internal/operational-task/${kind}`, {
|
|
method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": "internal-test", ...extraHeaders },
|
|
body: JSON.stringify(body),
|
|
});
|
|
const progress = { summary: "核对实际资料", diagram: null };
|
|
const command = { action: "highlight_features", layer: "pipes", feature_ids: ["P-1"], effect: "pulse" };
|
|
try {
|
|
const body = { session_id: contexts[0], request_id: requestId, command };
|
|
expect((await post("presentation", body, { "x-agent-internal-token": "wrong" })).status).toBe(403);
|
|
expect((await post("presentation", body, { Origin: "http://browser.test" })).status).toBe(403);
|
|
expect((await post("presentation", { ...body, session_id: "unknown" })).status).toBe(403);
|
|
for (const sessionId of contexts.slice(1)) {
|
|
expect((await post("presentation", { ...body, session_id: sessionId })).status).toBe(403);
|
|
expect((await post("artifacts", { session_id: sessionId, request_id: requestId, progress })).status).toBe(403);
|
|
}
|
|
expect((await post("presentation", { ...body, run_id: "model-supplied-run" })).status).toBe(400);
|
|
expect((await post("presentation", { ...body, command: {
|
|
action: "set_overlay", overlay_id: "o1", title: "位置", mode: "geometry", evidence_id: "e1", data: { coordinates: [1, 2] },
|
|
} })).status).toBe(400);
|
|
expect(published).toHaveLength(0);
|
|
const result = await post("presentation", body);
|
|
expect(result.status).toBe(200);
|
|
expect(await result.json()).toMatchObject({ recorded: true, event_seq: 8 });
|
|
expect(published[0]).toEqual({ kind: "presentation", args: [contexts[0], taskId, {
|
|
requestId, sourceSessionId: "client-0", command,
|
|
}] });
|
|
expect((await post("artifacts", { session_id: contexts[0], request_id: requestId, progress })).status).toBe(200);
|
|
expect(published[1]?.args).toEqual([contexts[0], taskId, {
|
|
requestId, sourceSessionId: "client-0", evidence: undefined, executionPlan: undefined, report: undefined, progress,
|
|
}]);
|
|
const report = { title: "报告", current_conclusion: "核验结束", conclusion_boundary: "授权数据范围",
|
|
stage: "final", outcome: "concluded", stop_reason: null, data_gaps: [] };
|
|
expect((await post("artifacts", { session_id: contexts[1], request_id: requestId, report })).status).toBe(200);
|
|
const interactive = { ...report, evidence_refs: ["e1"], ui_blocks: [{ evidence_refs: ["e1"], block: {
|
|
version: "answer-ui@1", block_id: "summary", fallback_text: "已保存的演练记录。", spec: {
|
|
root: "text", elements: { text: { type: "Text", children: [], props: { text: "已保存的演练记录。", tone: "neutral" } } },
|
|
},
|
|
} }] };
|
|
expect((await post("artifacts", { session_id: contexts[0], request_id: requestId, report: interactive })).status).toBe(200);
|
|
expect(published.at(-1)?.args[2]).toMatchObject({ report: { ui_blocks: interactive.ui_blocks }, sourceSessionId: "client-0" });
|
|
const count = published.length;
|
|
expect((await post("artifacts", { session_id: contexts[0], request_id: requestId,
|
|
report: { ...interactive, evidence_refs: [] } })).status).toBe(400);
|
|
expect(published).toHaveLength(count);
|
|
} finally {
|
|
contexts.forEach(removeRuntimeSessionContext);
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
}
|
|
});
|
|
});
|