293 lines
9.2 KiB
TypeScript
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;
|
|
}
|
|
});
|