





























@@ -296,26 +296,139 @@ describe("models.authStatus", () => {
296296expect(serialised).not.toContain("rt-SECRET-REFRESH");
297297});
298298299-it("skips env-backed OAuth providers (apiKey set in config) from missing synthesis", async () => {
300-// Provider configured `auth: "oauth"` with `apiKey` present (env-backed)
301-// must not be forwarded to buildAuthHealthSummary — doing so would flag
302-// it as missing even though env auth already satisfies it.
299+it("skips env-backed OAuth providers (resolvable apiKey) from missing synthesis", async () => {
300+// Provider configured `auth: "oauth"` with a resolvable apiKey — env
301+// auth already satisfies it, so forwarding to buildAuthHealthSummary
302+// would flag it as missing and cry wolf. Inline string is the simplest
303+// "available" SecretInput for testing.
303304mocks.loadConfig.mockReturnValue({
304305models: {
305306providers: {
306-"openai-codex": { auth: "oauth", apiKey: { env: "OPENAI_OAUTH_TOKEN" } },
307+"openai-codex": { auth: "oauth", apiKey: "sk-xxxxx" },
307308},
308309},
309310});
310311await handler(createOptions());
311-// When the only configured provider is env-backed, we pass `undefined`
312-// (meaning "no filter"), not a filter containing it.
313312const call = mocks.buildAuthHealthSummary.mock.calls[0] as unknown as
314313| [{ providers?: string[] }]
315314| undefined;
316315expect(call?.[0]?.providers).toBeUndefined();
317316});
318317318+it("still flags provider as missing when apiKey env SecretRef points at an unset env var", async () => {
319+// Config declares an env SecretRef but the referenced env var isn't
320+// set. We read process.env directly for env-source SecretRefs and fall
321+// through to the normal missing synthesis so the dashboard surfaces
322+// the broken config instead of masking it.
323+delete process.env.MODELS_AUTH_STATUS_TEST_MISSING_KEY;
324+mocks.loadConfig.mockReturnValue({
325+models: {
326+providers: {
327+"openai-codex": {
328+auth: "oauth",
329+apiKey: {
330+source: "env",
331+provider: "default",
332+id: "MODELS_AUTH_STATUS_TEST_MISSING_KEY",
333+},
334+},
335+},
336+},
337+});
338+await handler(createOptions());
339+const call = mocks.buildAuthHealthSummary.mock.calls[0] as unknown as
340+| [{ providers?: string[] }]
341+| undefined;
342+expect(call?.[0]?.providers).toEqual(["openai-codex"]);
343+});
344+345+it("env SecretRef pointing at a set env var is treated as env-backed", async () => {
346+process.env.MODELS_AUTH_STATUS_TEST_SET_KEY = "sk-real-value";
347+mocks.loadConfig.mockReturnValue({
348+models: {
349+providers: {
350+"openai-codex": {
351+auth: "oauth",
352+apiKey: {
353+source: "env",
354+provider: "default",
355+id: "MODELS_AUTH_STATUS_TEST_SET_KEY",
356+},
357+},
358+},
359+},
360+});
361+try {
362+await handler(createOptions());
363+const call = mocks.buildAuthHealthSummary.mock.calls[0] as unknown as
364+| [{ providers?: string[] }]
365+| undefined;
366+expect(call?.[0]?.providers).toBeUndefined();
367+} finally {
368+delete process.env.MODELS_AUTH_STATUS_TEST_SET_KEY;
369+}
370+});
371+372+it("env-backed escape hatch also applies to auth.profiles entries", async () => {
373+// auth.profiles loop must honor the env-backed skip from the
374+// models.providers loop — otherwise a provider with resolvable apiKey
375+// plus a matching auth.profiles entry re-adds itself and triggers the
376+// false-missing alert we just fixed.
377+mocks.loadConfig.mockReturnValue({
378+models: {
379+providers: {
380+"openai-codex": { auth: "oauth", apiKey: "sk-xxxxx" },
381+},
382+},
383+auth: {
384+profiles: {
385+"openai-codex:default": { provider: "openai-codex", mode: "oauth" },
386+},
387+},
388+});
389+await handler(createOptions());
390+const call = mocks.buildAuthHealthSummary.mock.calls[0] as unknown as
391+| [{ providers?: string[] }]
392+| undefined;
393+expect(call?.[0]?.providers).toBeUndefined();
394+});
395+396+it("normalizes expectsOAuth provider ids to match buildAuthHealthSummary", async () => {
397+// Config uses alias `z.ai`; buildAuthHealthSummary normalizes to `zai`.
398+// Without normalization, expectsOAuth.has(prov.provider) fires on the
399+// raw `z.ai` key but prov.provider is `zai`, so the "configured oauth
400+// but no oauth profile" signal silently skipped the alias path.
401+mocks.loadConfig.mockReturnValue({
402+models: { providers: { "z.ai": { auth: "oauth" } } },
403+});
404+mocks.buildAuthHealthSummary.mockReturnValue({
405+now: 0,
406+warnAfterMs: 0,
407+profiles: [],
408+providers: [
409+{
410+provider: "zai",
411+status: "static",
412+profiles: [
413+{
414+profileId: "zai:default",
415+provider: "zai",
416+type: "api_key",
417+status: "static",
418+source: "store",
419+label: "zai:default",
420+},
421+],
422+},
423+],
424+});
425+const opts = createOptions();
426+await handler(opts);
427+const [, payload] = opts.respond.mock.calls[0] ?? [];
428+const result = payload as ModelAuthStatusResult;
429+expect(result.providers[0]?.status).toBe("missing");
430+});
431+319432it("flags provider configured auth:oauth but with only api_key profile as missing", async () => {
320433// Config says provider should use OAuth; store has only an api_key
321434// credential (e.g. operator switched modes but forgot to login).
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。