Files
boss/tests/desktop-dialog-guard-backend.test.ts

284 lines
9.2 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm } from "node:fs/promises";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data");
let completeTaskRoute: typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route");
let decisionRoute: typeof import("../src/app/api/v1/dialog-guard/interventions/[interventionId]/decision/route");
let events: typeof import("../src/lib/boss-events");
let authCookie = "";
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-dialog-guard-backend-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, completeRouteModule, decisionRouteModule, eventsModule, authModule] =
await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts"),
import("../src/app/api/v1/dialog-guard/interventions/[interventionId]/decision/route.ts"),
import("../src/lib/boss-events.ts"),
import("../src/lib/boss-auth.ts"),
]);
data = dataModule;
completeTaskRoute = completeRouteModule;
decisionRoute = decisionRouteModule;
events = eventsModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.masterAgentTasks = [];
state.permissionAuditLogs = [];
state.dialogGuardInterventions = [];
await data.writeState(state);
});
function deviceRequest(taskId: string, body: Record<string, unknown>) {
return new NextRequest(
`http://127.0.0.1:3000/api/v1/master-agent/tasks/${taskId}/complete`,
{
method: "POST",
headers: {
"content-type": "application/json",
"x-boss-device-token": "boss-mac-studio-token",
},
body: JSON.stringify(body),
},
);
}
async function authedDecisionRequest(interventionId: string, body: Record<string, unknown>) {
const session = await data.createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
return new NextRequest(
`http://127.0.0.1:3000/api/v1/dialog-guard/interventions/${interventionId}/decision`,
{
method: "POST",
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: JSON.stringify(body),
},
);
}
async function queueDesktopTask() {
const [requestMessage] = await data.appendProjectMessages({
projectId: "master-agent",
messages: [
{
senderLabel: "Boss 超级管理员",
body: "打开微信发送一句测试消息",
kind: "text",
},
],
});
return data.queueMasterAgentTask({
projectId: "master-agent",
taskType: "desktop_control",
requestMessageId: requestMessage.id,
requestText: "打开微信发送一句测试消息",
executionPrompt: "打开微信发送一句测试消息",
requestedBy: "Boss 超级管理员",
requestedByAccount: "krisolo",
deviceId: "mac-studio",
accountId: "openai-master",
accountLabel: "gpt-5.4-mini",
intentCategory: "desktop_control",
runtimeKind: "computer-use-runtime",
riskLevel: "medium",
confirmationPolicy: "light_confirm",
});
}
test("needs_user_action task complete creates pending dialog intervention audit log and realtime event", async () => {
await setup();
const task = await queueDesktopTask();
const seenEvents: Array<{
event: string;
payload: {
interventionId?: string;
projectId?: string;
appName?: string;
taskId?: string;
status?: string;
};
}> = [];
const unsubscribe = events.subscribeBossEvents((event, payload) => {
seenEvents.push({ event, payload });
});
try {
const response = await completeTaskRoute.POST(
deviceRequest(task.taskId, {
deviceId: "mac-studio",
status: "needs_user_action",
kind: "dialog_intervention_required",
requestId: "runtime-request-001",
dialogId: "dialog-wechat-send-confirm",
appName: "微信",
platform: "darwin",
risk: "high",
summary: "微信即将发送外部可见消息,需要用户确认。",
recommendedAction: "allow_once",
availableActions: ["allow_once", "deny", "cancel_task"],
}),
{ params: Promise.resolve({ taskId: task.taskId }) },
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.task.status, "needs_user_action");
const state = await data.readState();
const intervention = state.dialogGuardInterventions.at(0);
assert.ok(intervention, "expected dialog intervention to be persisted");
assert.equal(intervention.taskId, task.taskId);
assert.equal(intervention.dialogId, "dialog-wechat-send-confirm");
assert.equal(intervention.requestId, "runtime-request-001");
assert.equal(intervention.deviceId, "mac-studio");
assert.equal(intervention.projectId, "master-agent");
assert.equal(intervention.appName, "微信");
assert.equal(intervention.platform, "darwin");
assert.equal(intervention.risk, "high");
assert.equal(intervention.status, "pending");
assert.deepEqual(intervention.availableActions, ["allow_once", "deny", "cancel_task"]);
assert.equal(
state.permissionAuditLogs.some(
(log) =>
log.action === "dialog_guard.intervention_required" &&
log.deviceId === "mac-studio" &&
log.projectId === "master-agent" &&
log.requestId === "runtime-request-001",
),
true,
);
assert.equal(
seenEvents.some(
(item) =>
item.event === "desktop.dialog_guard.intervention_required" &&
item.payload.interventionId === intervention.interventionId &&
item.payload.projectId === "master-agent" &&
item.payload.appName === "微信" &&
item.payload.taskId === task.taskId &&
item.payload.status === "pending",
),
true,
);
} finally {
unsubscribe();
}
});
test("decision route resolves pending intervention writes audit log and emits resolved event", async () => {
await setup();
const task = await queueDesktopTask();
await completeTaskRoute.POST(
deviceRequest(task.taskId, {
deviceId: "mac-studio",
status: "needs_user_action",
kind: "dialog_intervention_required",
requestId: "runtime-request-002",
dialogId: "dialog-open-file",
appName: "Finder",
platform: "darwin",
risk: "medium",
summary: "Finder 要打开一个本地文件。",
recommendedAction: "allow_once",
availableActions: ["allow_once", "deny", "handled_on_device"],
}),
{ params: Promise.resolve({ taskId: task.taskId }) },
);
const pending = (await data.readState()).dialogGuardInterventions.at(0);
assert.ok(pending, "expected setup to create a pending intervention");
const seenEvents: Array<{
event: string;
payload: {
interventionId?: string;
projectId?: string;
decision?: string;
status?: string;
};
}> = [];
const unsubscribe = events.subscribeBossEvents((event, payload) => {
seenEvents.push({ event, payload });
});
try {
const response = await decisionRoute.POST(
await authedDecisionRequest(pending.interventionId, {
decision: "allow_once",
note: "本次允许。",
}),
{ params: Promise.resolve({ interventionId: pending.interventionId }) },
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.intervention.status, "resolved");
assert.equal(payload.intervention.decision, "allow_once");
const state = await data.readState();
const resolved = state.dialogGuardInterventions.find(
(item) => item.interventionId === pending.interventionId,
);
assert.equal(resolved?.status, "resolved");
assert.equal(resolved?.decision, "allow_once");
assert.ok(resolved?.resolvedAt);
assert.equal(
state.permissionAuditLogs.some(
(log) =>
log.action === "dialog_guard.intervention_resolved" &&
log.actorAccount === "krisolo" &&
log.deviceId === "mac-studio" &&
log.projectId === "master-agent" &&
log.requestId === "runtime-request-002",
),
true,
);
assert.equal(
seenEvents.some(
(item) =>
item.event === "desktop.dialog_guard.intervention_resolved" &&
item.payload.interventionId === pending.interventionId &&
item.payload.projectId === "master-agent" &&
item.payload.decision === "allow_once" &&
item.payload.status === "resolved",
),
true,
);
} finally {
unsubscribe();
}
});