Skip to content

Commit 7bae391

Browse files
committed
perf(secrets): split runtime snapshot coverage
1 parent 2810a4f commit 7bae391

15 files changed

Lines changed: 1858 additions & 1067 deletions
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
3+
import { loadConfig } from "../config/config.js";
4+
import { withEnvAsync } from "../test-utils/env.js";
5+
import {
6+
asConfig,
7+
beginSecretsRuntimeIsolationForTest,
8+
EMPTY_LOADABLE_PLUGIN_ORIGINS,
9+
endSecretsRuntimeIsolationForTest,
10+
loadAuthStoreWithProfiles,
11+
OPENAI_ENV_KEY_REF,
12+
type SecretsRuntimeEnvSnapshot,
13+
} from "./runtime-auth.integration.test-helpers.js";
14+
import { activateSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot } from "./runtime.js";
15+
16+
vi.unmock("../version.js");
17+
18+
describe("secrets runtime snapshot auth activation", () => {
19+
let envSnapshot: SecretsRuntimeEnvSnapshot;
20+
21+
beforeEach(() => {
22+
envSnapshot = beginSecretsRuntimeIsolationForTest();
23+
});
24+
25+
afterEach(() => {
26+
endSecretsRuntimeIsolationForTest(envSnapshot);
27+
});
28+
29+
it("activates runtime snapshots for loadConfig and ensureAuthProfileStore", async () => {
30+
await withEnvAsync(
31+
{
32+
OPENCLAW_BUNDLED_PLUGINS_DIR: undefined,
33+
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
34+
OPENCLAW_VERSION: undefined,
35+
},
36+
async () => {
37+
const prepared = await prepareSecretsRuntimeSnapshot({
38+
config: asConfig({
39+
models: {
40+
providers: {
41+
openai: {
42+
baseUrl: "https://api.openai.com/v1",
43+
apiKey: OPENAI_ENV_KEY_REF,
44+
models: [],
45+
},
46+
},
47+
},
48+
}),
49+
env: { OPENAI_API_KEY: "sk-runtime" },
50+
agentDirs: ["/tmp/openclaw-agent-main"],
51+
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
52+
loadAuthStore: () =>
53+
loadAuthStoreWithProfiles({
54+
"openai:default": {
55+
type: "api_key",
56+
provider: "openai",
57+
keyRef: OPENAI_ENV_KEY_REF,
58+
},
59+
}),
60+
});
61+
62+
activateSecretsRuntimeSnapshot(prepared);
63+
64+
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-runtime");
65+
expect(
66+
ensureAuthProfileStore("/tmp/openclaw-agent-main").profiles["openai:default"],
67+
).toMatchObject({
68+
type: "api_key",
69+
key: "sk-runtime",
70+
});
71+
},
72+
);
73+
});
74+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import os from "node:os";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+
import { withTempHome } from "../config/home-env.test-harness.js";
4+
import {
5+
beginSecretsRuntimeIsolationForTest,
6+
createOpenAIFileRuntimeConfig,
7+
createOpenAIFileRuntimeFixture,
8+
EMPTY_LOADABLE_PLUGIN_ORIGINS,
9+
endSecretsRuntimeIsolationForTest,
10+
expectResolvedOpenAIRuntime,
11+
loadAuthStoreWithProfiles,
12+
OPENAI_FILE_KEY_REF,
13+
type SecretsRuntimeEnvSnapshot,
14+
} from "./runtime-auth.integration.test-helpers.js";
15+
import {
16+
activateSecretsRuntimeSnapshot,
17+
getActiveSecretsRuntimeSnapshot,
18+
prepareSecretsRuntimeSnapshot,
19+
} from "./runtime.js";
20+
21+
vi.unmock("../version.js");
22+
23+
describe("secrets runtime snapshot auth refresh failure", () => {
24+
let envSnapshot: SecretsRuntimeEnvSnapshot;
25+
26+
beforeEach(() => {
27+
envSnapshot = beginSecretsRuntimeIsolationForTest();
28+
});
29+
30+
afterEach(() => {
31+
endSecretsRuntimeIsolationForTest(envSnapshot);
32+
});
33+
34+
it("keeps last-known-good runtime snapshot active when refresh preparation fails", async () => {
35+
if (os.platform() === "win32") {
36+
return;
37+
}
38+
await withTempHome("openclaw-secrets-runtime-refresh-fail-", async (home) => {
39+
const { secretFile, agentDir } = await createOpenAIFileRuntimeFixture(home);
40+
41+
let loadAuthStoreCalls = 0;
42+
const loadAuthStore = () => {
43+
loadAuthStoreCalls += 1;
44+
if (loadAuthStoreCalls > 1) {
45+
throw new Error("simulated secrets runtime refresh failure");
46+
}
47+
return loadAuthStoreWithProfiles({
48+
"openai:default": {
49+
type: "api_key",
50+
provider: "openai",
51+
keyRef: OPENAI_FILE_KEY_REF,
52+
},
53+
});
54+
};
55+
56+
const prepared = await prepareSecretsRuntimeSnapshot({
57+
config: createOpenAIFileRuntimeConfig(secretFile),
58+
agentDirs: [agentDir],
59+
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
60+
loadAuthStore,
61+
});
62+
63+
activateSecretsRuntimeSnapshot(prepared);
64+
expectResolvedOpenAIRuntime(agentDir);
65+
66+
await expect(
67+
prepareSecretsRuntimeSnapshot({
68+
config: {
69+
...createOpenAIFileRuntimeConfig(secretFile),
70+
gateway: { auth: { mode: "token" } },
71+
},
72+
agentDirs: [agentDir],
73+
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
74+
loadAuthStore,
75+
}),
76+
).rejects.toThrow(/simulated secrets runtime refresh failure/i);
77+
78+
const activeAfterFailure = getActiveSecretsRuntimeSnapshot();
79+
expect(activeAfterFailure).not.toBeNull();
80+
expectResolvedOpenAIRuntime(agentDir);
81+
expect(activeAfterFailure?.sourceConfig.models?.providers?.openai?.apiKey).toEqual(
82+
OPENAI_FILE_KEY_REF,
83+
);
84+
});
85+
});
86+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import os from "node:os";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+
import { withTempHome } from "../config/home-env.test-harness.js";
4+
import {
5+
beginSecretsRuntimeIsolationForTest,
6+
createOpenAIFileRuntimeConfig,
7+
createOpenAIFileRuntimeFixture,
8+
EMPTY_LOADABLE_PLUGIN_ORIGINS,
9+
endSecretsRuntimeIsolationForTest,
10+
expectResolvedOpenAIRuntime,
11+
type SecretsRuntimeEnvSnapshot,
12+
} from "./runtime-auth.integration.test-helpers.js";
13+
import { activateSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot } from "./runtime.js";
14+
15+
vi.unmock("../version.js");
16+
17+
describe("secrets runtime snapshot auth write refresh", () => {
18+
let envSnapshot: SecretsRuntimeEnvSnapshot;
19+
20+
beforeEach(() => {
21+
envSnapshot = beginSecretsRuntimeIsolationForTest();
22+
});
23+
24+
afterEach(() => {
25+
endSecretsRuntimeIsolationForTest(envSnapshot);
26+
});
27+
28+
it("keeps active secrets runtime snapshots resolved after refreshes", async () => {
29+
if (os.platform() === "win32") {
30+
return;
31+
}
32+
await withTempHome("openclaw-secrets-runtime-write-", async (home) => {
33+
const { secretFile, agentDir } = await createOpenAIFileRuntimeFixture(home);
34+
35+
const prepared = await prepareSecretsRuntimeSnapshot({
36+
config: createOpenAIFileRuntimeConfig(secretFile),
37+
agentDirs: [agentDir],
38+
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
39+
});
40+
41+
activateSecretsRuntimeSnapshot(prepared);
42+
43+
expectResolvedOpenAIRuntime(agentDir);
44+
45+
const refreshed = await prepareSecretsRuntimeSnapshot({
46+
config: {
47+
...createOpenAIFileRuntimeConfig(secretFile),
48+
gateway: { auth: { mode: "token" } },
49+
},
50+
agentDirs: [agentDir],
51+
loadablePluginOrigins: EMPTY_LOADABLE_PLUGIN_ORIGINS,
52+
});
53+
activateSecretsRuntimeSnapshot(refreshed);
54+
55+
expectResolvedOpenAIRuntime(agentDir);
56+
expect(refreshed.config.gateway?.auth).toEqual({ mode: "token" });
57+
expect(refreshed.sourceConfig.gateway?.auth).toEqual({ mode: "token" });
58+
});
59+
});
60+
});
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { iterateBootstrapChannelPlugins } from "../channels/plugins/bootstrap-registry.js";
1+
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
22
import type { OpenClawConfig } from "../config/config.js";
33
import { type ResolverContext, type SecretDefaults } from "./runtime-shared.js";
44

@@ -7,10 +7,15 @@ export function collectChannelConfigAssignments(params: {
77
defaults: SecretDefaults | undefined;
88
context: ResolverContext;
99
}): void {
10-
if (!params.config.channels || Object.keys(params.config.channels).length === 0) {
10+
const channelIds = Object.keys(params.config.channels ?? {});
11+
if (channelIds.length === 0) {
1112
return;
1213
}
13-
for (const plugin of iterateBootstrapChannelPlugins()) {
14+
for (const channelId of channelIds) {
15+
const plugin = getBootstrapChannelPlugin(channelId);
16+
if (!plugin) {
17+
continue;
18+
}
1419
plugin.secrets?.collectRuntimeConfigAssignments?.(params);
1520
}
1621
}

0 commit comments

Comments
 (0)