Files
TJWaterAgent/tests/results/store.test.ts
T
jiang ce04704af2
Generic Container CI/CD / test-build-publish (push) Successful in 2m2s
Agent CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m2s
fix(agent): isolate conversation workspaces
2026-08-25 13:18:39 +08:00

310 lines
9.2 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { mkdir, mkdtemp, rm, stat, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { ResultReferenceResolver } from "../../src/results/resolver.js";
import {
RESULT_REFERENCE_KIND,
RESULT_REFERENCE_SOURCE,
ResultReferenceStore,
} from "../../src/results/store.js";
describe("ResultReferenceResolver", () => {
let tempDir: string;
let importRoot: string;
let conversationWorkspace: string;
let store: ResultReferenceStore;
let resolver: ResultReferenceResolver;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "tjwater-result-ref-"));
importRoot = join(tempDir, "conversation-workspaces");
conversationWorkspace = join(importRoot, "conversation-1");
await mkdir(conversationWorkspace, { recursive: true });
store = new ResultReferenceStore(join(tempDir, "refs"), 60_000);
resolver = new ResultReferenceResolver(store, importRoot, 1024 * 1024);
await store.initialize();
});
afterEach(async () => {
await rm(tempDir, { force: true, recursive: true });
});
it("stores metadata for render refs and resolves them", async () => {
const record = await resolver.register({
actorKey: "actor-1",
clientSessionId: "client-1",
data: {
node_area_map: {
J1: "DMA-1",
J2: "DMA-2",
},
},
kind: RESULT_REFERENCE_KIND.renderJunctionsPayload,
projectId: "project-1",
projectKey: "project-key-1",
schemaVersion: 1,
sessionId: "session-1",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-1",
});
expect(record.kind).toBe(RESULT_REFERENCE_KIND.renderJunctionsPayload);
expect(record.schemaVersion).toBe(1);
expect(record.source).toBe(RESULT_REFERENCE_SOURCE.agentGenerated);
const result = await resolver.getFullAuthorized(
record.resultRef,
{
actorKey: "actor-1",
projectId: "project-1",
},
);
expect(result).not.toBeNull();
expect(result?.kind).toBe(RESULT_REFERENCE_KIND.renderJunctionsPayload);
expect(result?.schema_version).toBe(1);
expect(result?.source).toBe(RESULT_REFERENCE_SOURCE.agentGenerated);
expect(result?.data).toEqual({
node_area_map: {
J1: "DMA-1",
J2: "DMA-2",
},
});
});
it("rejects malformed refs and auth mismatches", async () => {
const malformedRef = "res-bbbbbbbbbbbbbbbb";
await writeFile(
join(tempDir, `${malformedRef}.json`),
JSON.stringify(
{
resultRef: malformedRef,
createdAt: "2026-05-21T00:00:00.000Z",
data: { value: 1 },
preview: {
count: 1,
fields: ["value"],
sample: { value: 1 },
summary: "object<1 fields>",
},
projectId: "project-1",
projectKey: "project-key-1",
sessionId: "session-1",
sizeBytes: 10,
traceId: "trace-1",
},
null,
2,
),
"utf8",
);
const malformed = await store.getAuthorizedRecord(malformedRef, {
actorKey: "actor-1",
projectId: "project-1",
});
expect(malformed).toBeNull();
const renderRecord = await resolver.register({
actorKey: "actor-2",
clientSessionId: "client-2",
data: {
node_area_map: {
J1: "DMA-1",
},
},
kind: RESULT_REFERENCE_KIND.renderJunctionsPayload,
projectId: "project-2",
projectKey: "project-key-2",
schemaVersion: 1,
sessionId: "session-2",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-2",
});
const wrongActor = await resolver.getFullAuthorized(renderRecord.resultRef, {
actorKey: "actor-other",
projectId: "project-2",
});
expect(wrongActor).toBeNull();
});
it("registers render refs from local wrapper files and normalizes payloads", async () => {
const filePath = join(conversationWorkspace, "render-wrapper.json");
await writeFile(
filePath,
JSON.stringify(
{
metadata: {
createdAt: "2026-05-21T00:00:00.000Z",
projectId: "project-3",
},
location: {
file_path: filePath,
},
data: {
node_area_map: {
J1: "DMA-1",
J2: 2,
},
area_ids: ["DMA-1", " DMA-2 "],
area_colors: {
"DMA-1": "#ff0000",
"DMA-2": "#00ff00",
},
},
createdAt: "2026-05-21T00:00:00.000Z",
},
null,
2,
),
"utf8",
);
const record = await resolver.registerRenderPayloadFile(filePath, {
actorKey: "actor-3",
clientSessionId: "client-3",
projectId: "project-3",
projectKey: "project-key-3",
sessionId: "session-3",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-3",
workspaceDirectory: conversationWorkspace,
});
expect(record.kind).toBe(RESULT_REFERENCE_KIND.renderJunctionsPayload);
expect(record.source).toBe(RESULT_REFERENCE_SOURCE.agentGenerated);
const result = await resolver.getFullAuthorized(
record.resultRef,
{
actorKey: "actor-3",
projectId: "project-3",
},
{
expectedKind: RESULT_REFERENCE_KIND.renderJunctionsPayload,
},
);
expect(result?.data).toEqual({
node_area_map: {
J1: "DMA-1",
J2: "2",
},
area_ids: ["DMA-1", "DMA-2"],
area_colors: {
"DMA-1": "#ff0000",
"DMA-2": "#00ff00",
},
});
await expect(stat(filePath)).rejects.toThrow();
});
it("rejects render payload files outside the configured import directory", async () => {
const outsideDir = await mkdtemp(join(tmpdir(), "tjwater-result-outside-"));
const filePath = join(outsideDir, "render-wrapper.json");
await writeFile(
filePath,
JSON.stringify({
metadata: {},
location: { file_path: filePath },
data: { node_area_map: { J1: "DMA-1" } },
}),
"utf8",
);
try {
await expect(
resolver.registerRenderPayloadFile(filePath, {
actorKey: "actor-4",
clientSessionId: "client-4",
projectKey: "project-key-4",
sessionId: "session-4",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-4",
workspaceDirectory: outsideDir,
}),
).rejects.toThrow("RESULT_REF_IMPORT_DIR");
} finally {
await rm(outsideDir, { force: true, recursive: true });
}
});
it("rejects oversized render payload files before parsing", async () => {
const filePath = join(conversationWorkspace, "oversized.json");
await writeFile(filePath, "x".repeat(128), "utf8");
const sizeLimitedResolver = new ResultReferenceResolver(store, importRoot, 64);
await expect(
sizeLimitedResolver.registerRenderPayloadFile(filePath, {
actorKey: "actor-5",
clientSessionId: "client-5",
projectKey: "project-key-5",
sessionId: "session-5",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-5",
workspaceDirectory: conversationWorkspace,
}),
).rejects.toThrow("RESULT_REF_IMPORT_MAX_BYTES");
});
it("rejects render payload files owned by another conversation workspace", async () => {
const otherWorkspace = join(importRoot, "conversation-2");
await mkdir(otherWorkspace);
const filePath = join(otherWorkspace, "render-wrapper.json");
await writeFile(
filePath,
JSON.stringify({
metadata: {},
location: { file_path: filePath },
data: { node_area_map: { J1: "DMA-1" } },
}),
"utf8",
);
await expect(
resolver.registerRenderPayloadFile(filePath, {
actorKey: "actor-6",
clientSessionId: "client-6",
projectKey: "project-key-6",
sessionId: "session-6",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-6",
workspaceDirectory: conversationWorkspace,
}),
).rejects.toThrow("current conversation workspace");
});
it("rejects a conversation workspace that is itself a symbolic link", async () => {
const targetWorkspace = join(importRoot, "conversation-target");
const linkedWorkspace = join(importRoot, "conversation-linked");
await mkdir(targetWorkspace);
await symlink(targetWorkspace, linkedWorkspace, "dir");
const filePath = join(targetWorkspace, "render-wrapper.json");
await writeFile(
filePath,
JSON.stringify({
metadata: {},
location: { file_path: filePath },
data: { node_area_map: { J1: "DMA-1" } },
}),
"utf8",
);
await expect(
resolver.registerRenderPayloadFile(filePath, {
actorKey: "actor-7",
clientSessionId: "client-7",
projectKey: "project-key-7",
sessionId: "session-7",
source: RESULT_REFERENCE_SOURCE.agentGenerated,
traceId: "trace-7",
workspaceDirectory: linkedWorkspace,
}),
).rejects.toThrow("symbolic link");
});
});