Files
boss/tests/master-agent-task-recovery-route.test.ts
2026-06-06 19:12:18 +08:00

125 lines
4.3 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";
import type { MasterAgentTask } from "../src/lib/boss-data";
let runtimeRoot = "";
let data: typeof import("../src/lib/boss-data.ts");
let authCookie = "";
let getRecovery: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/recovery/route.ts"))["GET"];
let postRecovery: (typeof import("../src/app/api/v1/master-agent/tasks/[taskId]/recovery/route.ts"))["POST"];
let baseState: Awaited<ReturnType<typeof import("../src/lib/boss-data.ts")["readState"]>>;
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-task-recovery-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [dataModule, authModule, routeModule] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/app/api/v1/master-agent/tasks/[taskId]/recovery/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getRecovery = routeModule.GET;
postRecovery = routeModule.POST;
baseState = structuredClone(await data.readState());
}
test.after(async () => {
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
});
function task(overrides: Partial<MasterAgentTask>): MasterAgentTask {
return {
taskId: "task-recoverable",
projectId: "project-1",
taskType: "conversation_reply",
requestMessageId: "msg-1",
requestText: "继续执行",
executionPrompt: "继续执行",
requestedBy: "Boss",
requestedByAccount: "owner@boss.com",
deviceId: "mac-1",
status: "running",
phase: "executor_starting",
requestedAt: "2026-06-06T08:00:00.000Z",
lastProgressAt: "2026-06-06T08:01:00.000Z",
attemptCount: 1,
maxAttempts: 2,
recoverable: true,
lastErrorCode: "EXECUTOR_START_FAILED",
...overrides,
};
}
async function authedRequest(method = "GET", body?: unknown) {
const session = await data.createAuthSession({
account: "owner@boss.com",
role: "highest_admin",
displayName: "Owner",
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/master-agent/tasks/task-recoverable/recovery", {
method,
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: body ? JSON.stringify(body) : undefined,
});
}
test.beforeEach(async () => {
await setup();
const state = structuredClone(baseState);
state.authAccounts = [
{
id: "account-owner",
account: "owner@boss.com",
passwordHash: "secret",
displayName: "Owner",
role: "highest_admin",
createdAt: "2026-06-06T08:00:00.000Z",
updatedAt: "2026-06-06T08:00:00.000Z",
},
];
state.masterAgentTasks = [task({})];
await data.writeState(state);
});
test("task recovery GET returns safe diagnosis", async () => {
const response = await getRecovery(
await authedRequest(),
{ params: Promise.resolve({ taskId: "task-recoverable" }) },
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.recovery.taskId, "task-recoverable");
assert.equal(payload.recovery.canRetry, true);
assert.equal(payload.recovery.safeNextAction, "retry");
assert.equal(payload.recovery.diagnosis.includes("executor_starting"), true);
});
test("task recovery POST retry requeues only recoverable pre-turn task", async () => {
const response = await postRecovery(
await authedRequest("POST", { action: "retry", reason: "executor recovered" }),
{ params: Promise.resolve({ taskId: "task-recoverable" }) },
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.equal(payload.task.status, "queued");
assert.equal(payload.task.phase, "queued");
assert.equal("requestText" in payload.task, false);
assert.equal("executionPrompt" in payload.task, false);
const state = await data.readState();
assert.equal(state.permissionAuditLogs.some((log) => log.action === "master_agent.task_retried"), true);
});