Files
boss/tests/admin-company-lifecycle-route.test.ts

289 lines
9.9 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 authCookie = "";
let getAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["GET"];
let postAdminAccess: (typeof import("../src/app/api/v1/admin/access/route"))["POST"];
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-admin-company-"));
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/access/route.ts"),
]);
data = dataModule;
authCookie = authModule.AUTH_SESSION_COOKIE;
getAdminAccess = routeModule.GET;
postAdminAccess = routeModule.POST;
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-27T14:00:00+08:00";
state.authAccounts = [
{
id: "account-owner",
account: "owner@platform.com",
passwordHash: "secret",
displayName: "平台老板",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-worker",
account: "worker@acme.com",
passwordHash: "secret",
displayName: "待分配成员",
role: "member",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.devices = [
{
id: "mac-1",
name: "客户 Mac",
avatar: "M",
account: "worker@acme.com",
source: "production",
status: "online",
projects: [],
quota5h: 0,
quota7d: 0,
lastSeenAt: now,
preferredExecutionMode: "cli",
capabilities: {
gui: { connected: true, lastSeenAt: now },
cli: { connected: true, lastSeenAt: now },
browserAutomation: { connected: false },
computerUse: { connected: false },
},
},
];
state.accountDeviceGrants = [
{
grantId: "grant-device",
account: "worker@acme.com",
deviceId: "mac-1",
permissions: ["device.view"],
grantedBy: "owner@platform.com",
grantedAt: now,
},
];
state.accountProjectGrants = [];
state.accountSkillGrants = [];
state.permissionAuditLogs = [];
await data.writeState(state);
});
async function authedRequest(body?: Record<string, unknown>, method = "POST") {
const session = await data.createAuthSession({
account: "owner@platform.com",
role: "highest_admin",
displayName: "平台老板",
loginMethod: "password",
});
return new NextRequest("http://127.0.0.1:3000/api/v1/admin/access", {
method,
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
},
body: body ? JSON.stringify(body) : undefined,
});
}
async function adminPost(body: Record<string, unknown>) {
return postAdminAccess(await authedRequest(body));
}
test("highest admin can manage companies and assign accounts and devices", async () => {
const createCompany = await adminPost({
action: "upsert_company",
companyId: "acme",
name: "Acme 客户",
ownerAccount: "owner@acme.com",
successOwnerAccount: "cs@platform.com",
planTier: "enterprise",
contractExpiresAt: "2026-12-31T23:59:59+08:00",
note: "第一批 To B 客户",
});
assert.equal(createCompany.status, 200);
const createPayload = await createCompany.json();
assert.equal(createPayload.company.companyId, "acme");
assert.equal(createPayload.company.name, "Acme 客户");
assert.equal(createPayload.company.planTier, "enterprise");
assert.equal(createPayload.company.successOwnerAccount, "cs@platform.com");
assert.equal(createPayload.company.contractExpiresAt, "2026-12-31T23:59:59+08:00");
const assignAccount = await adminPost({
action: "assign_account_company",
account: "worker@acme.com",
companyId: "acme",
});
assert.equal(assignAccount.status, 200);
assert.equal((await assignAccount.json()).account.companyId, "acme");
const assignDevice = await adminPost({
action: "assign_device_company",
deviceId: "mac-1",
companyId: "acme",
});
assert.equal(assignDevice.status, 200);
assert.equal((await assignDevice.json()).device.companyId, "acme");
const getResponse = await getAdminAccess(await authedRequest(undefined, "GET"));
const getPayload = await getResponse.json();
assert.equal(getPayload.companies.some((company: { companyId: string }) => company.companyId === "acme"), true);
assert.equal(getPayload.companies.find((company: { companyId: string }) => company.companyId === "acme")?.planTier, "enterprise");
assert.equal(getPayload.accounts.find((account: { account: string }) => account.account === "worker@acme.com")?.companyId, "acme");
assert.equal(getPayload.devices.find((device: { id: string }) => device.id === "mac-1")?.companyId, "acme");
});
test("highest admin can toggle account MFA without exposing the secret in access GET", async () => {
const response = await adminPost({
action: "set_account_mfa_required",
account: "worker@acme.com",
required: true,
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.mfaRequired, true);
assert.equal(typeof payload.mfaSetupSecret, "string");
assert.equal(payload.account.mfaSecret, undefined);
const getResponse = await getAdminAccess(await authedRequest(undefined, "GET"));
const getPayload = await getResponse.json();
const account = getPayload.accounts.find((item: { account: string }) => item.account === "worker@acme.com");
assert.equal(account.mfaRequired, true);
assert.equal(account.mfaSecret, undefined);
});
test("admin access GET returns safe project metadata without chat transcripts", async () => {
const state = await data.readState();
const now = "2026-04-27T14:05:00+08:00";
state.projects = [
{
id: "project-secret",
name: "敏感项目",
pinned: false,
deviceIds: ["mac-1"],
preview: "passwordHash should not leave through admin access",
updatedAt: now,
lastMessageAt: now,
isGroup: false,
threadMeta: {
projectId: "project-secret",
threadId: "thread-secret",
threadDisplayName: "敏感线程",
folderName: "secret",
activityIconCount: 1,
updatedAt: now,
},
groupMembers: [],
createdByAgent: false,
collaborationMode: "development",
approvalState: "not_required",
unreadCount: 0,
riskLevel: "low",
messages: [
{
id: "msg-secret",
role: "assistant",
author: "线程",
body: "passwordHash=secret should stay in project transcript only",
createdAt: now,
},
],
goals: [],
versions: [],
},
];
await data.writeState(state);
const response = await getAdminAccess(await authedRequest(undefined, "GET"));
assert.equal(response.status, 200);
const payload = await response.json();
const project = payload.projects.find((item: { id: string }) => item.id === "project-secret");
assert.equal(project.name, "敏感项目");
assert.deepEqual(project.deviceIds, ["mac-1"]);
assert.equal(project.messages, undefined);
assert.equal(project.preview, undefined);
assert.equal(JSON.stringify(payload.accounts).includes("passwordHash"), false);
assert.equal(JSON.stringify(payload.projects).includes("passwordHash"), false);
});
test("highest admin can bulk import accounts into a company", async () => {
await adminPost({ action: "upsert_company", companyId: "acme", name: "Acme 客户" });
const response = await adminPost({
action: "bulk_import_accounts",
companyId: "acme",
accounts: [
{ account: "pm@acme.com", displayName: "产品负责人", role: "admin", password: "pass-123" },
{ account: "dev2@acme.com", displayName: "开发二号", role: "member", password: "pass-456" },
],
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.imported.length, 2);
assert.equal(payload.imported.every((account: { passwordHash?: string }) => account.passwordHash === undefined), true);
const state = await data.readState();
assert.deepEqual(
state.authAccounts
.filter((account) => account.companyId === "acme")
.map((account) => account.account)
.sort(),
["dev2@acme.com", "pm@acme.com"],
);
});
test("highest admin can reclaim a leaving account and clear its grants and sessions", async () => {
const workerSession = await data.createAuthSession({
account: "worker@acme.com",
role: "member",
displayName: "待分配成员",
loginMethod: "password",
});
const response = await adminPost({
action: "reclaim_account",
account: "worker@acme.com",
reason: "员工离职",
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.status, "disabled");
assert.equal(payload.removedGrants.deviceGrants, 1);
const state = await data.readState();
assert.equal(state.authAccounts.find((account) => account.account === "worker@acme.com")?.status, "disabled");
assert.equal(state.accountDeviceGrants.some((grant) => grant.account === "worker@acme.com"), false);
assert.equal(
state.authSessions.find((session) => session.sessionToken === workerSession.sessionToken)?.revokedAt !== undefined,
true,
);
assert.equal(state.permissionAuditLogs.at(0)?.action, "account.reclaimed");
});