From b432dc5af945101d9be0901e69e6be17550bc63e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 06:44:35 +0100 Subject: [PATCH] perf(test): trim secrets runtime coverage --- src/secrets/runtime-manifest.runtime.ts | 1 + src/secrets/runtime-prepare.runtime.ts | 5 + src/secrets/runtime-web-tools-state.test.ts | 119 ++++++++++ src/secrets/runtime-web-tools.test.ts | 42 ++++ src/secrets/runtime.test.ts | 235 -------------------- src/secrets/runtime.ts | 44 ++-- 6 files changed, 194 insertions(+), 252 deletions(-) create mode 100644 src/secrets/runtime-manifest.runtime.ts create mode 100644 src/secrets/runtime-prepare.runtime.ts create mode 100644 src/secrets/runtime-web-tools-state.test.ts diff --git a/src/secrets/runtime-manifest.runtime.ts b/src/secrets/runtime-manifest.runtime.ts new file mode 100644 index 00000000000..36071aa7029 --- /dev/null +++ b/src/secrets/runtime-manifest.runtime.ts @@ -0,0 +1 @@ +export { loadPluginManifestRegistry } from "../plugins/manifest-registry.js"; diff --git a/src/secrets/runtime-prepare.runtime.ts b/src/secrets/runtime-prepare.runtime.ts new file mode 100644 index 00000000000..2712b4bb8ea --- /dev/null +++ b/src/secrets/runtime-prepare.runtime.ts @@ -0,0 +1,5 @@ +export { resolveSecretRefValues } from "./resolve.js"; +export { collectAuthStoreAssignments } from "./runtime-auth-collectors.js"; +export { collectConfigAssignments } from "./runtime-config-collectors.js"; +export { applyResolvedAssignments, createResolverContext } from "./runtime-shared.js"; +export { resolveRuntimeWebTools } from "./runtime-web-tools.js"; diff --git a/src/secrets/runtime-web-tools-state.test.ts b/src/secrets/runtime-web-tools-state.test.ts new file mode 100644 index 00000000000..7ee8d68dea1 --- /dev/null +++ b/src/secrets/runtime-web-tools-state.test.ts @@ -0,0 +1,119 @@ +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../config/config.js"; +import type { PluginWebSearchProviderEntry } from "../plugins/types.js"; + +const { resolvePluginWebSearchProvidersMock } = vi.hoisted(() => ({ + resolvePluginWebSearchProvidersMock: vi.fn<() => PluginWebSearchProviderEntry[]>(() => [ + { + pluginId: "google", + id: "gemini", + label: "gemini", + hint: "gemini test provider", + envVars: ["GEMINI_API_KEY"], + placeholder: "gemini-...", + signupUrl: "https://example.com/gemini", + autoDetectOrder: 20, + credentialPath: "plugins.entries.google.config.webSearch.apiKey", + inactiveSecretPaths: ["plugins.entries.google.config.webSearch.apiKey"], + getCredentialValue: (searchConfig) => searchConfig?.apiKey, + setCredentialValue: (searchConfigTarget, value) => { + searchConfigTarget.apiKey = value; + }, + getConfiguredCredentialValue: (config) => + (config?.plugins?.entries?.google?.config as { webSearch?: { apiKey?: unknown } }) + ?.webSearch?.apiKey, + setConfiguredCredentialValue: (configTarget, value) => { + const plugins = (configTarget.plugins ??= {}) as { entries?: Record }; + const entries = (plugins.entries ??= {}); + const entry = (entries.google ??= {}) as { config?: Record }; + const config = (entry.config ??= {}); + const webSearch = (config.webSearch ??= {}) as { apiKey?: unknown }; + webSearch.apiKey = value; + }, + createTool: () => null, + }, + ]), +})); + +vi.mock("../plugins/web-search-providers.runtime.js", () => ({ + resolvePluginWebSearchProviders: resolvePluginWebSearchProvidersMock, +})); + +function asConfig(value: unknown): OpenClawConfig { + return value as OpenClawConfig; +} + +let clearConfigCache: typeof import("../config/config.js").clearConfigCache; +let clearRuntimeConfigSnapshot: typeof import("../config/config.js").clearRuntimeConfigSnapshot; +let activateSecretsRuntimeSnapshot: typeof import("./runtime.js").activateSecretsRuntimeSnapshot; +let clearSecretsRuntimeSnapshot: typeof import("./runtime.js").clearSecretsRuntimeSnapshot; +let getActiveRuntimeWebToolsMetadata: typeof import("./runtime.js").getActiveRuntimeWebToolsMetadata; +let prepareSecretsRuntimeSnapshot: typeof import("./runtime.js").prepareSecretsRuntimeSnapshot; + +describe("runtime web tools state", () => { + beforeAll(async () => { + ({ clearConfigCache, clearRuntimeConfigSnapshot } = await import("../config/config.js")); + ({ + activateSecretsRuntimeSnapshot, + clearSecretsRuntimeSnapshot, + getActiveRuntimeWebToolsMetadata, + prepareSecretsRuntimeSnapshot, + } = await import("./runtime.js")); + }); + + afterEach(() => { + clearSecretsRuntimeSnapshot(); + clearRuntimeConfigSnapshot(); + clearConfigCache(); + }); + + it("exposes active runtime web tool metadata as a defensive clone", async () => { + const snapshot = await prepareSecretsRuntimeSnapshot({ + config: asConfig({ + tools: { + web: { + search: { + provider: "gemini", + }, + }, + }, + plugins: { + entries: { + google: { + config: { + webSearch: { + apiKey: { + source: "env", + provider: "default", + id: "WEB_SEARCH_GEMINI_API_KEY", + }, + }, + }, + }, + }, + }, + }), + env: { + WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", + }, + agentDirs: ["/tmp/openclaw-agent-main"], + loadAuthStore: () => ({ version: 1, profiles: {} }), + }); + + activateSecretsRuntimeSnapshot(snapshot); + + const first = getActiveRuntimeWebToolsMetadata(); + expect(first?.search.providerConfigured).toBe("gemini"); + expect(first?.search.selectedProvider).toBe("gemini"); + expect(first?.search.selectedProviderKeySource).toBe("secretRef"); + if (!first) { + throw new Error("missing runtime web tools metadata"); + } + first.search.providerConfigured = "brave"; + first.search.selectedProvider = "brave"; + + const second = getActiveRuntimeWebToolsMetadata(); + expect(second?.search.providerConfigured).toBe("gemini"); + expect(second?.search.selectedProvider).toBe("gemini"); + }); +}); diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index a2f531532a6..fe424187db0 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -370,6 +370,48 @@ describe("runtime web tools resolution", () => { }, ); + it("resolves selected provider SecretRef even when provider config is disabled", async () => { + const { metadata, resolvedConfig, context } = await runRuntimeWebTools({ + config: asConfig({ + tools: { + web: { + search: { + enabled: true, + provider: "gemini", + }, + }, + }, + plugins: { + entries: { + google: { + enabled: true, + config: { + webSearch: { + enabled: false, + apiKey: { + source: "env", + provider: "default", + id: "WEB_SEARCH_GEMINI_API_KEY", + }, + }, + }, + }, + }, + }, + }), + env: { + WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", + }, + }); + + expect(metadata.search.providerConfigured).toBe("gemini"); + expect(metadata.search.selectedProvider).toBe("gemini"); + expect(readProviderKey(resolvedConfig, "gemini")).toBe("web-search-gemini-ref"); + expect(context.warnings.map((warning) => warning.path)).not.toContain( + "plugins.entries.google.config.webSearch.apiKey", + ); + }); + it("auto-detects provider precedence across all configured providers", async () => { const { metadata, resolvedConfig, context } = await runRuntimeWebTools({ config: asConfig({ diff --git a/src/secrets/runtime.test.ts b/src/secrets/runtime.test.ts index 512a2c453e1..917cac3731b 100644 --- a/src/secrets/runtime.test.ts +++ b/src/secrets/runtime.test.ts @@ -92,7 +92,6 @@ let clearConfigCache: typeof import("../config/config.js").clearConfigCache; let clearRuntimeConfigSnapshot: typeof import("../config/config.js").clearRuntimeConfigSnapshot; let activateSecretsRuntimeSnapshot: typeof import("./runtime.js").activateSecretsRuntimeSnapshot; let clearSecretsRuntimeSnapshot: typeof import("./runtime.js").clearSecretsRuntimeSnapshot; -let getActiveRuntimeWebToolsMetadata: typeof import("./runtime.js").getActiveRuntimeWebToolsMetadata; let prepareSecretsRuntimeSnapshot: typeof import("./runtime.js").prepareSecretsRuntimeSnapshot; function createOpenAiFileModelsConfig(): NonNullable { @@ -120,7 +119,6 @@ describe("secrets runtime snapshot", () => { ({ activateSecretsRuntimeSnapshot, clearSecretsRuntimeSnapshot, - getActiveRuntimeWebToolsMetadata, prepareSecretsRuntimeSnapshot, } = await import("./runtime.js")); }); @@ -739,239 +737,6 @@ describe("secrets runtime snapshot", () => { expect(profile.key).toBe("primary-key-value"); }); - it("treats non-selected web search provider refs as inactive", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - search: { - enabled: true, - provider: "brave", - apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" }, - }, - }, - }, - plugins: { - entries: { - xai: { - config: { - webSearch: { - apiKey: { source: "env", provider: "default", id: "MISSING_GROK_API_KEY" }, - }, - }, - }, - }, - }, - }), - env: { - WEB_SEARCH_API_KEY: "web-search-ref", // pragma: allowlist secret - }, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => ({ version: 1, profiles: {} }), - }); - - expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref"); - const xaiWebSearchConfig = snapshot.config.plugins?.entries?.xai?.config as - | { webSearch?: { apiKey?: unknown } } - | undefined; - expect(xaiWebSearchConfig?.webSearch?.apiKey).toEqual({ - source: "env", - provider: "default", - id: "MISSING_GROK_API_KEY", - }); - expect(snapshot.warnings).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE", - path: "plugins.entries.xai.config.webSearch.apiKey", - }), - ]), - ); - }); - - it("keeps non-selected provider refs inactive in web search auto mode", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - search: { - enabled: true, - apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" }, - }, - }, - }, - plugins: { - entries: { - google: { - config: { - webSearch: { - apiKey: { - source: "env", - provider: "default", - id: "WEB_SEARCH_GEMINI_API_KEY", - }, - }, - }, - }, - }, - }, - }), - env: { - WEB_SEARCH_API_KEY: "web-search-ref", // pragma: allowlist secret - WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", // pragma: allowlist secret - }, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => ({ version: 1, profiles: {} }), - }); - - expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref"); - const googleWebSearchConfig = snapshot.config.plugins?.entries?.google?.config as - | { webSearch?: { apiKey?: unknown } } - | undefined; - expect(googleWebSearchConfig?.webSearch?.apiKey).toEqual({ - source: "env", - provider: "default", - id: "WEB_SEARCH_GEMINI_API_KEY", - }); - expect(snapshot.webTools.search.selectedProvider).toBe("brave"); - expect(snapshot.warnings).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE", - path: "plugins.entries.google.config.webSearch.apiKey", - }), - ]), - ); - }); - - it("resolves selected web search provider ref even when provider config is disabled", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - search: { - enabled: true, - provider: "gemini", - }, - }, - }, - plugins: { - entries: { - google: { - config: { - webSearch: { - enabled: false, - apiKey: { - source: "env", - provider: "default", - id: "WEB_SEARCH_GEMINI_API_KEY", - }, - }, - }, - }, - }, - }, - }), - env: { - WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", // pragma: allowlist secret - }, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => ({ version: 1, profiles: {} }), - }); - const resolvedGoogleWebSearchConfig = snapshot.config.plugins?.entries?.google?.config as - | { webSearch?: { apiKey?: unknown } } - | undefined; - expect(resolvedGoogleWebSearchConfig?.webSearch?.apiKey).toBe("web-search-gemini-ref"); - expect(snapshot.warnings.map((warning) => warning.path)).not.toContain( - "plugins.entries.google.config.webSearch.apiKey", - ); - }); - - it("fails fast at startup when selected web search provider ref is unresolved", async () => { - await expect( - prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - search: { - enabled: true, - provider: "gemini", - }, - }, - }, - plugins: { - entries: { - google: { - config: { - webSearch: { - apiKey: { - source: "env", - provider: "default", - id: "MISSING_WEB_SEARCH_GEMINI_API_KEY", - }, - }, - }, - }, - }, - }, - }), - env: {}, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => ({ version: 1, profiles: {} }), - }), - ).rejects.toThrow("[WEB_SEARCH_KEY_UNRESOLVED_NO_FALLBACK]"); - }); - - it("exposes active runtime web tool metadata as a defensive clone", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - search: { - provider: "gemini", - }, - }, - }, - plugins: { - entries: { - google: { - config: { - webSearch: { - apiKey: { - source: "env", - provider: "default", - id: "WEB_SEARCH_GEMINI_API_KEY", - }, - }, - }, - }, - }, - }, - }), - env: { - WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", // pragma: allowlist secret - }, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => ({ version: 1, profiles: {} }), - }); - - activateSecretsRuntimeSnapshot(snapshot); - - const first = getActiveRuntimeWebToolsMetadata(); - expect(first?.search.providerConfigured).toBe("gemini"); - expect(first?.search.selectedProvider).toBe("gemini"); - expect(first?.search.selectedProviderKeySource).toBe("secretRef"); - if (!first) { - throw new Error("missing runtime web tools metadata"); - } - first.search.providerConfigured = "brave"; - first.search.selectedProvider = "brave"; - - const second = getActiveRuntimeWebToolsMetadata(); - expect(second?.search.providerConfigured).toBe("gemini"); - expect(second?.search.selectedProvider).toBe("gemini"); - }); - it("resolves model provider request secret refs for headers, auth, and tls material", async () => { const config = asConfig({ models: { diff --git a/src/secrets/runtime.ts b/src/secrets/runtime.ts index 2e40635cd2f..ee989ab48fc 100644 --- a/src/secrets/runtime.ts +++ b/src/secrets/runtime.ts @@ -17,27 +17,19 @@ import { setRuntimeConfigSnapshot, type OpenClawConfig, } from "../config/config.js"; -import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js"; import type { PluginOrigin } from "../plugins/types.js"; import { resolveUserPath } from "../utils.js"; import { collectCommandSecretAssignmentsFromSnapshot, type CommandSecretAssignment, } from "./command-config.js"; -import { resolveSecretRefValues } from "./resolve.js"; -import { collectAuthStoreAssignments } from "./runtime-auth-collectors.js"; -import { collectConfigAssignments } from "./runtime-config-collectors.js"; -import { - applyResolvedAssignments, - createResolverContext, - type SecretResolverWarning, -} from "./runtime-shared.js"; +import { type SecretResolverWarning } from "./runtime-shared.js"; import { clearActiveRuntimeWebToolsMetadata, getActiveRuntimeWebToolsMetadata as getActiveRuntimeWebToolsMetadataFromState, setActiveRuntimeWebToolsMetadata, } from "./runtime-web-tools-state.js"; -import { resolveRuntimeWebTools, type RuntimeWebToolsMetadata } from "./runtime-web-tools.js"; +import type { RuntimeWebToolsMetadata } from "./runtime-web-tools.js"; export type { SecretResolverWarning } from "./runtime-shared.js"; @@ -75,6 +67,18 @@ const preparedSnapshotRefreshContext = new WeakMap< PreparedSecretsRuntimeSnapshot, SecretsRuntimeRefreshContext >(); +let runtimeManifestPromise: Promise | null = null; +let runtimePreparePromise: Promise | null = null; + +function loadRuntimeManifestHelpers() { + runtimeManifestPromise ??= import("./runtime-manifest.runtime.js"); + return runtimeManifestPromise; +} + +function loadRuntimePrepareHelpers() { + runtimePreparePromise ??= import("./runtime-prepare.runtime.js"); + return runtimePreparePromise; +} function cloneSnapshot(snapshot: PreparedSecretsRuntimeSnapshot): PreparedSecretsRuntimeSnapshot { return { @@ -130,14 +134,15 @@ function resolveRefreshAgentDirs( return [...new Set([...context.explicitAgentDirs, ...configDerived])]; } -function resolveLoadablePluginOrigins(params: { +async function resolveLoadablePluginOrigins(params: { config: OpenClawConfig; env: NodeJS.ProcessEnv; -}): ReadonlyMap { +}): Promise> { const workspaceDir = resolveAgentWorkspaceDir( params.config, resolveDefaultAgentId(params.config), ); + const { loadPluginManifestRegistry } = await loadRuntimeManifestHelpers(); const manifestRegistry = loadPluginManifestRegistry({ config: params.config, workspaceDir, @@ -172,12 +177,20 @@ export async function prepareSecretsRuntimeSnapshot(params: { /** Test override for discovered loadable plugins and their origins. */ loadablePluginOrigins?: ReadonlyMap; }): Promise { + const { + applyResolvedAssignments, + collectAuthStoreAssignments, + collectConfigAssignments, + createResolverContext, + resolveRuntimeWebTools, + resolveSecretRefValues, + } = await loadRuntimePrepareHelpers(); const runtimeEnv = mergeSecretsRuntimeEnv(params.env); const sourceConfig = structuredClone(params.config); const resolvedConfig = structuredClone(params.config); const loadablePluginOrigins = params.loadablePluginOrigins ?? - resolveLoadablePluginOrigins({ config: sourceConfig, env: runtimeEnv }); + (await resolveLoadablePluginOrigins({ config: sourceConfig, env: runtimeEnv })); const context = createResolverContext({ sourceConfig, env: runtimeEnv, @@ -249,10 +262,7 @@ export function activateSecretsRuntimeSnapshot(snapshot: PreparedSecretsRuntimeS env: { ...process.env } as Record, explicitAgentDirs: null, loadAuthStore: loadAuthProfileStoreForSecretsRuntime, - loadablePluginOrigins: resolveLoadablePluginOrigins({ - config: next.sourceConfig, - env: process.env, - }), + loadablePluginOrigins: new Map(), } satisfies SecretsRuntimeRefreshContext); setRuntimeConfigSnapshot(next.config, next.sourceConfig); replaceRuntimeAuthProfileStoreSnapshots(next.authStores);