import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ensureAuthProfileStore } from "../agents/auth-profiles.js"; import { loadConfig, writeConfigFile } from "../config/config.js"; import { withTempHome } from "../config/home-env.test-harness.js"; import { withEnvAsync } from "../test-utils/env.js"; import { asConfig, beginSecretsRuntimeIsolationForTest, createOpenAIFileRuntimeConfig, createOpenAIFileRuntimeFixture, EMPTY_LOADABLE_PLUGIN_ORIGINS, endSecretsRuntimeIsolationForTest, expectResolvedOpenAIRuntime, loadAuthStoreWithProfiles, OPENAI_ENV_KEY_REF, OPENAI_FILE_KEY_REF, type SecretsRuntimeEnvSnapshot, } from "./runtime.integration.test-helpers.js"; import { activateSecretsRuntimeSnapshot, getActiveSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot, } from "./runtime.js"; vi.unmock("../version.js"); describe("secrets runtime snapshot auth integration", () => { let envSnapshot: SecretsRuntimeEnvSnapshot; beforeEach(() => { envSnapshot = beginSecretsRuntimeIsolationForTest(); }); afterEach(() => { endSecretsRuntimeIsolationForTest(envSnapshot); }); it("activates runtime snapshots for loadConfig and ensureAuthProfileStore", async () => { await withEnvAsync( { OPENCLAW_BUNDLED_PLUGINS_DIR: undefined, OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", OPENCLAW_VERSION: undefined, }, async () => { const prepared = await prepareSecretsRuntimeSnapshot({ config: asConfig({ models: { providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: OPENAI_ENV_KEY_REF, models: [], }, }, }, }), env: { OPENAI_API_KEY: "sk-runtime" }, agentDirs: ["/tmp/openclaw-agent-main"], loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, loadAuthStore: () => loadAuthStoreWithProfiles({ "openai:default": { type: "api_key", provider: "openai", keyRef: OPENAI_ENV_KEY_REF, }, }), }); activateSecretsRuntimeSnapshot(prepared); expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-runtime"); expect( ensureAuthProfileStore("/tmp/openclaw-agent-main").profiles["openai:default"], ).toMatchObject({ type: "api_key", key: "sk-runtime", }); }, ); }); it("keeps active secrets runtime snapshots resolved after config writes", async () => { if (os.platform() === "win32") { return; } await withTempHome("openclaw-secrets-runtime-write-", async (home) => { const { secretFile, agentDir } = await createOpenAIFileRuntimeFixture(home); const prepared = await prepareSecretsRuntimeSnapshot({ config: createOpenAIFileRuntimeConfig(secretFile), agentDirs: [agentDir], loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, }); activateSecretsRuntimeSnapshot(prepared); expectResolvedOpenAIRuntime(agentDir); await writeConfigFile({ ...loadConfig(), gateway: { auth: { mode: "token" } }, }); expect(loadConfig().gateway?.auth).toEqual({ mode: "token" }); expectResolvedOpenAIRuntime(agentDir); }); }); it("keeps last-known-good runtime snapshot active when refresh fails after a write", async () => { if (os.platform() === "win32") { return; } await withTempHome("openclaw-secrets-runtime-refresh-fail-", async (home) => { const { secretFile, agentDir } = await createOpenAIFileRuntimeFixture(home); let loadAuthStoreCalls = 0; const loadAuthStore = () => { loadAuthStoreCalls += 1; if (loadAuthStoreCalls > 1) { throw new Error("simulated secrets runtime refresh failure"); } return loadAuthStoreWithProfiles({ "openai:default": { type: "api_key", provider: "openai", keyRef: OPENAI_FILE_KEY_REF, }, }); }; const prepared = await prepareSecretsRuntimeSnapshot({ config: createOpenAIFileRuntimeConfig(secretFile), agentDirs: [agentDir], loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, loadAuthStore, }); activateSecretsRuntimeSnapshot(prepared); await expect( writeConfigFile({ ...loadConfig(), gateway: { auth: { mode: "token" } }, }), ).rejects.toThrow( /runtime snapshot refresh failed: simulated secrets runtime refresh failure/i, ); const activeAfterFailure = getActiveSecretsRuntimeSnapshot(); expect(activeAfterFailure).not.toBeNull(); expect(loadConfig().gateway?.auth).toBeUndefined(); expectResolvedOpenAIRuntime(agentDir); expect(activeAfterFailure?.sourceConfig.models?.providers?.openai?.apiKey).toEqual( OPENAI_FILE_KEY_REF, ); }); }); it("recomputes config-derived agent dirs when refreshing active secrets runtime snapshots", async () => { await withTempHome("openclaw-secrets-runtime-agent-dirs-", async (home) => { const mainAgentDir = path.join(home, ".openclaw", "agents", "main", "agent"); const opsAgentDir = path.join(home, ".openclaw", "agents", "ops", "agent"); await fs.mkdir(mainAgentDir, { recursive: true }); await fs.mkdir(opsAgentDir, { recursive: true }); await fs.writeFile( path.join(mainAgentDir, "auth-profiles.json"), `${JSON.stringify( { version: 1, profiles: { "openai:default": { type: "api_key", provider: "openai", keyRef: OPENAI_ENV_KEY_REF, }, }, }, null, 2, )}\n`, { encoding: "utf8", mode: 0o600 }, ); await fs.writeFile( path.join(opsAgentDir, "auth-profiles.json"), `${JSON.stringify( { version: 1, profiles: { "anthropic:ops": { type: "api_key", provider: "anthropic", keyRef: { source: "env", provider: "default", id: "ANTHROPIC_API_KEY" }, }, }, }, null, 2, )}\n`, { encoding: "utf8", mode: 0o600 }, ); const prepared = await prepareSecretsRuntimeSnapshot({ config: asConfig({}), env: { OPENAI_API_KEY: "sk-main-runtime", ANTHROPIC_API_KEY: "sk-ops-runtime", }, loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS, }); activateSecretsRuntimeSnapshot(prepared); expect(ensureAuthProfileStore(opsAgentDir).profiles["anthropic:ops"]).toBeUndefined(); await writeConfigFile({ agents: { list: [{ id: "ops", agentDir: opsAgentDir }], }, }); expect(ensureAuthProfileStore(opsAgentDir).profiles["anthropic:ops"]).toMatchObject({ type: "api_key", key: "sk-ops-runtime", keyRef: { source: "env", provider: "default", id: "ANTHROPIC_API_KEY" }, }); }); }); });