From 093ddd293aa76cdaae266fe553fdda535313a411 Mon Sep 17 00:00:00 2001 From: MicroGrey Date: Fri, 12 Jun 2026 15:13:24 +0800 Subject: [PATCH] test(offload): cover auth-profile apiKey resolver Add unit tests for resolveApiKeyFromAuthProfile covering the four resolution branches: api_key profile returns the plaintext key, oauth/token-only providers return undefined, providers with no profile return undefined, and keyRef-only (no plaintext) profiles are skipped. Also covers the older-host case where the provider-auth SDK subpath is unavailable. To keep the test self-contained (no real OpenClaw install or on-disk auth-profile store), resolveApiKeyFromAuthProfile gains an optional fourth parameter to inject a fake provider-auth SDK loader. Production callers pass three arguments and keep loading the real SDK via require. Signed-off-by: MicroGrey --- src/offload/auth-profile-key.test.ts | 68 ++++++++++++++++++++++++++++ src/offload/auth-profile-key.ts | 3 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/offload/auth-profile-key.test.ts diff --git a/src/offload/auth-profile-key.test.ts b/src/offload/auth-profile-key.test.ts new file mode 100644 index 0000000..6a7ad69 --- /dev/null +++ b/src/offload/auth-profile-key.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect } from "vitest"; +import { resolveApiKeyFromAuthProfile } from "./auth-profile-key.js"; + +/** + * Build a fake provider-auth SDK whose store contains the given profiles and + * whose `listProfilesForProvider` maps a provider name to its profile ids. + */ +function makeSdk( + profiles: Record, + profileIdsByProvider: Record, +) { + return { + ensureAuthProfileStore: () => ({ profiles }), + listProfilesForProvider: (_store: unknown, provider: string) => + profileIdsByProvider[provider] ?? [], + resolveOpenClawAgentDir: () => "/fake/agent/dir", + }; +} + +const api = { config: { models: { providers: {} } } }; + +describe("resolveApiKeyFromAuthProfile", () => { + it("returns the plaintext key for an api_key profile bound to the provider", () => { + const sdk = makeSdk( + { "xiaomi:default": { type: "api_key", key: "sk-xiaomi-123" } }, + { xiaomi: ["xiaomi:default"] }, + ); + + const key = resolveApiKeyFromAuthProfile(api, "xiaomi", undefined, () => sdk); + + expect(key).toBe("sk-xiaomi-123"); + }); + + it("returns undefined when the provider only has oauth/token profiles", () => { + const sdk = makeSdk( + { + "claude:default": { type: "oauth" }, + "github:default": { type: "token" }, + }, + { anthropic: ["claude:default"], github: ["github:default"] }, + ); + + expect(resolveApiKeyFromAuthProfile(api, "anthropic", undefined, () => sdk)).toBeUndefined(); + expect(resolveApiKeyFromAuthProfile(api, "github", undefined, () => sdk)).toBeUndefined(); + }); + + it("returns undefined when the provider has no profiles at all", () => { + const sdk = makeSdk( + { "xiaomi:default": { type: "api_key", key: "sk-xiaomi-123" } }, + { xiaomi: ["xiaomi:default"] }, + ); + + expect(resolveApiKeyFromAuthProfile(api, "deepseek", undefined, () => sdk)).toBeUndefined(); + }); + + it("skips api_key profiles that store only a keyRef (no plaintext key)", () => { + const sdk = makeSdk( + { "vault:default": { type: "api_key", keyRef: { source: "keychain" } } }, + { vault: ["vault:default"] }, + ); + + expect(resolveApiKeyFromAuthProfile(api, "vault", undefined, () => sdk)).toBeUndefined(); + }); + + it("returns undefined when the provider-auth SDK is unavailable (older host)", () => { + expect(resolveApiKeyFromAuthProfile(api, "xiaomi", undefined, () => undefined)).toBeUndefined(); + }); +}); diff --git a/src/offload/auth-profile-key.ts b/src/offload/auth-profile-key.ts index 4829c33..43a8712 100644 --- a/src/offload/auth-profile-key.ts +++ b/src/offload/auth-profile-key.ts @@ -49,11 +49,12 @@ export function resolveApiKeyFromAuthProfile( api: { config?: unknown }, providerKey: string, logger?: PluginLogger, + _loadSdkOverride?: () => ProviderAuthSdk | undefined, ): string | undefined { try { // Lazily load the SDK subpath so a missing export on older OpenClaw // versions degrades gracefully instead of crashing module load. - const sdk = loadProviderAuthSdk(); + const sdk = _loadSdkOverride ? _loadSdkOverride() : loadProviderAuthSdk(); if (!sdk) return undefined; const { ensureAuthProfileStore, listProfilesForProvider, resolveOpenClawAgentDir } = sdk;