test: genericize synthetic auth coverage

This commit is contained in:
Peter Steinberger
2026-04-18 19:58:07 +01:00
parent f168a62068
commit 58759bb565
3 changed files with 80 additions and 93 deletions

View File

@@ -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<ReturnType<typeof resolveApiKeyForProvider>>;
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".');
});
});

View File

@@ -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<ResolvedProviderAuth> {

View File

@@ -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;
};