Files
next-tjwater-agent/tests/routes/permissionPersistence.test.ts
T
jiang 5ee93329a4
Generic Container CI/CD / test-build-publish (push) Successful in 2m4s
Agent CI/CD / build-test-publish-and-deploy (push) Successful in 2m4s
refactor: streamline query requests and session storage
2026-11-09 18:49:32 +08:00

59 lines
4.1 KiB
TypeScript

import { createChatRunAccumulator } from "../../src/routes/chat-run-accumulator.js";
import { expect, it } from "bun:test";
import express from "express";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getLocalAgentContext } from "../../src/context/localContext.js";
import { registerChatInteractionRoutes } from "../../src/routes/chatInteractionRoutes.js";
import type { ActiveRun } from "../../src/routes/chatUiState.js";
import type { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
import { SessionMetadataStore } from "../../src/sessions/metadataStore.js";
import { SessionUiStateStore } from "../../src/sessions/uiStateStore.js";
import { SessionWorkspaceStore } from "../../src/sessions/workspace-store.js";
it.each([false, true])("persists permission replies in the owner's workspace (native failure=%s)", async (nativeFailure) => {
const root = await mkdtemp(join(tmpdir(), "permission-persistence-"));
const workspaces = new SessionWorkspaceStore(join(root, "workspaces"));
const sessions = new SessionMetadataStore(join(root, "metadata"), workspaces);
const states = new SessionUiStateStore(workspaces);
const context = { ...getLocalAgentContext(), sessionId: "permission-session" };
await sessions.ensure(context);
const messages = [{ id: "answer", role: "assistant", permissions: [] }];
await states.write(context, { sessionId: context.sessionId, isTitleManuallyEdited: true, messages });
let calls = 0;
const runtime = { replyPermission: async () => { calls++; if (nativeFailure) throw new Error("native unavailable"); } } as unknown as OpencodeRuntimeAdapter;
const run: ActiveRun = {
clientSessionId: context.sessionId, controller: new AbortController(), messages,
pendingPermissions: new Map([["permission", { request_id: "permission", session_id: context.sessionId, permission: "shell", patterns: [], always: [], created_at: Date.now() }]]),
pendingQuestions: new Map(), subscribers: new Set(), status: "running",
};
const accumulator = createChatRunAccumulator({ run, messages: [], messageId: "answer", sessionId: context.sessionId,
collaboration: false, write() {}, onStatus() {} });
accumulator.apply("permission_request", run.pendingPermissions.get("permission")! as unknown as Record<string, unknown>);
await states.write(context, { sessionId: context.sessionId, isTitleManuallyEdited: true, messages: run.messages });
const app = express(); app.use(express.json());
registerChatInteractionRoutes(app, { activeRuns: new Map([[context.sessionId, run]]), runtime, sessionMetadataStore: sessions, sessionUiStateStore: 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}/permission/permission/reply`;
const reply = (headers = {}) => fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...headers }, body: JSON.stringify({ session_id: context.sessionId, reply: "once" }) });
try {
expect((await reply({ "X-Agent-Local-User": "other" })).status).toBe(404);
expect(calls).toBe(0);
expect((await reply()).status).toBe(nativeFailure ? 502 : 202);
expect(calls).toBe(1);
expect(run.pendingPermissions.has("permission")).toBe(nativeFailure);
const saved = await states.read(context);
expect(saved?.isTitleManuallyEdited).toBe(true);
const parts = (saved?.messages[0] as { parts: Array<{ type: string; data: Record<string, unknown> }> }).parts;
expect(parts.find((part) => part.type === "data-permission_response")?.data).toMatchObject(nativeFailure
? { request_id: "permission", status: "error", error: "native unavailable" }
: { request_id: "permission", reply: "once" });
expect(run.messages[0]).toMatchObject({ permissions: [{ requestId: "permission", status: nativeFailure ? "error" : "approved_once" }] });
} finally {
await new Promise<void>((resolve) => server.close(() => resolve()));
await rm(root, { recursive: true, force: true });
}
});