mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 18:33:37 +00:00
test: dedupe secrets and guardrail fixtures
This commit is contained in:
@@ -35,41 +35,55 @@ function createEnv(stateDir: string): NodeJS.ProcessEnv {
|
||||
};
|
||||
}
|
||||
|
||||
async function createLegacyStateFixture(params?: { includePreKey?: boolean }) {
|
||||
const root = await createTempDir();
|
||||
const stateDir = path.join(root, ".openclaw");
|
||||
const env = createEnv(stateDir);
|
||||
const cfg = createConfig();
|
||||
|
||||
await fs.mkdir(path.join(stateDir, "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agents", "worker-1", "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agent"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ legacyDirect: { sessionId: "legacy-direct", updatedAt: 10 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "sessions", "trace.jsonl"), "{}\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ "group:123@g.us": { sessionId: "group-session", updatedAt: 5 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "agent", "settings.json"), '{"ok":true}\n', "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "creds.json"), '{"auth":true}\n', "utf8");
|
||||
if (params?.includePreKey) {
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "pre-key-1.json"),
|
||||
'{"preKey":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "oauth.json"), '{"oauth":true}\n', "utf8");
|
||||
await fs.writeFile(resolveChannelAllowFromPath("telegram", env), '["123","456"]\n', "utf8");
|
||||
|
||||
return {
|
||||
root,
|
||||
stateDir,
|
||||
env,
|
||||
cfg,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await tempDirs.cleanup();
|
||||
});
|
||||
|
||||
describe("state migrations", () => {
|
||||
it("detects legacy sessions, agent files, whatsapp auth, and telegram allowFrom copies", async () => {
|
||||
const root = await createTempDir();
|
||||
const stateDir = path.join(root, ".openclaw");
|
||||
const env = createEnv(stateDir);
|
||||
const cfg = createConfig();
|
||||
|
||||
await fs.mkdir(path.join(stateDir, "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agents", "worker-1", "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agent"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ legacyDirect: { sessionId: "legacy-direct", updatedAt: 10 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "sessions", "trace.jsonl"), "{}\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ "group:123@g.us": { sessionId: "group-session", updatedAt: 5 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "agent", "settings.json"), '{"ok":true}\n', "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "creds.json"), '{"auth":true}\n', "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "oauth.json"),
|
||||
'{"oauth":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(resolveChannelAllowFromPath("telegram", env), '["123","456"]\n', "utf8");
|
||||
const { root, stateDir, env, cfg } = await createLegacyStateFixture();
|
||||
|
||||
const detected = await detectLegacyStateMigrations({
|
||||
cfg,
|
||||
@@ -99,40 +113,7 @@ describe("state migrations", () => {
|
||||
});
|
||||
|
||||
it("runs legacy state migrations and canonicalizes the merged session store", async () => {
|
||||
const root = await createTempDir();
|
||||
const stateDir = path.join(root, ".openclaw");
|
||||
const env = createEnv(stateDir);
|
||||
const cfg = createConfig();
|
||||
|
||||
await fs.mkdir(path.join(stateDir, "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agents", "worker-1", "sessions"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "agent"), { recursive: true });
|
||||
await fs.mkdir(path.join(stateDir, "credentials"), { recursive: true });
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ legacyDirect: { sessionId: "legacy-direct", updatedAt: 10 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "sessions", "trace.jsonl"), "{}\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "agents", "worker-1", "sessions", "sessions.json"),
|
||||
`${JSON.stringify({ "group:123@g.us": { sessionId: "group-session", updatedAt: 5 } }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(path.join(stateDir, "agent", "settings.json"), '{"ok":true}\n', "utf8");
|
||||
await fs.writeFile(path.join(stateDir, "credentials", "creds.json"), '{"auth":true}\n', "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "pre-key-1.json"),
|
||||
'{"preKey":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(stateDir, "credentials", "oauth.json"),
|
||||
'{"oauth":true}\n',
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(resolveChannelAllowFromPath("telegram", env), '["123","456"]\n', "utf8");
|
||||
const { root, stateDir, env, cfg } = await createLegacyStateFixture({ includePreKey: true });
|
||||
|
||||
const detected = await detectLegacyStateMigrations({
|
||||
cfg,
|
||||
|
||||
@@ -164,6 +164,12 @@ let extensionSourceFilesCache: string[] | null = null;
|
||||
let coreSourceFilesCache: string[] | null = null;
|
||||
const extensionFilesCache = new Map<string, string[]>();
|
||||
|
||||
type SourceFileCollectorOptions = {
|
||||
rootDir: string;
|
||||
shouldSkipPath?: (normalizedFullPath: string) => boolean;
|
||||
shouldSkipEntry?: (params: { entryName: string; normalizedFullPath: string }) => boolean;
|
||||
};
|
||||
|
||||
function readSource(path: string): string {
|
||||
const fullPath = resolve(REPO_ROOT, path);
|
||||
const cached = sourceTextCache.get(fullPath);
|
||||
@@ -179,6 +185,51 @@ function normalizePath(path: string): string {
|
||||
return path.replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
function collectSourceFiles(
|
||||
cached: string[] | undefined | null,
|
||||
options: SourceFileCollectorOptions,
|
||||
): string[] {
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const files: string[] = [];
|
||||
const stack = [options.rootDir];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop();
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||
const fullPath = resolve(current, entry.name);
|
||||
const normalizedFullPath = normalizePath(fullPath);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage") {
|
||||
continue;
|
||||
}
|
||||
if (options.shouldSkipPath?.(normalizedFullPath)) {
|
||||
continue;
|
||||
}
|
||||
stack.push(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !/\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.name.endsWith(".d.ts")) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
options.shouldSkipPath?.(normalizedFullPath) ||
|
||||
options.shouldSkipEntry?.({ entryName: entry.name, normalizedFullPath })
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function readSetupBarrelImportBlock(path: string): string {
|
||||
const lines = readSource(path).split("\n");
|
||||
const targetLineIndex = lines.findIndex((line) =>
|
||||
@@ -195,145 +246,55 @@ function readSetupBarrelImportBlock(path: string): string {
|
||||
}
|
||||
|
||||
function collectExtensionSourceFiles(): string[] {
|
||||
if (extensionSourceFilesCache) {
|
||||
return extensionSourceFilesCache;
|
||||
}
|
||||
const extensionsDir = normalizePath(resolve(ROOT_DIR, "..", "extensions"));
|
||||
const sharedExtensionsDir = normalizePath(resolve(extensionsDir, "shared"));
|
||||
const files: string[] = [];
|
||||
const stack = [resolve(ROOT_DIR, "..", "extensions")];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop();
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||
const fullPath = resolve(current, entry.name);
|
||||
const normalizedFullPath = normalizePath(fullPath);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage") {
|
||||
continue;
|
||||
}
|
||||
stack.push(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !/\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.name.endsWith(".d.ts") || normalizedFullPath.includes(sharedExtensionsDir)) {
|
||||
continue;
|
||||
}
|
||||
if (normalizedFullPath.includes(`${extensionsDir}/shared/`)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
normalizedFullPath.includes(".test.") ||
|
||||
normalizedFullPath.includes(".test-") ||
|
||||
normalizedFullPath.includes(".fixture.") ||
|
||||
normalizedFullPath.includes(".snap") ||
|
||||
normalizedFullPath.includes("test-support") ||
|
||||
entry.name === "api.ts" ||
|
||||
entry.name === "runtime-api.ts"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
extensionSourceFilesCache = files;
|
||||
return files;
|
||||
extensionSourceFilesCache = collectSourceFiles(extensionSourceFilesCache, {
|
||||
rootDir: resolve(ROOT_DIR, "..", "extensions"),
|
||||
shouldSkipPath: (normalizedFullPath) =>
|
||||
normalizedFullPath.includes(sharedExtensionsDir) ||
|
||||
normalizedFullPath.includes(`${extensionsDir}/shared/`),
|
||||
shouldSkipEntry: ({ entryName, normalizedFullPath }) =>
|
||||
normalizedFullPath.includes(".test.") ||
|
||||
normalizedFullPath.includes(".test-") ||
|
||||
normalizedFullPath.includes(".fixture.") ||
|
||||
normalizedFullPath.includes(".snap") ||
|
||||
normalizedFullPath.includes("test-support") ||
|
||||
entryName === "api.ts" ||
|
||||
entryName === "runtime-api.ts",
|
||||
});
|
||||
return extensionSourceFilesCache;
|
||||
}
|
||||
|
||||
function collectCoreSourceFiles(): string[] {
|
||||
if (coreSourceFilesCache) {
|
||||
return coreSourceFilesCache;
|
||||
}
|
||||
const srcDir = resolve(ROOT_DIR, "..", "src");
|
||||
const normalizedPluginSdkDir = normalizePath(resolve(ROOT_DIR, "plugin-sdk"));
|
||||
const files: string[] = [];
|
||||
const stack = [srcDir];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop();
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||
const fullPath = resolve(current, entry.name);
|
||||
const normalizedFullPath = normalizePath(fullPath);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage") {
|
||||
continue;
|
||||
}
|
||||
stack.push(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !/\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.name.endsWith(".d.ts")) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
normalizedFullPath.includes(".test.") ||
|
||||
normalizedFullPath.includes(".mock-harness.") ||
|
||||
normalizedFullPath.includes(".spec.") ||
|
||||
normalizedFullPath.includes(".fixture.") ||
|
||||
normalizedFullPath.includes(".snap") ||
|
||||
// src/plugin-sdk is the curated bridge layer; validate its contracts with dedicated
|
||||
// plugin-sdk guardrails instead of the generic "core should not touch extensions" rule.
|
||||
normalizedFullPath.includes(`${normalizedPluginSdkDir}/`)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
coreSourceFilesCache = files;
|
||||
return files;
|
||||
coreSourceFilesCache = collectSourceFiles(coreSourceFilesCache, {
|
||||
rootDir: srcDir,
|
||||
shouldSkipEntry: ({ normalizedFullPath }) =>
|
||||
normalizedFullPath.includes(".test.") ||
|
||||
normalizedFullPath.includes(".mock-harness.") ||
|
||||
normalizedFullPath.includes(".spec.") ||
|
||||
normalizedFullPath.includes(".fixture.") ||
|
||||
normalizedFullPath.includes(".snap") ||
|
||||
// src/plugin-sdk is the curated bridge layer; validate its contracts with dedicated
|
||||
// plugin-sdk guardrails instead of the generic "core should not touch extensions" rule.
|
||||
normalizedFullPath.includes(`${normalizedPluginSdkDir}/`),
|
||||
});
|
||||
return coreSourceFilesCache;
|
||||
}
|
||||
|
||||
function collectExtensionFiles(extensionId: string): string[] {
|
||||
const cached = extensionFilesCache.get(extensionId);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const extensionDir = resolve(ROOT_DIR, "..", "extensions", extensionId);
|
||||
const files: string[] = [];
|
||||
const stack = [extensionDir];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop();
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||
const fullPath = resolve(current, entry.name);
|
||||
const normalizedFullPath = normalizePath(fullPath);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage") {
|
||||
continue;
|
||||
}
|
||||
stack.push(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile() || !/\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.name.endsWith(".d.ts")) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
normalizedFullPath.includes(".test.") ||
|
||||
normalizedFullPath.includes(".test-") ||
|
||||
normalizedFullPath.includes(".spec.") ||
|
||||
normalizedFullPath.includes(".fixture.") ||
|
||||
normalizedFullPath.includes(".snap") ||
|
||||
entry.name === "runtime-api.ts"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
const files = collectSourceFiles(cached, {
|
||||
rootDir: resolve(ROOT_DIR, "..", "extensions", extensionId),
|
||||
shouldSkipEntry: ({ entryName, normalizedFullPath }) =>
|
||||
normalizedFullPath.includes(".test.") ||
|
||||
normalizedFullPath.includes(".test-") ||
|
||||
normalizedFullPath.includes(".spec.") ||
|
||||
normalizedFullPath.includes(".fixture.") ||
|
||||
normalizedFullPath.includes(".snap") ||
|
||||
entryName === "runtime-api.ts",
|
||||
});
|
||||
extensionFilesCache.set(extensionId, files);
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ import {
|
||||
vi.unmock("../version.js");
|
||||
|
||||
const OPENAI_ENV_KEY_REF = { source: "env", provider: "default", id: "OPENAI_API_KEY" } as const;
|
||||
const OPENAI_FILE_KEY_REF = {
|
||||
source: "file",
|
||||
provider: "default",
|
||||
id: "/providers/openai/apiKey",
|
||||
} as const;
|
||||
const allowInsecureTempSecretFile = process.platform === "win32";
|
||||
|
||||
function asConfig(value: unknown): OpenClawConfig {
|
||||
@@ -35,6 +40,77 @@ function loadAuthStoreWithProfiles(profiles: AuthProfileStore["profiles"]): Auth
|
||||
};
|
||||
}
|
||||
|
||||
async function createOpenAIFileRuntimeFixture(home: string) {
|
||||
const configDir = path.join(home, ".openclaw");
|
||||
const secretFile = path.join(configDir, "secrets.json");
|
||||
const agentDir = path.join(configDir, "agents", "main", "agent");
|
||||
const authStorePath = path.join(agentDir, "auth-profiles.json");
|
||||
|
||||
await fs.mkdir(agentDir, { recursive: true });
|
||||
await fs.chmod(configDir, 0o700).catch(() => {});
|
||||
await fs.writeFile(
|
||||
secretFile,
|
||||
`${JSON.stringify({ providers: { openai: { apiKey: "sk-file-runtime" } } }, null, 2)}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
await fs.writeFile(
|
||||
authStorePath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai:default": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
keyRef: OPENAI_FILE_KEY_REF,
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
|
||||
return {
|
||||
configDir,
|
||||
secretFile,
|
||||
agentDir,
|
||||
};
|
||||
}
|
||||
|
||||
function createOpenAIFileRuntimeConfig(secretFile: string): OpenClawConfig {
|
||||
return asConfig({
|
||||
secrets: {
|
||||
providers: {
|
||||
default: {
|
||||
source: "file",
|
||||
path: secretFile,
|
||||
mode: "json",
|
||||
...(allowInsecureTempSecretFile ? { allowInsecurePath: true } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
apiKey: OPENAI_FILE_KEY_REF,
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function expectResolvedOpenAIRuntime(agentDir: string) {
|
||||
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-file-runtime");
|
||||
expect(ensureAuthProfileStore(agentDir).profiles["openai:default"]).toMatchObject({
|
||||
type: "api_key",
|
||||
key: "sk-file-runtime",
|
||||
});
|
||||
}
|
||||
|
||||
describe("secrets runtime snapshot integration", () => {
|
||||
let envSnapshot: ReturnType<typeof captureEnv>;
|
||||
|
||||
@@ -106,68 +182,16 @@ describe("secrets runtime snapshot integration", () => {
|
||||
return;
|
||||
}
|
||||
await withTempHome("openclaw-secrets-runtime-write-", async (home) => {
|
||||
const configDir = path.join(home, ".openclaw");
|
||||
const secretFile = path.join(configDir, "secrets.json");
|
||||
const agentDir = path.join(configDir, "agents", "main", "agent");
|
||||
const authStorePath = path.join(agentDir, "auth-profiles.json");
|
||||
await fs.mkdir(agentDir, { recursive: true });
|
||||
await fs.chmod(configDir, 0o700).catch(() => {});
|
||||
await fs.writeFile(
|
||||
secretFile,
|
||||
`${JSON.stringify({ providers: { openai: { apiKey: "sk-file-runtime" } } }, null, 2)}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
await fs.writeFile(
|
||||
authStorePath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai:default": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
keyRef: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
const { secretFile, agentDir } = await createOpenAIFileRuntimeFixture(home);
|
||||
|
||||
const prepared = await prepareSecretsRuntimeSnapshot({
|
||||
config: asConfig({
|
||||
secrets: {
|
||||
providers: {
|
||||
default: {
|
||||
source: "file",
|
||||
path: secretFile,
|
||||
mode: "json",
|
||||
...(allowInsecureTempSecretFile ? { allowInsecurePath: true } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
config: createOpenAIFileRuntimeConfig(secretFile),
|
||||
agentDirs: [agentDir],
|
||||
});
|
||||
|
||||
activateSecretsRuntimeSnapshot(prepared);
|
||||
|
||||
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-file-runtime");
|
||||
expect(ensureAuthProfileStore(agentDir).profiles["openai:default"]).toMatchObject({
|
||||
type: "api_key",
|
||||
key: "sk-file-runtime",
|
||||
});
|
||||
expectResolvedOpenAIRuntime(agentDir);
|
||||
|
||||
await writeConfigFile({
|
||||
...loadConfig(),
|
||||
@@ -175,11 +199,7 @@ describe("secrets runtime snapshot integration", () => {
|
||||
});
|
||||
|
||||
expect(loadConfig().gateway?.auth).toEqual({ mode: "token" });
|
||||
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-file-runtime");
|
||||
expect(ensureAuthProfileStore(agentDir).profiles["openai:default"]).toMatchObject({
|
||||
type: "api_key",
|
||||
key: "sk-file-runtime",
|
||||
});
|
||||
expectResolvedOpenAIRuntime(agentDir);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,35 +208,7 @@ describe("secrets runtime snapshot integration", () => {
|
||||
return;
|
||||
}
|
||||
await withTempHome("openclaw-secrets-runtime-refresh-fail-", async (home) => {
|
||||
const configDir = path.join(home, ".openclaw");
|
||||
const secretFile = path.join(configDir, "secrets.json");
|
||||
const agentDir = path.join(configDir, "agents", "main", "agent");
|
||||
const authStorePath = path.join(agentDir, "auth-profiles.json");
|
||||
await fs.mkdir(agentDir, { recursive: true });
|
||||
await fs.chmod(configDir, 0o700).catch(() => {});
|
||||
await fs.writeFile(
|
||||
secretFile,
|
||||
`${JSON.stringify({ providers: { openai: { apiKey: "sk-file-runtime" } } }, null, 2)}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
await fs.writeFile(
|
||||
authStorePath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
profiles: {
|
||||
"openai:default": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
keyRef: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
const { secretFile, agentDir } = await createOpenAIFileRuntimeFixture(home);
|
||||
|
||||
let loadAuthStoreCalls = 0;
|
||||
const loadAuthStore = () => {
|
||||
@@ -228,33 +220,13 @@ describe("secrets runtime snapshot integration", () => {
|
||||
"openai:default": {
|
||||
type: "api_key",
|
||||
provider: "openai",
|
||||
keyRef: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
|
||||
keyRef: OPENAI_FILE_KEY_REF,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const prepared = await prepareSecretsRuntimeSnapshot({
|
||||
config: asConfig({
|
||||
secrets: {
|
||||
providers: {
|
||||
default: {
|
||||
source: "file",
|
||||
path: secretFile,
|
||||
mode: "json",
|
||||
...(allowInsecureTempSecretFile ? { allowInsecurePath: true } : {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
config: createOpenAIFileRuntimeConfig(secretFile),
|
||||
agentDirs: [agentDir],
|
||||
loadAuthStore,
|
||||
});
|
||||
@@ -273,16 +245,10 @@ describe("secrets runtime snapshot integration", () => {
|
||||
const activeAfterFailure = getActiveSecretsRuntimeSnapshot();
|
||||
expect(activeAfterFailure).not.toBeNull();
|
||||
expect(loadConfig().gateway?.auth).toBeUndefined();
|
||||
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-file-runtime");
|
||||
expect(activeAfterFailure?.sourceConfig.models?.providers?.openai?.apiKey).toEqual({
|
||||
source: "file",
|
||||
provider: "default",
|
||||
id: "/providers/openai/apiKey",
|
||||
});
|
||||
expect(ensureAuthProfileStore(agentDir).profiles["openai:default"]).toMatchObject({
|
||||
type: "api_key",
|
||||
key: "sk-file-runtime",
|
||||
});
|
||||
expectResolvedOpenAIRuntime(agentDir);
|
||||
expect(activeAfterFailure?.sourceConfig.models?.providers?.openai?.apiKey).toEqual(
|
||||
OPENAI_FILE_KEY_REF,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user