Files
boss/tests/admin-enterprise-ops-route.test.ts

212 lines
6.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 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-enterprise-ops-"));
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;
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-27T18:00:00+08:00";
state.adminCompanies = [
{
companyId: "tenant-a",
name: "Tenant A",
status: "active",
ownerAccount: "admin@tenant-a.com",
createdAt: now,
updatedAt: now,
},
];
state.authAccounts = [
{
id: "account-owner",
account: "owner@platform.com",
passwordHash: data.hashPassword("OwnerPass123"),
displayName: "平台老板",
role: "highest_admin",
createdAt: now,
updatedAt: now,
},
{
id: "account-admin",
account: "admin@tenant-a.com",
passwordHash: data.hashPassword("OldPass123"),
displayName: "客户管理员",
role: "admin",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
{
id: "account-worker",
account: "worker@tenant-a.com",
passwordHash: data.hashPassword("WorkerPass123"),
displayName: "客户成员",
role: "member",
companyId: "tenant-a",
createdAt: now,
updatedAt: now,
},
];
state.authSessions = [];
state.permissionAuditLogs = [];
await data.writeState(state);
});
async function authedRequest(body: Record<string, unknown>, extraHeaders: Record<string, string> = {}) {
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: "POST",
headers: {
"content-type": "application/json",
cookie: `${authCookie}=${session.sessionToken}`,
...extraHeaders,
},
body: JSON.stringify(body),
});
}
async function adminPost(body: Record<string, unknown>) {
return postAdminAccess(await authedRequest(body));
}
async function adminPostWithHeaders(body: Record<string, unknown>, headers: Record<string, string>) {
return postAdminAccess(await authedRequest(body, headers));
}
test("bulk import preview reports create and update rows without mutating accounts", async () => {
const response = await adminPost({
action: "preview_bulk_import_accounts",
companyId: "tenant-a",
accounts: [
{ account: "worker@tenant-a.com", displayName: "现有成员", role: "member" },
{ account: "new@tenant-a.com", displayName: "新成员", role: "admin" },
],
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.preview.summary.create, 1);
assert.equal(payload.preview.summary.update, 1);
assert.deepEqual(
payload.preview.rows.map((row: { account: string; operation: string }) => [row.account, row.operation]),
[
["worker@tenant-a.com", "update"],
["new@tenant-a.com", "create"],
],
);
const state = await data.readState();
assert.equal(state.authAccounts.some((account) => account.account === "new@tenant-a.com"), false);
});
test("highest admin can reset a child account password and revoke old sessions", async () => {
const oldSession = await data.loginAccount({
account: "worker@tenant-a.com",
password: "WorkerPass123",
method: "password",
});
const response = await adminPostWithHeaders(
{
action: "reset_account_password",
account: "worker@tenant-a.com",
password: "NewWorkerPass456",
},
{
"x-forwarded-for": "203.0.113.10, 10.0.0.1",
"user-agent": "BossAdminTest/1.0",
"x-request-id": "req-reset-001",
},
);
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.account.account, "worker@tenant-a.com");
assert.equal(payload.account.passwordHash, undefined);
await assert.rejects(
data.loginAccount({ account: "worker@tenant-a.com", password: "WorkerPass123", method: "password" }),
/INVALID_ACCOUNT_OR_PASSWORD/,
);
const login = await data.loginAccount({
account: "worker@tenant-a.com",
password: "NewWorkerPass456",
method: "password",
});
assert.equal(login.account, "worker@tenant-a.com");
assert.equal(await data.getAuthSession(oldSession.sessionToken), null);
const audit = (await data.readState()).permissionAuditLogs.at(0);
assert.equal(audit?.action, "account.password_reset");
assert.equal(audit?.ipAddress, "203.0.113.10");
assert.equal(audit?.userAgent, "BossAdminTest/1.0");
assert.equal(audit?.requestId, "req-reset-001");
assert.deepEqual(audit?.beforeJson, { account: "worker@tenant-a.com", status: "active", activeSessions: 1 });
assert.deepEqual(audit?.afterJson, { account: "worker@tenant-a.com", status: "active", revokedSessions: 1 });
});
test("disabling a company disables child accounts and revokes their sessions", async () => {
const workerSession = await data.createAuthSession({
account: "worker@tenant-a.com",
role: "member",
displayName: "客户成员",
loginMethod: "password",
});
const response = await adminPost({
action: "set_company_status",
companyId: "tenant-a",
status: "disabled",
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.company.status, "disabled");
assert.equal(payload.disabledAccounts, 2);
const state = await data.readState();
assert.equal(state.adminCompanies.find((company) => company.companyId === "tenant-a")?.status, "disabled");
assert.equal(
state.authAccounts
.filter((account) => account.companyId === "tenant-a")
.every((account) => account.status === "disabled"),
true,
);
assert.equal(
state.authSessions.find((session) => session.sessionToken === workerSession.sessionToken)?.revokedAt !== undefined,
true,
);
assert.equal(state.permissionAuditLogs.at(0)?.action, "company.status_updated");
});