Files
boss/tests/admin-backoffice-bff-route.test.ts

265 lines
8.0 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.ts");
let authCookie = "";
let getBackoffice: (typeof import("../src/app/api/v1/admin/backoffice/route.ts"))["GET"];
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-admin-backoffice-"));
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/backoffice/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getBackoffice = 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-30T10:00:00+08:00";
state.adminCompanies = [
{
companyId: "acme",
name: "Acme 科技",
ownerAccount: "owner@acme.com",
successOwnerAccount: "cs@boss.com",
planTier: "enterprise",
contractExpiresAt: "2027-04-30T00:00:00+08:00",
status: "active",
createdAt: now,
updatedAt: now,
},
];
state.authAccounts = [
{
id: "account-owner",
account: "owner@acme.com",
passwordHash: "do-not-leak-owner-password-hash",
displayName: "Acme 老板",
role: "highest_admin",
status: "active",
companyId: "acme",
mfaSecret: "do-not-leak-mfa-secret",
primaryDeviceId: "mac-1",
createdAt: now,
updatedAt: now,
lastLoginAt: now,
},
{
id: "account-dev",
account: "dev@acme.com",
passwordHash: "do-not-leak-dev-password-hash",
displayName: "开发同事",
role: "member",
status: "active",
companyId: "acme",
primaryDeviceId: "win-1",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.devices = [
{
id: "mac-1",
name: "Acme Mac Studio",
avatar: "A",
account: "owner@acme.com",
companyId: "acme",
source: "production",
status: "online",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-30T09:58:00+08:00",
preferredExecutionMode: "cli",
capabilities: {
gui: { connected: true, lastSeenAt: "2026-04-30T09:58:00+08:00" },
cli: { connected: true, lastSeenAt: "2026-04-30T09:58:00+08:00" },
browserAutomation: { connected: true, lastSeenAt: "2026-04-30T09:57:00+08:00" },
computerUse: { connected: true, lastSeenAt: "2026-04-30T09:57:00+08:00" },
},
},
{
id: "win-1",
name: "Acme Windows",
avatar: "W",
account: "dev@acme.com",
companyId: "acme",
source: "production",
status: "offline",
projects: ["project-acme"],
quota5h: 0,
quota7d: 0,
lastSeenAt: "2026-04-30T08: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.deviceSkills = [
{
skillId: "mac-1:boss-server-debug",
deviceId: "mac-1",
name: "boss-server-debug",
description: "Boss 服务器调试",
path: "/Users/kris/.codex/skills/boss-server-debug/SKILL.md",
invocation: "$boss-server-debug",
category: "运维",
updatedAt: now,
},
];
state.opsFaults = [
{
faultId: "fault-1",
faultKey: "LOCAL_AGENT.HEARTBEAT_FAILED",
severity: "warning",
status: "opened",
nodeId: "win-1",
serviceName: "local-agent",
projectId: "project-acme",
traceId: "trace-1",
runbookId: "runbook-agent",
firstSeenAt: "2026-04-30T08:10:00+08:00",
lastSeenAt: "2026-04-30T08:30:00+08:00",
summary: "Windows 节点心跳失败",
suggestedNextAction: "检查 local-agent",
autoRepairable: true,
},
];
state.permissionAuditLogs = [
{
auditId: "audit-1",
actorAccount: "owner@acme.com",
action: "grant.created",
targetAccount: "dev@acme.com",
deviceId: "mac-1",
permissions: ["device.view"],
detail: "授权设备只读",
createdAt: now,
},
];
state.adminRiskTimeline = [
{
eventId: "risk-event-1",
riskId: "ops-fault:fault-1",
companyId: "acme",
action: "risk.created",
actorAccount: "system",
note: "发现节点风险",
createdAt: now,
},
];
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/backoffice", {
headers: {
cookie: `${authCookie}=${session.sessionToken}`,
},
});
}
test("backoffice bff rejects non highest admin accounts", async () => {
await setup();
const response = await getBackoffice(await authedRequest("dev@acme.com", "member"));
assert.equal(response.status, 403);
});
test("backoffice bff exposes yudao style management contract without secrets", async () => {
await setup();
const response = await getBackoffice(await authedRequest("owner@acme.com", "highest_admin"));
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.deepEqual(
payload.menuTree.map((item: { label: string }) => item.label),
["工作台", "租户管理", "账号管理", "角色权限", "资源授权", "Skill 中心", "风险告警", "审计日志", "系统设置"],
);
assert.equal(payload.yudaoMapping.tenant, "adminCompanies");
assert.equal(payload.yudaoMapping.user, "authAccounts");
assert.equal(payload.yudaoMapping.role, "BOSS_PERMISSION_TEMPLATES");
assert.equal(payload.workbench.summary.companies >= 1, true);
assert.equal(
payload.tenants.some((tenant: { name: string }) => tenant.name === "Acme 科技"),
true,
);
assert.equal(payload.users[0].passwordHash, undefined);
assert.equal(payload.users[0].mfaSecret, undefined);
assert.equal(payload.roles.permissionTemplates.length >= 3, true);
assert.equal(payload.resourceGroups.devices.length, 2);
assert.equal(
payload.resourceGroups.projects.some((project: { name: string }) => project.name === "Acme 生产项目"),
true,
);
assert.equal(payload.resourceGroups.skills[0].name, "boss-server-debug");
assert.equal(payload.audit.permissionLogs.length, 1);
assert.equal(payload.audit.risks.length >= 1, true);
const serialized = JSON.stringify(payload);
assert.equal(serialized.includes("do-not-leak-owner-password-hash"), false);
assert.equal(serialized.includes("do-not-leak-mfa-secret"), false);
});