test(providers): add tests for Z.AI/Zhipu multi-configuration

Update existing test files to cover all four GLM provider variants:

- auth-choice-options.test.ts: Group test for zai, zai-coding, zhipu,
  zhipu-coding auth choice menu presence (follows MiniMax/Moonshot pattern)

- program.smoke.test.ts: Add CLI flag wiring tests for --zai-coding-api-key,
  --zhipu-api-key, --zhipu-coding-api-key

- model-auth.test.ts: Add env var resolution tests for:
  - zai-coding with fallback to ZAI_API_KEY
  - zhipu with ZHIPU_API_KEY
  - zhipu-coding with fallback to ZHIPU_API_KEY

- provider-usage.test.ts: Add test for zhipu bigmodel.cn endpoint
  (will fail until fetchZhipuUsage is implemented)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
Kyle Howells 2026-01-29 16:31:36 +00:00
parent 61d9798b6b
commit 17a4345004
4 changed files with 193 additions and 1 deletions

View File

@ -233,6 +233,132 @@ describe("getApiKeyForModel", () => {
}
});
it("resolves zai-coding API key with fallback to ZAI_API_KEY", async () => {
const previous = {
coding: process.env.ZAI_CODING_API_KEY,
zai: process.env.ZAI_API_KEY,
legacy: process.env.Z_AI_API_KEY,
};
try {
delete process.env.ZAI_CODING_API_KEY;
process.env.ZAI_API_KEY = "zai-fallback-key";
delete process.env.Z_AI_API_KEY;
vi.resetModules();
const { resolveApiKeyForProvider } = await import("./model-auth.js");
const resolved = await resolveApiKeyForProvider({
provider: "zai-coding",
store: { version: 1, profiles: {} },
});
expect(resolved.apiKey).toBe("zai-fallback-key");
expect(resolved.source).toContain("ZAI_API_KEY");
} finally {
if (previous.coding === undefined) {
delete process.env.ZAI_CODING_API_KEY;
} else {
process.env.ZAI_CODING_API_KEY = previous.coding;
}
if (previous.zai === undefined) {
delete process.env.ZAI_API_KEY;
} else {
process.env.ZAI_API_KEY = previous.zai;
}
if (previous.legacy === undefined) {
delete process.env.Z_AI_API_KEY;
} else {
process.env.Z_AI_API_KEY = previous.legacy;
}
}
});
it("throws when zhipu API key is missing", async () => {
const previous = process.env.ZHIPU_API_KEY;
try {
delete process.env.ZHIPU_API_KEY;
vi.resetModules();
const { resolveApiKeyForProvider } = await import("./model-auth.js");
let error: unknown = null;
try {
await resolveApiKeyForProvider({
provider: "zhipu",
store: { version: 1, profiles: {} },
});
} catch (err) {
error = err;
}
expect(String(error)).toContain('No API key found for provider "zhipu".');
} finally {
if (previous === undefined) {
delete process.env.ZHIPU_API_KEY;
} else {
process.env.ZHIPU_API_KEY = previous;
}
}
});
it("resolves zhipu API key from ZHIPU_API_KEY", async () => {
const previous = process.env.ZHIPU_API_KEY;
try {
process.env.ZHIPU_API_KEY = "zhipu-test-key";
vi.resetModules();
const { resolveApiKeyForProvider } = await import("./model-auth.js");
const resolved = await resolveApiKeyForProvider({
provider: "zhipu",
store: { version: 1, profiles: {} },
});
expect(resolved.apiKey).toBe("zhipu-test-key");
expect(resolved.source).toContain("ZHIPU_API_KEY");
} finally {
if (previous === undefined) {
delete process.env.ZHIPU_API_KEY;
} else {
process.env.ZHIPU_API_KEY = previous;
}
}
});
it("resolves zhipu-coding API key with fallback to ZHIPU_API_KEY", async () => {
const previous = {
coding: process.env.ZHIPU_CODING_API_KEY,
zhipu: process.env.ZHIPU_API_KEY,
};
try {
delete process.env.ZHIPU_CODING_API_KEY;
process.env.ZHIPU_API_KEY = "zhipu-fallback-key";
vi.resetModules();
const { resolveApiKeyForProvider } = await import("./model-auth.js");
const resolved = await resolveApiKeyForProvider({
provider: "zhipu-coding",
store: { version: 1, profiles: {} },
});
expect(resolved.apiKey).toBe("zhipu-fallback-key");
expect(resolved.source).toContain("ZHIPU_API_KEY");
} finally {
if (previous.coding === undefined) {
delete process.env.ZHIPU_CODING_API_KEY;
} else {
process.env.ZHIPU_CODING_API_KEY = previous.coding;
}
if (previous.zhipu === undefined) {
delete process.env.ZHIPU_API_KEY;
} else {
process.env.ZHIPU_API_KEY = previous.zhipu;
}
}
});
it("resolves Synthetic API key from env", async () => {
const previousSynthetic = process.env.SYNTHETIC_API_KEY;

View File

@ -182,6 +182,24 @@ describe("cli program (smoke)", () => {
key: "sk-zai-test",
field: "zaiApiKey",
},
{
authChoice: "zai-coding-api-key",
flag: "--zai-coding-api-key",
key: "sk-zai-coding-test",
field: "zaiCodingApiKey",
},
{
authChoice: "zhipu-api-key",
flag: "--zhipu-api-key",
key: "sk-zhipu-test",
field: "zhipuApiKey",
},
{
authChoice: "zhipu-coding-api-key",
flag: "--zhipu-coding-api-key",
key: "sk-zhipu-coding-test",
field: "zhipuCodingApiKey",
},
] as const;
for (const entry of cases) {

View File

@ -23,14 +23,19 @@ describe("buildAuthChoiceOptions", () => {
expect(options.some((opt) => opt.value === "token")).toBe(true);
});
it("includes Z.AI (GLM) auth choice", () => {
it("includes Z.AI / Zhipu (GLM) auth choices", () => {
const store: AuthProfileStore = { version: 1, profiles: {} };
const options = buildAuthChoiceOptions({
store,
includeSkip: false,
});
// International variants (api.z.ai)
expect(options.some((opt) => opt.value === "zai-api-key")).toBe(true);
expect(options.some((opt) => opt.value === "zai-coding-api-key")).toBe(true);
// China variants (bigmodel.cn)
expect(options.some((opt) => opt.value === "zhipu-api-key")).toBe(true);
expect(options.some((opt) => opt.value === "zhipu-coding-api-key")).toBe(true);
});
it("includes MiniMax auth choice", () => {

View File

@ -137,6 +137,49 @@ describe("provider usage loading", () => {
expect(mockFetch).toHaveBeenCalled();
});
// TODO: Implement fetchZhipuUsage in provider-usage.load.ts
it("loads zhipu usage from bigmodel.cn endpoint", async () => {
const makeResponse = (status: number, body: unknown): Response => {
const payload = typeof body === "string" ? body : JSON.stringify(body);
const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" };
return new Response(payload, { status, headers });
};
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>(async (input) => {
const url =
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
if (url.includes("open.bigmodel.cn")) {
return makeResponse(200, {
success: true,
code: 200,
data: {
planName: "Basic",
limits: [
{
type: "TOKENS_LIMIT",
percentage: 30,
unit: 3,
number: 6,
nextResetTime: "2026-01-07T06:00:00Z",
},
],
},
});
}
return makeResponse(404, "not found");
});
const summary = await loadProviderUsageSummary({
now: Date.UTC(2026, 0, 7, 0, 0, 0),
auth: [{ provider: "zhipu", token: "token-1" }],
fetch: mockFetch,
});
const zhipu = summary.providers.find((p) => p.provider === "zhipu");
expect(zhipu?.plan).toBe("Basic");
expect(zhipu?.windows[0]?.usedPercent).toBe(30);
});
it("handles nested MiniMax usage payloads", async () => {
const makeResponse = (status: number, body: unknown): Response => {
const payload = typeof body === "string" ? body : JSON.stringify(body);