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.ts"); let authCookie = ""; let getAdminOverview: (typeof import("../src/app/api/v1/admin/overview/route.ts"))["GET"]; let baseState: Awaited>; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-admin-overview-")); 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/admin/overview/route.ts"), ]); data = dataModule; authCookie = authModule.AUTH_SESSION_COOKIE; getAdminOverview = routeModule.GET; 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); const now = "2026-04-27T10:00:00+08:00"; state.authAccounts = [ { id: "account-owner", account: "owner@acme.com", passwordHash: "secret", displayName: "Acme 老板", role: "highest_admin", primaryDeviceId: "mac-1", createdAt: now, updatedAt: now, lastLoginAt: now, }, { id: "account-dev", account: "dev@acme.com", passwordHash: "secret", displayName: "开发同事", role: "member", primaryDeviceId: "win-1", createdAt: now, updatedAt: now, }, ]; state.authSessions = []; state.devices = [ { id: "mac-1", name: "Acme Mac Studio", avatar: "A", account: "owner@acme.com", source: "production", status: "online", projects: ["project-acme"], quota5h: 0, quota7d: 0, lastSeenAt: "2026-04-27T09:58:00+08:00", preferredExecutionMode: "cli", capabilities: { gui: { connected: true, lastSeenAt: "2026-04-27T09:58:00+08:00" }, cli: { connected: true, lastSeenAt: "2026-04-27T09:58:00+08:00" }, browserAutomation: { connected: false }, computerUse: { connected: false }, }, }, { id: "win-1", name: "Acme Windows", avatar: "W", account: "dev@acme.com", source: "production", status: "offline", projects: ["project-acme"], quota5h: 0, quota7d: 0, lastSeenAt: "2026-04-27T08:00:00+08:00", preferredExecutionMode: "gui", capabilities: { gui: { connected: false }, cli: { connected: false }, browserAutomation: { connected: false }, computerUse: { connected: false }, }, }, ]; state.projects = [ { id: "project-acme", name: "Acme 生产项目", pinned: false, deviceIds: ["mac-1", "win-1"], preview: "", updatedAt: now, lastMessageAt: now, isGroup: false, threadMeta: { projectId: "project-acme", threadId: "thread-acme", threadDisplayName: "Acme 线程", folderName: "acme", activityIconCount: 0, updatedAt: now, }, groupMembers: [], createdByAgent: false, collaborationMode: "development", approvalState: "not_required", unreadCount: 0, riskLevel: "low", messages: [], goals: [], versions: [], }, ]; state.opsFaults = [ { faultId: "fault-1", faultKey: "LOCAL_AGENT.HEARTBEAT_FAILED", severity: "critical", status: "opened", nodeId: "win-1", serviceName: "local-agent", projectId: "project-acme", traceId: "trace-1", runbookId: "runbook-agent", firstSeenAt: "2026-04-27T09:00:00+08:00", lastSeenAt: "2026-04-27T09:30:00+08:00", summary: "Windows 节点心跳失败", suggestedNextAction: "检查 local-agent", autoRepairable: true, }, ]; state.threadContextAlerts = [ { alertId: "alert-1", projectId: "project-acme", threadId: "thread-acme", alertType: "context_critical", alertStatus: "opened", openedAt: "2026-04-27T09:20:00+08:00", summary: "线程上下文接近耗尽", masterActions: ["先固化版本记录"], }, ]; state.threadContextSnapshots = [ { snapshotId: "snapshot-1", projectId: "project-acme", taskId: "task-context-1", threadId: "thread-acme", title: "Acme 线程", summary: "上下文接近耗尽", nodeId: "mac-1", workerId: "worker-1", sourceKind: "codex_sdk", status: "running", contextBudgetRemainingPct: 8, contextBudgetLevel: "critical", mustFinishBeforeCompaction: true, estimatedRemainingTurns: 1, estimatedRemainingLargeMessages: 0, compactionCount: 0, patchPending: true, testsPending: true, evidencePending: false, checklist: ["固化项目目标", "补版本记录"], capturedAt: "2026-04-27T09:19:00+08:00", }, ]; state.masterAgentTasks = [ { taskId: "task-failed-1", projectId: "project-acme", taskType: "conversation_reply", requestMessageId: "msg-1", requestText: "帮我继续开发", executionPrompt: "prompt", requestedBy: "owner@acme.com", requestedByAccount: "owner@acme.com", deviceId: "mac-1", status: "failed", requestedAt: "2026-04-27T09:10:00+08:00", completedAt: "2026-04-27T09:12:00+08:00", errorMessage: "Master Codex Node 执行失败", }, ]; state.accountDeviceGrants = [ { grantId: "grant-expired", account: "dev@acme.com", deviceId: "mac-1", permissions: ["device.view"], grantedBy: "owner@acme.com", grantedAt: "2026-04-20T10:00:00+08:00", expiresAt: "2026-04-21T10:00:00+08:00", }, ]; state.accountProjectGrants = []; state.accountSkillGrants = []; await data.writeState(state); }); async function authedRequest(account: string, role: "member" | "admin" | "highest_admin") { const session = await data.createAuthSession({ account, role, displayName: account, loginMethod: "password", }); return new NextRequest("http://127.0.0.1:3000/api/v1/admin/overview", { headers: { cookie: `${authCookie}=${session.sessionToken}` }, }); } test("admin overview requires highest admin", async () => { await setup(); const unauthorized = await getAdminOverview( new NextRequest("http://127.0.0.1:3000/api/v1/admin/overview"), ); assert.equal(unauthorized.status, 401); const forbidden = await getAdminOverview(await authedRequest("dev@acme.com", "member")); assert.equal(forbidden.status, 403); }); test("highest admin can read companies devices risks and grant summary", async () => { await setup(); const response = await getAdminOverview(await authedRequest("owner@acme.com", "highest_admin")); assert.equal(response.status, 200); assert.equal(response.headers.get("cache-control"), "private, no-store, max-age=0"); const payload = await response.json(); assert.equal(payload.ok, true); assert.equal(payload.summary.companies, 2); assert.equal(payload.summary.accounts, 3); assert.equal(payload.summary.devices, 2); assert.equal(payload.summary.onlineDevices, 1); assert.equal(payload.summary.openRisks, 4); assert.equal(payload.summary.criticalRisks, 2); const acme = payload.companies.find((company: { companyId: string }) => company.companyId === "acme.com"); assert.equal(acme.name, "acme.com"); assert.equal(acme.accountCount, 2); assert.equal(acme.adminCount, 1); assert.equal(acme.deviceCount, 2); assert.equal(acme.onlineDeviceCount, 1); assert.equal(acme.openRiskCount, 4); assert.equal(payload.accounts[0].passwordHash, undefined); const offlineRisk = payload.risks.find((risk: { kind: string }) => risk.kind === "device_offline"); assert.equal(offlineRisk.deviceId, "win-1"); assert.equal(offlineRisk.companyId, "acme.com"); assert.match(offlineRisk.title, /设备离线/); const failedTaskRisk = payload.risks.find((risk: { kind: string }) => risk.kind === "master_agent_task_failed"); assert.match(failedTaskRisk.detail, /Master Codex Node/); const winDevice = payload.devices.find((device: { id: string }) => device.id === "win-1"); assert.equal(winDevice.projectCount, 1); assert.equal(winDevice.codexGuiOnline, false); assert.equal(winDevice.codexCliOnline, false); assert.equal(winDevice.openRiskCount, 2); assert.deepEqual(payload.grantsSummary, { deviceGrants: 1, projectGrants: 0, skillGrants: 0, expiredGrants: 1, }); }); test("admin overview folds repeated master-agent task failures for the same device and project", async () => { const state = await data.readState(); const baseTask = state.masterAgentTasks[0]; assert.ok(baseTask); state.masterAgentTasks = [ baseTask, { ...baseTask, taskId: "task-failed-2", requestMessageId: "msg-2", requestedAt: "2026-04-27T09:14:00+08:00", completedAt: "2026-04-27T09:15:00+08:00", errorMessage: "Master Codex Node 执行失败", }, { ...baseTask, taskId: "task-failed-3", requestMessageId: "msg-3", requestedAt: "2026-04-27T09:16:00+08:00", completedAt: "2026-04-27T09:17:00+08:00", errorMessage: "Master Codex Node 执行失败", }, ]; await data.writeState(state); const response = await getAdminOverview(await authedRequest("owner@acme.com", "highest_admin")); assert.equal(response.status, 200); const payload = await response.json(); const masterTaskRisks = payload.risks.filter((risk: { kind: string }) => risk.kind === "master_agent_task_failed"); assert.equal(masterTaskRisks.length, 1); assert.match(masterTaskRisks[0].detail, /已折叠 2 条同类失败/); assert.equal(payload.summary.openRisks, 4); }); test("admin overview prefers explicit company assignment over account domain", async () => { const state = await data.readState(); const now = "2026-04-27T11:00:00+08:00"; state.adminCompanies = [ { companyId: "tenant-hyzq", name: "环宇智擎客户", ownerAccount: "owner@acme.com", status: "active", note: "企业客户", createdAt: now, updatedAt: now, }, ]; state.authAccounts = state.authAccounts.map((account) => ({ ...account, companyId: account.account === "dev@acme.com" ? "tenant-hyzq" : account.companyId, })); state.devices = state.devices.map((device) => ({ ...device, companyId: device.id === "win-1" ? "tenant-hyzq" : device.companyId, })); await data.writeState(state); const response = await getAdminOverview(await authedRequest("owner@acme.com", "highest_admin")); assert.equal(response.status, 200); const payload = await response.json(); const tenant = payload.companies.find((company: { companyId: string }) => company.companyId === "tenant-hyzq"); assert.equal(tenant.name, "环宇智擎客户"); assert.equal(tenant.accountCount, 1); assert.equal(tenant.deviceCount, 1); const devAccount = payload.accounts.find((account: { account: string }) => account.account === "dev@acme.com"); assert.equal(devAccount.companyId, "tenant-hyzq"); assert.equal(devAccount.companyName, "环宇智擎客户"); const winDevice = payload.devices.find((device: { id: string }) => device.id === "win-1"); assert.equal(winDevice.companyId, "tenant-hyzq"); assert.equal(winDevice.companyName, "环宇智擎客户"); const offlineRisk = payload.risks.find((risk: { kind: string }) => risk.kind === "device_offline"); assert.equal(offlineRisk.companyId, "tenant-hyzq"); });