Files
openclaw/src/secrets/runtime.test.ts

271 lines
8.1 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
import { loadConfig, type OpenClawConfig } from "../config/config.js";
import {
activateSecretsRuntimeSnapshot,
clearSecretsRuntimeSnapshot,
prepareSecretsRuntimeSnapshot,
} from "./runtime.js";
describe("secrets runtime snapshot", () => {
afterEach(() => {
clearSecretsRuntimeSnapshot();
});
it("resolves env refs for config and auth profiles", async () => {
const config: OpenClawConfig = {
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
skills: {
entries: {
"review-pr": {
enabled: true,
apiKey: { source: "env", provider: "default", id: "REVIEW_SKILL_API_KEY" },
},
},
},
};
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
env: {
OPENAI_API_KEY: "sk-env-openai",
GITHUB_TOKEN: "ghp-env-token",
REVIEW_SKILL_API_KEY: "sk-skill-ref",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
key: "old-openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
"github-copilot:default": {
type: "token",
provider: "github-copilot",
token: "old-gh",
tokenRef: { source: "env", provider: "default", id: "GITHUB_TOKEN" },
},
"openai:inline": {
type: "api_key",
provider: "openai",
key: "${OPENAI_API_KEY}",
},
},
}),
});
expect(snapshot.config.models?.providers?.openai?.apiKey).toBe("sk-env-openai");
expect(snapshot.config.skills?.entries?.["review-pr"]?.apiKey).toBe("sk-skill-ref");
expect(snapshot.warnings).toHaveLength(2);
expect(snapshot.authStores[0]?.store.profiles["openai:default"]).toMatchObject({
type: "api_key",
key: "sk-env-openai",
});
expect(snapshot.authStores[0]?.store.profiles["github-copilot:default"]).toMatchObject({
type: "token",
token: "ghp-env-token",
});
expect(snapshot.authStores[0]?.store.profiles["openai:inline"]).toMatchObject({
type: "api_key",
key: "sk-env-openai",
});
});
it("resolves file refs via configured file provider", async () => {
if (process.platform === "win32") {
return;
}
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-file-provider-"));
const secretsPath = path.join(root, "secrets.json");
try {
await fs.writeFile(
secretsPath,
JSON.stringify(
{
providers: {
openai: {
apiKey: "sk-from-file-provider",
},
},
},
null,
2,
),
"utf8",
);
await fs.chmod(secretsPath, 0o600);
const config: OpenClawConfig = {
secrets: {
providers: {
default: {
source: "file",
path: secretsPath,
mode: "json",
},
},
defaults: {
file: "default",
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
models: [],
},
},
},
};
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.models?.providers?.openai?.apiKey).toBe("sk-from-file-provider");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
it("fails when file provider payload is not a JSON object", async () => {
if (process.platform === "win32") {
return;
}
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-file-provider-bad-"));
const secretsPath = path.join(root, "secrets.json");
try {
await fs.writeFile(secretsPath, JSON.stringify(["not-an-object"]), "utf8");
await fs.chmod(secretsPath, 0o600);
await expect(
prepareSecretsRuntimeSnapshot({
config: {
secrets: {
providers: {
default: {
source: "file",
path: secretsPath,
mode: "json",
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
models: [],
},
},
},
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow("payload is not a JSON object");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
it("activates runtime snapshots for loadConfig and ensureAuthProfileStore", async () => {
const prepared = await prepareSecretsRuntimeSnapshot({
config: {
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
},
env: { OPENAI_API_KEY: "sk-runtime" },
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
},
}),
});
activateSecretsRuntimeSnapshot(prepared);
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-runtime");
const store = ensureAuthProfileStore("/tmp/openclaw-agent-main");
expect(store.profiles["openai:default"]).toMatchObject({
type: "api_key",
key: "sk-runtime",
});
});
it("does not write inherited auth stores during runtime secret activation", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-runtime-"));
const stateDir = path.join(root, ".openclaw");
const mainAgentDir = path.join(stateDir, "agents", "main", "agent");
const workerStorePath = path.join(stateDir, "agents", "worker", "agent", "auth-profiles.json");
const prevStateDir = process.env.OPENCLAW_STATE_DIR;
try {
await fs.mkdir(mainAgentDir, { recursive: true });
await fs.writeFile(
path.join(mainAgentDir, "auth-profiles.json"),
JSON.stringify({
version: 1,
profiles: {
"openai:default": {
type: "api_key",
provider: "openai",
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
},
},
}),
"utf8",
);
process.env.OPENCLAW_STATE_DIR = stateDir;
await prepareSecretsRuntimeSnapshot({
config: {
agents: {
list: [{ id: "worker" }],
},
},
env: { OPENAI_API_KEY: "sk-runtime-worker" },
});
await expect(fs.access(workerStorePath)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
if (prevStateDir === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = prevStateDir;
}
await fs.rm(root, { recursive: true, force: true });
}
});
});