Files
boss/tests/ai-account-routes.test.ts

293 lines
9.2 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 createAccountRoute: (typeof import("../src/app/api/v1/accounts/route"))["POST"];
let updateAccountRoute: (typeof import("../src/app/api/v1/accounts/[accountId]/route"))["PATCH"];
let validateDraftAccountRoute: (typeof import("../src/app/api/v1/accounts/validate-draft/route"))["POST"];
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
let AUTH_SESSION_COOKIE = "";
async function setup() {
if (runtimeRoot) {
return;
}
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-ai-account-routes-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [accountsModule, accountDetailModule, validateDraftModule, dataModule, authModule] = await Promise.all([
import("../src/app/api/v1/accounts/route.ts"),
import("../src/app/api/v1/accounts/[accountId]/route.ts"),
import("../src/app/api/v1/accounts/validate-draft/route.ts"),
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
]);
createAccountRoute = accountsModule.POST;
updateAccountRoute = accountDetailModule.PATCH;
validateDraftAccountRoute = validateDraftModule.POST;
createAuthSession = dataModule.createAuthSession;
AUTH_SESSION_COOKIE = authModule.AUTH_SESSION_COOKIE;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
async function createAuthedJsonRequest(url: string, method: "POST" | "PATCH", body: Record<string, unknown>) {
const session = await createAuthSession({
account: "krisolo",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
return new NextRequest(url, {
method,
headers: {
"content-type": "application/json",
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
},
body: JSON.stringify(body),
});
}
test("POST /api/v1/accounts accepts 环宇智擎 accounts", async () => {
await setup();
const response = await createAccountRoute(
await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts", "POST", {
label: "主 GPT",
role: "primary",
provider: "hyzq_api",
displayName: "环宇智擎主链路",
model: "gpt-5.4-mini",
apiKey: "sk-hyzq-demo-123456",
enabled: true,
setActive: true,
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
account: {
provider: string;
providerLabel: string;
apiBaseUrl?: string;
isActive: boolean;
};
};
assert.equal(payload.ok, true);
assert.equal(payload.account.provider, "hyzq_api");
assert.equal(payload.account.providerLabel, "环宇智擎 API");
assert.equal(payload.account.apiBaseUrl, "https://api.hyzq2046.com/v1");
assert.equal(payload.account.isActive, true);
});
test("PATCH /api/v1/accounts/[accountId] accepts GLM accounts", async () => {
await setup();
const createResponse = await createAccountRoute(
await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts", "POST", {
label: "备用 GPT",
role: "backup",
provider: "custom_api",
displayName: "临时备用链路",
model: "temp-model",
apiBaseUrl: "https://gateway.example.com/v1",
apiKey: "sk-temp-demo-123456",
enabled: true,
}),
);
const createPayload = (await createResponse.json()) as { account: { accountId: string } };
const response = await updateAccountRoute(
await createAuthedJsonRequest(
`http://127.0.0.1:3000/api/v1/accounts/${createPayload.account.accountId}`,
"PATCH",
{
label: "备用 GPT",
role: "backup",
provider: "glm_api",
displayName: "GLM 备用账号",
model: "glm-4.5",
apiKey: "sk-glm-demo-123456",
enabled: true,
},
),
{ params: Promise.resolve({ accountId: createPayload.account.accountId }) },
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
account: {
provider: string;
providerLabel: string;
apiBaseUrl?: string;
displayName: string;
};
};
assert.equal(payload.ok, true);
assert.equal(payload.account.provider, "glm_api");
assert.equal(payload.account.providerLabel, "GLM API");
assert.equal(payload.account.apiBaseUrl, "https://open.bigmodel.cn/api/paas/v4");
assert.equal(payload.account.displayName, "GLM 备用账号");
});
test("POST /api/v1/accounts/validate-draft probes API draft and returns available models", async () => {
await setup();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
if (typeof input === "string" && input === "https://api.hyzq2046.com/v1/responses") {
return new Response(JSON.stringify({ output_text: "连接正常" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-hyzq-draft-validate",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const response = await validateDraftAccountRoute(
await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/validate-draft", "POST", {
provider: "hyzq_api",
apiKey: "sk-hyzq-demo-123456",
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
status: string;
requestId?: string;
availableModels: string[];
};
assert.equal(payload.ok, true);
assert.equal(payload.status, "ready");
assert.equal(payload.requestId, "req-hyzq-draft-validate");
assert.deepEqual(payload.availableModels, ["gpt-5.4-mini", "gpt-5.4"]);
} finally {
globalThis.fetch = originalFetch;
}
});
test("POST /api/v1/accounts/validate-draft prefers provider returned models over static defaults", async () => {
await setup();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
if (typeof input === "string" && input === "https://api.openai.com/v1/responses") {
return new Response(JSON.stringify({ output_text: "连接正常" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-openai-draft-validate",
},
});
}
if (typeof input === "string" && input === "https://api.openai.com/v1/models") {
return new Response(JSON.stringify({
data: [
{ id: "gpt-5.4" },
{ id: "gpt-4.1" },
],
}), {
status: 200,
headers: {
"content-type": "application/json",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const response = await validateDraftAccountRoute(
await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/validate-draft", "POST", {
provider: "openai_api",
apiKey: "sk-openai-demo-123456",
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
availableModels: string[];
};
assert.equal(payload.ok, true);
assert.deepEqual(payload.availableModels, ["gpt-5.4", "gpt-4.1"]);
} finally {
globalThis.fetch = originalFetch;
}
});
test("POST /api/v1/accounts/validate-draft falls back to compatible models for custom api", async () => {
await setup();
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
if (typeof input === "string" && input === "https://gateway.example.com/v1/chat/completions") {
return new Response(JSON.stringify({
choices: [
{
message: {
content: "连接正常",
},
},
],
}), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-custom-draft-validate",
},
});
}
if (typeof input === "string" && input === "https://gateway.example.com/v1/models") {
return new Response(JSON.stringify({ data: [] }), {
status: 200,
headers: {
"content-type": "application/json",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const response = await validateDraftAccountRoute(
await createAuthedJsonRequest("http://127.0.0.1:3000/api/v1/accounts/validate-draft", "POST", {
provider: "custom_api",
apiKey: "sk-custom-demo-123456",
apiBaseUrl: "https://gateway.example.com/v1",
}),
);
assert.equal(response.status, 200);
const payload = (await response.json()) as {
ok: boolean;
message: string;
availableModels: string[];
};
assert.equal(payload.ok, true);
assert.match(payload.message, /兜底/);
assert.deepEqual(payload.availableModels, ["gpt-5.4-mini", "gpt-5.4", "gpt-5.1", "gpt-4.1"]);
} finally {
globalThis.fetch = originalFetch;
}
});