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 openAiOnboardRoute: (typeof import("../src/app/api/v1/accounts/onboard/openai-api/route"))["POST"]; let masterNodeOnboardRoute: (typeof import("../src/app/api/v1/accounts/onboard/master-node/route"))["POST"]; let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"]; let readState: (typeof import("../src/lib/boss-data"))["readState"]; let AUTH_SESSION_COOKIE: string; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-ai-onboard-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [openAiModule, masterNodeModule, data, auth] = await Promise.all([ import("../src/app/api/v1/accounts/onboard/openai-api/route.ts"), import("../src/app/api/v1/accounts/onboard/master-node/route.ts"), import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), ]); openAiOnboardRoute = openAiModule.POST; masterNodeOnboardRoute = masterNodeModule.POST; createAuthSession = data.createAuthSession; readState = data.readState; AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE; } test.after(async () => { if (runtimeRoot) { await rm(runtimeRoot, { recursive: true, force: true }); } }); async function createAuthedJsonRequest(url: string, body: Record) { const session = await createAuthSession({ account: "17600003315", role: "highest_admin", displayName: "Boss 超级管理员", loginMethod: "password", }); return new NextRequest(url, { method: "POST", headers: { "content-type": "application/json", cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`, }, body: JSON.stringify(body), }); } test("POST /api/v1/accounts/onboard/openai-api creates a primary openai account and activates it", async () => { await setup(); const originalFetch = globalThis.fetch; globalThis.fetch = (async (input, init) => { if (typeof input === "string" && input === "https://api.openai.com/v1/responses") { assert.equal(init?.method, "POST"); return new Response(JSON.stringify({ output_text: "连接正常" }), { status: 200, headers: { "content-type": "application/json", "x-request-id": "req-onboard-openai", }, }); } throw new Error(`unexpected fetch: ${String(input)}`); }) as typeof fetch; try { const response = await openAiOnboardRoute( await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/onboard/openai-api", { label: "主 GPT", displayName: "OpenAI 平台账号", accountIdentifier: "sk-proj-demo", model: "gpt-5.4", apiKey: "sk-live-demo-123456", }), ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; accountId: string; message: string; activeIdentity: { accountId: string; provider: string; displayName: string }; }; assert.equal(payload.ok, true); assert.equal(payload.accountId, "openai-api-primary"); assert.equal(payload.activeIdentity.accountId, "openai-api-primary"); assert.equal(payload.activeIdentity.provider, "openai_api"); assert.match(payload.message, /已登录/); assert.match(payload.message, /当前主控/); const state = await readState(); const account = state.aiAccounts.find((item) => item.accountId === "openai-api-primary"); assert.ok(account, "expected openai primary account to be created"); assert.equal(account?.provider, "openai_api"); assert.equal(account?.role, "primary"); assert.equal(account?.isActive, true); assert.equal(account?.status, "ready"); assert.equal(account?.displayName, "OpenAI 平台账号"); assert.equal(account?.apiKey, "sk-live-demo-123456"); assert.match(account?.apiKeyMasked ?? "", /\.\.\./); } finally { globalThis.fetch = originalFetch; } }); test("POST /api/v1/accounts/onboard/master-node upserts a master node account and returns bound-device guidance", async () => { await setup(); const response = await masterNodeOnboardRoute( await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/onboard/master-node", { label: "主 GPT", displayName: "Mac 上的 Master Codex Node", accountIdentifier: "17600003315", nodeId: "mac-studio", nodeLabel: "Mac Studio", model: "gpt-5.4", setActive: true, }), ); assert.equal(response.status, 200); const payload = (await response.json()) as { ok: boolean; accountId: string; message: string; validation: { status: string; message: string }; }; assert.equal(payload.ok, true); assert.equal(payload.accountId, "master-codex-primary"); assert.match(payload.message, /当前主控|已绑定/); assert.equal(payload.validation.status, "ready"); assert.match(payload.validation.message, /不在手机里直接登录/); assert.match(payload.validation.message, /Mac Studio|绑定设备/); const state = await readState(); const account = state.aiAccounts.find((item) => item.accountId === "master-codex-primary"); assert.ok(account, "expected master node primary account"); assert.equal(account?.provider, "master_codex_node"); assert.equal(account?.displayName, "Mac 上的 Master Codex Node"); assert.equal(account?.nodeId, "mac-studio"); assert.equal(account?.nodeLabel, "Mac Studio"); assert.equal(account?.isActive, true); }); test("POST /api/v1/accounts/onboard/openai-api returns a clear network guidance when OpenAI is unreachable", async () => { await setup(); const originalFetch = globalThis.fetch; globalThis.fetch = (async () => { const error = new TypeError("fetch failed"); (error as TypeError & { cause?: { code?: string; message?: string } }).cause = { code: "ENETUNREACH", message: "connect ENETUNREACH api.openai.com", }; throw error; }) as typeof fetch; try { const response = await openAiOnboardRoute( await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/onboard/openai-api", { label: "主 GPT", displayName: "OpenAI 平台账号", accountIdentifier: "sk-proj-demo", model: "gpt-5.4", apiKey: "sk-live-demo-123456", }), ); assert.equal(response.status, 400); const payload = (await response.json()) as { ok: boolean; message: string }; assert.equal(payload.ok, false); assert.match(payload.message, /无法访问 api\.openai\.com|无法连接 OpenAI API/); } finally { globalThis.fetch = originalFetch; } });