From 58759bb5657cb3dbb99da509f481c4dd88ba6913 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 18 Apr 2026 19:58:07 +0100 Subject: [PATCH] test: genericize synthetic auth coverage --- src/agents/model-auth.profiles.test.ts | 169 +++++++++---------- src/agents/model-auth.ts | 2 +- src/plugins/hook-before-agent-start.types.ts | 2 +- 3 files changed, 80 insertions(+), 93 deletions(-) diff --git a/src/agents/model-auth.profiles.test.ts b/src/agents/model-auth.profiles.test.ts index d1e371815c9..290eee0a84a 100644 --- a/src/agents/model-auth.profiles.test.ts +++ b/src/agents/model-auth.profiles.test.ts @@ -69,9 +69,9 @@ vi.mock("./model-auth-env-vars.js", () => { const candidates = { anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"], google: ["GEMINI_API_KEY", "GOOGLE_API_KEY"], + "demo-local": ["DEMO_LOCAL_API_KEY"], huggingface: ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"], "minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"], - ollama: ["OLLAMA_API_KEY"], "opencode-go": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"], openai: ["OPENAI_API_KEY"], qianfan: ["QIANFAN_API_KEY"], @@ -104,26 +104,19 @@ vi.mock("../plugins/provider-runtime.js", () => ({ provider: string; context: { providerConfig?: { api?: string; baseUrl?: string; models?: unknown[] } }; }) => { - if (params.provider !== "ollama" && params.provider !== "demo-local") { + if (params.provider !== "demo-local") { return undefined; } const providerConfig = params.context.providerConfig; - const hasMeaningfulOllamaConfig = - params.provider !== "ollama" - ? Boolean(providerConfig?.api?.trim()) || - Boolean(providerConfig?.baseUrl?.trim()) || - (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0) - : (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0) || - Boolean(providerConfig?.api?.trim() && providerConfig.api.trim() !== "ollama") || - Boolean( - providerConfig?.baseUrl?.trim() && - providerConfig.baseUrl.trim().replace(/\/+$/, "") !== "http://127.0.0.1:11434", - ); - if (!hasMeaningfulOllamaConfig) { + const hasMeaningfulConfig = + Boolean(providerConfig?.api?.trim()) || + Boolean(providerConfig?.baseUrl?.trim()) || + (Array.isArray(providerConfig?.models) && providerConfig.models.length > 0); + if (!hasMeaningfulConfig) { return undefined; } return { - apiKey: params.provider === "ollama" ? "ollama-local" : "demo-local", + apiKey: "demo-local", source: `models.providers.${params.provider} (synthetic local key)`, mode: "api-key" as const, }; @@ -133,12 +126,7 @@ vi.mock("../plugins/provider-runtime.js", () => ({ provider: string; context: { resolvedApiKey?: string }; }) => { - const expectedMarker = - params.provider === "ollama" - ? "ollama-local" - : params.provider === "demo-local" - ? "demo-local" - : undefined; + const expectedMarker = params.provider === "demo-local" ? "demo-local" : undefined; return Boolean(expectedMarker && params.context.resolvedApiKey?.trim() === expectedMarker); }, })); @@ -207,15 +195,15 @@ async function expectBedrockAuthSource(params: { }); } -function buildOllamaStore(keys: string[]) { +function buildDemoLocalStore(keys: string[]) { return { version: 1 as const, profiles: Object.fromEntries( keys.map((key, index) => [ - index === 0 ? "ollama:default" : `ollama:${index + 1}`, + index === 0 ? "demo-local:default" : `demo-local:${index + 1}`, { type: "api_key" as const, - provider: "ollama" as const, + provider: "demo-local" as const, key, }, ]), @@ -223,13 +211,13 @@ function buildOllamaStore(keys: string[]) { }; } -function buildOllamaProviderCfg(apiKey: string): OpenClawConfig { +function buildDemoLocalProviderCfg(apiKey: string): OpenClawConfig { return { models: { providers: { - ollama: { - baseUrl: "https://ollama.com", - api: "ollama", + "demo-local": { + baseUrl: "https://local-provider.example", + api: "openai-completions", apiKey, models: [], }, @@ -238,17 +226,17 @@ function buildOllamaProviderCfg(apiKey: string): OpenClawConfig { }; } -async function resolveOllamaApiKey(params: { +async function resolveDemoLocalApiKey(params: { envApiKey: string | undefined; storedKeys: string[]; configuredApiKey: string; }) { let resolved!: Awaited>; - await withEnvAsync({ OLLAMA_API_KEY: params.envApiKey }, async () => { + await withEnvAsync({ DEMO_LOCAL_API_KEY: params.envApiKey }, async () => { resolved = await resolveApiKeyForProvider({ - provider: "ollama", - store: buildOllamaStore(params.storedKeys), - cfg: buildOllamaProviderCfg(params.configuredApiKey), + provider: "demo-local", + store: buildDemoLocalStore(params.storedKeys), + cfg: buildDemoLocalProviderCfg(params.configuredApiKey), }); }); return resolved; @@ -517,16 +505,16 @@ describe("getApiKeyForModel", () => { ); }); - it("resolves synthetic local auth key for configured ollama provider without apiKey", async () => { - await withEnvAsync({ OLLAMA_API_KEY: undefined }, async () => { + it("resolves plugin-owned synthetic local auth for a configured provider without apiKey", async () => { + await withEnvAsync({ DEMO_LOCAL_API_KEY: undefined }, async () => { const resolved = await resolveApiKeyForProvider({ - provider: "ollama", + provider: "demo-local", store: { version: 1, profiles: {} }, cfg: { models: { providers: { - ollama: { - baseUrl: "http://gpu-node-server:11434", + "demo-local": { + baseUrl: "http://local-provider:11434", api: "openai-completions", models: [], }, @@ -534,45 +522,44 @@ describe("getApiKeyForModel", () => { }, }, }); - expect(resolved.apiKey).toBe("ollama-local"); + expect(resolved.apiKey).toBe("demo-local"); expect(resolved.mode).toBe("api-key"); expect(resolved.source).toContain("synthetic local key"); }); }); - it("does not mint synthetic local auth for default-ish ollama stubs", async () => { - await withEnvAsync({ OLLAMA_API_KEY: undefined }, async () => { + it("does not mint synthetic local auth for empty provider stubs", async () => { + await withEnvAsync({ DEMO_LOCAL_API_KEY: undefined }, async () => { await expect( resolveApiKeyForProvider({ - provider: "ollama", + provider: "demo-local", store: { version: 1, profiles: {} }, cfg: { models: { providers: { - ollama: { - baseUrl: "http://127.0.0.1:11434", - api: "ollama", + "demo-local": { + baseUrl: "", models: [], }, }, }, }, }), - ).rejects.toThrow(/No API key found for provider "ollama"/); + ).rejects.toThrow(/No API key found for provider "demo-local"/); }); }); - it("prefers explicit OLLAMA_API_KEY over synthetic local key", async () => { - await withEnvAsync({ [envVar("OLLAMA", "API", "KEY")]: "env-ollama-key" }, async () => { + it("prefers explicit provider env auth over synthetic local key", async () => { + await withEnvAsync({ [envVar("DEMO", "LOCAL", "API", "KEY")]: "env-demo-key" }, async () => { // pragma: allowlist secret const resolved = await resolveApiKeyForProvider({ - provider: "ollama", + provider: "demo-local", store: { version: 1, profiles: {} }, cfg: { models: { providers: { - ollama: { - baseUrl: "http://gpu-node-server:11434", + "demo-local": { + baseUrl: "http://local-provider:11434", api: "openai-completions", models: [], }, @@ -580,63 +567,63 @@ describe("getApiKeyForModel", () => { }, }, }); - expect(resolved.apiKey).toBe("env-ollama-key"); - expect(resolved.source).toContain("OLLAMA_API_KEY"); + expect(resolved.apiKey).toBe("env-demo-key"); + expect(resolved.source).toContain("DEMO_LOCAL_API_KEY"); }); }); - it("prefers explicit OLLAMA_API_KEY over the stored ollama-local profile", async () => { - const resolved = await resolveOllamaApiKey({ - envApiKey: "env-ollama-key", - storedKeys: ["ollama-local"], - configuredApiKey: "OLLAMA_API_KEY", + it("prefers explicit provider env auth over a stored synthetic local profile", async () => { + const resolved = await resolveDemoLocalApiKey({ + envApiKey: "env-demo-key", + storedKeys: ["demo-local"], + configuredApiKey: "DEMO_LOCAL_API_KEY", }); - expect(resolved.apiKey).toBe("env-ollama-key"); - expect(resolved.source).toContain("OLLAMA_API_KEY"); + expect(resolved.apiKey).toBe("env-demo-key"); + expect(resolved.source).toContain("DEMO_LOCAL_API_KEY"); expect(resolved.profileId).toBeUndefined(); }); - it("prefers explicit configured ollama apiKey over the stored ollama-local profile", async () => { - const resolved = await resolveOllamaApiKey({ + it("prefers explicit configured apiKey over a stored synthetic local profile", async () => { + const resolved = await resolveDemoLocalApiKey({ envApiKey: undefined, - storedKeys: ["ollama-local"], - configuredApiKey: "config-ollama-key", + storedKeys: ["demo-local"], + configuredApiKey: "config-demo-key", }); - expect(resolved.apiKey).toBe("config-ollama-key"); + expect(resolved.apiKey).toBe("config-demo-key"); expect(resolved.source).toBe("models.json"); expect(resolved.profileId).toBeUndefined(); }); - it("falls back to the stored ollama-local profile when no real ollama auth exists", async () => { - const resolved = await resolveOllamaApiKey({ + it("falls back to the stored synthetic local profile when no real auth exists", async () => { + const resolved = await resolveDemoLocalApiKey({ envApiKey: undefined, - storedKeys: ["ollama-local"], - configuredApiKey: "OLLAMA_API_KEY", + storedKeys: ["demo-local"], + configuredApiKey: "DEMO_LOCAL_API_KEY", }); - expect(resolved.apiKey).toBe("ollama-local"); - expect(resolved.source).toBe("profile:ollama:default"); - expect(resolved.profileId).toBe("ollama:default"); + expect(resolved.apiKey).toBe("demo-local"); + expect(resolved.source).toBe("profile:demo-local:default"); + expect(resolved.profileId).toBe("demo-local:default"); }); - it("keeps a real stored ollama profile ahead of env auth", async () => { - const resolved = await resolveOllamaApiKey({ - envApiKey: "env-ollama-key", - storedKeys: ["stored-ollama-key"], - configuredApiKey: "OLLAMA_API_KEY", + it("keeps a real stored profile ahead of env auth", async () => { + const resolved = await resolveDemoLocalApiKey({ + envApiKey: "env-demo-key", + storedKeys: ["stored-demo-key"], + configuredApiKey: "DEMO_LOCAL_API_KEY", }); - expect(resolved.apiKey).toBe("stored-ollama-key"); - expect(resolved.source).toBe("profile:ollama:default"); - expect(resolved.profileId).toBe("ollama:default"); + expect(resolved.apiKey).toBe("stored-demo-key"); + expect(resolved.source).toBe("profile:demo-local:default"); + expect(resolved.profileId).toBe("demo-local:default"); }); - it("defers every stored ollama-local profile until real auth sources are checked", async () => { - const resolved = await resolveOllamaApiKey({ - envApiKey: "env-ollama-key", - storedKeys: ["ollama-local", "ollama-local"], - configuredApiKey: "OLLAMA_API_KEY", + it("defers every stored synthetic local profile until real auth sources are checked", async () => { + const resolved = await resolveDemoLocalApiKey({ + envApiKey: "env-demo-key", + storedKeys: ["demo-local", "demo-local"], + configuredApiKey: "DEMO_LOCAL_API_KEY", }); - expect(resolved.apiKey).toBe("env-ollama-key"); - expect(resolved.source).toContain("OLLAMA_API_KEY"); + expect(resolved.apiKey).toBe("env-demo-key"); + expect(resolved.source).toContain("DEMO_LOCAL_API_KEY"); expect(resolved.profileId).toBeUndefined(); }); @@ -671,14 +658,14 @@ describe("getApiKeyForModel", () => { expect(resolved.profileId).toBeUndefined(); }); - it("still throws for ollama when no env/profile/config provider is available", async () => { - await withEnvAsync({ OLLAMA_API_KEY: undefined }, async () => { + it("still throws when no env/profile/config provider auth is available", async () => { + await withEnvAsync({ DEMO_LOCAL_API_KEY: undefined }, async () => { await expect( resolveApiKeyForProvider({ - provider: "ollama", + provider: "demo-local", store: { version: 1, profiles: {} }, }), - ).rejects.toThrow('No API key found for provider "ollama".'); + ).rejects.toThrow('No API key found for provider "demo-local".'); }); }); diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index b1c06084c68..3f614149cf6 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -414,7 +414,7 @@ export async function resolveApiKeyForProvider(params: { store?: AuthProfileStore; agentDir?: string; /** When true, treat profileId as a user-locked selection that must not be - * silently overridden by env/config credentials (e.g. ollama-local). */ + * silently overridden by env/config credentials. */ lockedProfile?: boolean; credentialPrecedence?: ProviderCredentialPrecedence; }): Promise { diff --git a/src/plugins/hook-before-agent-start.types.ts b/src/plugins/hook-before-agent-start.types.ts index 6236cf05d53..00b2dc6a900 100644 --- a/src/plugins/hook-before-agent-start.types.ts +++ b/src/plugins/hook-before-agent-start.types.ts @@ -7,7 +7,7 @@ export type PluginHookBeforeModelResolveEvent = { export type PluginHookBeforeModelResolveResult = { /** Override the model for this agent run. E.g. "llama3.3:8b" */ modelOverride?: string; - /** Override the provider for this agent run. E.g. "ollama" */ + /** Override the provider for this agent run. E.g. "local-provider" */ providerOverride?: string; };