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 getMessagesRoute: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["GET"]; let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"]; let readState: (typeof import("../src/lib/boss-data"))["readState"]; let writeState: (typeof import("../src/lib/boss-data"))["writeState"]; let AUTH_SESSION_COOKIE = ""; let baseState: Awaited>; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-project-messages-route-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [messageModule, data, auth] = await Promise.all([ import("../src/app/api/v1/projects/[projectId]/messages/route.ts"), import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), ]); getMessagesRoute = messageModule.GET; createAuthSession = data.createAuthSession; readState = data.readState; writeState = data.writeState; baseState = structuredClone(await readState()); AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE; } test.after(async () => { if (runtimeRoot) { await rm(runtimeRoot, { recursive: true, force: true }); } }); test.beforeEach(async () => { await setup(); await writeState(structuredClone(baseState)); }); function buildSingleThreadProject(projectId: string) { return { id: projectId, name: "轻量消息线程", pinned: false, systemPinned: false, deviceIds: ["device-message-lite"], preview: "等待增量刷新。", updatedAt: "2026-04-10T16:20:00+08:00", lastMessageAt: "2026-04-10T16:20:00+08:00", isGroup: false, threadMeta: { projectId, threadId: "thread-message-lite", threadDisplayName: "轻量消息线程", folderName: "Boss", activityIconCount: 0, updatedAt: "2026-04-10T16:20:00+08:00", codexThreadRef: "thread-message-lite", codexFolderRef: "boss", }, groupMembers: [], createdByAgent: true, collaborationMode: "development" as const, approvalState: "not_required" as const, unreadCount: 0, riskLevel: "low" as const, messages: [ { id: "message-lite-1", sender: "assistant", senderLabel: "Codex", body: "新的消息已经到了。", kind: "text" as const, sentAt: "2026-04-10T16:20:00+08:00", }, ], goals: [], versions: [], }; } async function createAuthedRequest(projectId: string) { const session = await createAuthSession({ account: "17600003315", role: "highest_admin", displayName: "Boss 超级管理员", loginMethod: "password", }); return new NextRequest(`http://127.0.0.1:3000/api/v1/projects/${projectId}/messages`, { method: "GET", headers: { cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`, }, }); } test("GET /api/v1/projects/[projectId]/messages returns a lightweight chat payload", async () => { await setup(); const state = await readState(); const project = buildSingleThreadProject("message-lite"); await writeState({ ...state, devices: state.devices.concat({ id: "device-message-lite", name: "Mac Studio", avatar: "M", account: "17600003315", source: "production", status: "online", projects: [project.id], quota5h: 0, quota7d: 0, lastSeenAt: "2026-04-10T16:20:00+08:00", note: "", }), projects: state.projects.concat(project), }); const response = await getMessagesRoute( await createAuthedRequest(project.id), { params: Promise.resolve({ projectId: project.id }) }, ); assert.equal(response.status, 200); assert.equal(response.headers.get("Cache-Control"), "private, no-store, max-age=0"); const payload = (await response.json()) as { ok: boolean; project: { id: string; messages: Array<{ id: string }> }; devices: Array<{ id: string }>; conversationTasks: Array<{ taskId: string; requestMessageId: string; status: string; sessionId?: string; requestId?: string; }>; executionWarnings: Array; activeThreadContexts?: unknown; recentAppLogs?: unknown; openFaults?: unknown; }; assert.equal(payload.ok, true); assert.equal(payload.project.id, project.id); assert.deepEqual( payload.project.messages.map((message) => message.id), ["message-lite-1"], ); assert.deepEqual( payload.devices.map((device) => device.id), ["device-message-lite"], ); assert.deepEqual(payload.conversationTasks, []); assert.deepEqual(payload.executionWarnings, []); assert.equal("activeThreadContexts" in payload, false); assert.equal("recentAppLogs" in payload, false); assert.equal("openFaults" in payload, false); }); test("GET /api/v1/projects/[projectId]/messages includes current-project conversation task summaries with request/session ids", async () => { await setup(); const state = await readState(); const project = buildSingleThreadProject("message-lite-tasks"); await writeState({ ...state, devices: state.devices.concat({ id: "device-message-lite", name: "Mac Studio", avatar: "M", account: "17600003315", source: "production", status: "online", projects: [project.id], quota5h: 0, quota7d: 0, lastSeenAt: "2026-04-10T16:20:00+08:00", note: "", }), projects: state.projects.concat(project), masterAgentTasks: state.masterAgentTasks.concat( { taskId: "task-message-lite-1", projectId: project.id, taskType: "conversation_reply", requestMessageId: "message-lite-1", requestText: "新的消息已经到了。", executionPrompt: "请继续回复。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", deviceId: "master-agent-hermes", accountId: "hermes-runtime", accountLabel: "Hermes Runtime", targetProjectId: project.id, targetThreadId: "thread-message-lite", targetThreadDisplayName: "轻量消息线程", status: "completed", requestedAt: "2026-04-10T16:20:01+08:00", completedAt: "2026-04-10T16:20:05+08:00", replyBody: "Hermes 已完成回复。", requestId: "req-message-lite-1", sessionId: "session-message-lite-1", }, { taskId: "task-message-lite-hidden", projectId: project.id, taskType: "conversation_reply", requestMessageId: "missing-message-id", requestText: "这条不应暴露", executionPrompt: "内部同步", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", deviceId: "master-agent-hermes", accountId: "hermes-runtime", accountLabel: "Hermes Runtime", targetProjectId: project.id, targetThreadId: "thread-message-lite", targetThreadDisplayName: "轻量消息线程", status: "completed", requestedAt: "2026-04-10T16:20:02+08:00", completedAt: "2026-04-10T16:20:06+08:00", replyBody: "内部同步回复", sessionId: "session-hidden", }, ), }); const response = await getMessagesRoute( await createAuthedRequest(project.id), { params: Promise.resolve({ projectId: project.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; conversationTasks: Array<{ taskId: string; requestMessageId: string; status: string; sessionId?: string; requestId?: string; }>; }; assert.equal(payload.ok, true); assert.deepEqual(payload.conversationTasks, [ { taskId: "task-message-lite-1", requestMessageId: "message-lite-1", status: "completed", requestId: "req-message-lite-1", sessionId: "session-message-lite-1", targetProjectId: project.id, targetThreadId: "thread-message-lite", }, ]); }); test("GET /api/v1/projects/[projectId]/messages disables caching when unauthorized", async () => { await setup(); const response = await getMessagesRoute( new NextRequest("http://127.0.0.1:3000/api/v1/projects/message-lite/messages"), { params: Promise.resolve({ projectId: "message-lite" }) }, ); assert.equal(response.status, 401); assert.equal(response.headers.get("Cache-Control"), "private, no-store, max-age=0"); }); test("GET /api/v1/projects/[projectId]/messages includes execution warnings keyed by request/session/task", async () => { await setup(); const state = await readState(); const project = buildSingleThreadProject("message-lite-warnings"); await writeState({ ...state, devices: state.devices.concat({ id: "device-message-lite", name: "Mac Studio", avatar: "M", account: "17600003315", source: "production", status: "online", projects: [project.id], quota5h: 0, quota7d: 0, lastSeenAt: "2026-04-10T16:20:00+08:00", note: "", }), projects: state.projects.concat(project), masterAgentTasks: state.masterAgentTasks.concat({ taskId: "task-message-warning-1", projectId: project.id, taskType: "conversation_reply", requestMessageId: "message-lite-1", requestText: "新的消息已经到了。", executionPrompt: "请继续回复。", requestedBy: "Boss 超级管理员", requestedByAccount: "17600003315", deviceId: "master-agent-hermes", accountId: "hermes-runtime", accountLabel: "Hermes Runtime", targetProjectId: project.id, targetThreadId: "thread-message-lite", targetThreadDisplayName: "轻量消息线程", status: "completed", requestedAt: "2026-04-10T16:20:01+08:00", completedAt: "2026-04-10T16:20:05+08:00", replyBody: "Hermes 已完成回复。", requestId: "req-message-warning-1", sessionId: "session-message-warning-1", }), threadExecutionWarnings: state.threadExecutionWarnings.concat( { warningId: "thread-warning-1", taskId: "task-message-warning-1", requestMessageId: "message-lite-1", projectId: project.id, targetProjectId: project.id, targetThreadId: "thread-message-lite", sessionId: "session-message-warning-1", requestId: "req-message-warning-1", title: "上下文即将溢出", summary: "本次回复已接近上下文上限,建议尽快压缩。", createdAt: "2026-04-10T16:20:06+08:00", }, { warningId: "thread-warning-other", taskId: "task-other", requestMessageId: "other-message", projectId: "other-project", targetProjectId: "other-project", targetThreadId: "thread-other", sessionId: "session-other", requestId: "req-other", title: "其他线程 warning", summary: "不应出现在当前项目。", createdAt: "2026-04-10T16:20:07+08:00", }, ), }); const response = await getMessagesRoute( await createAuthedRequest(project.id), { params: Promise.resolve({ projectId: project.id }) }, ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; executionWarnings: Array<{ warningId: string; taskId: string; requestMessageId: string; sessionId?: string; requestId?: string; targetProjectId?: string; targetThreadId?: string; title: string; summary: string; createdAt: string; }>; }; assert.equal(payload.ok, true); assert.deepEqual(payload.executionWarnings, [ { warningId: "thread-warning-1", taskId: "task-message-warning-1", requestMessageId: "message-lite-1", sessionId: "session-message-warning-1", requestId: "req-message-warning-1", targetProjectId: project.id, targetThreadId: "thread-message-lite", title: "上下文即将溢出", summary: "本次回复已接近上下文上限,建议尽快压缩。", createdAt: "2026-04-10T16:20:06+08:00", }, ]); });