Files
next-tjwater-agent/tests/routes/task-publication-bridge.test.ts
jiang 7d47a2df86
Generic Container CI/CD / test-build-publish (push) Failing after 8m25s
Agent CI/CD / build-test-publish-and-deploy (push) Failing after 8m25s
feat: add scoped history and artifacts
2026-10-22 18:40:20 +08:00

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()));
}
});
});