Files
openclaw/src/secrets/runtime.coverage.test.ts
2026-04-07 06:48:55 +01:00

565 lines
18 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { beforeAll, describe, expect, it } from "vitest";
import type { AuthProfileStore } from "../agents/auth-profiles.js";
import type { OpenClawConfig } from "../config/config.js";
import type { PluginOrigin } from "../plugins/types.js";
import { getPath, setPathCreateStrict } from "./path-utils.js";
import { canonicalizeSecretTargetCoverageId } from "./target-registry-test-helpers.js";
type SecretRegistryEntry = {
id: string;
configFile: "openclaw.json" | "auth-profiles.json";
pathPattern: string;
refPathPattern?: string;
secretShape: "secret_input" | "sibling_ref";
expectedResolvedValue: "string";
authProfileType?: "api_key" | "token";
};
type SecretRefCredentialMatrix = {
entries: Array<{
id: string;
configFile: "openclaw.json" | "auth-profiles.json";
path: string;
refPath?: string;
secretShape: SecretRegistryEntry["secretShape"];
when?: {
type?: SecretRegistryEntry["authProfileType"];
};
}>;
};
function loadCoverageRegistryEntries(): SecretRegistryEntry[] {
const matrixPath = path.join(
process.cwd(),
"docs",
"reference",
"secretref-user-supplied-credentials-matrix.json",
);
const matrix = JSON.parse(fs.readFileSync(matrixPath, "utf8")) as SecretRefCredentialMatrix;
return matrix.entries.map((entry) => ({
id: entry.id,
configFile: entry.configFile,
pathPattern: entry.path,
...(entry.refPath ? { refPathPattern: entry.refPath } : {}),
secretShape: entry.secretShape,
expectedResolvedValue: "string",
...(entry.when?.type ? { authProfileType: entry.when.type } : {}),
}));
}
const COVERAGE_REGISTRY_ENTRIES = loadCoverageRegistryEntries();
const DEBUG_COVERAGE_BATCHES = process.env.OPENCLAW_DEBUG_RUNTIME_COVERAGE === "1";
const COVERAGE_LOADABLE_PLUGIN_ORIGINS =
buildCoverageLoadablePluginOrigins(COVERAGE_REGISTRY_ENTRIES);
let applyResolvedAssignments: typeof import("./runtime-shared.js").applyResolvedAssignments;
let collectAuthStoreAssignments: typeof import("./runtime-auth-collectors.js").collectAuthStoreAssignments;
let collectConfigAssignments: typeof import("./runtime-config-collectors.js").collectConfigAssignments;
let createResolverContext: typeof import("./runtime-shared.js").createResolverContext;
let resolveSecretRefValues: typeof import("./resolve.js").resolveSecretRefValues;
let resolveRuntimeWebTools: typeof import("./runtime-web-tools.js").resolveRuntimeWebTools;
async function ensureConfigCoverageRuntimeLoaded(): Promise<void> {
if (!collectConfigAssignments) {
({ collectConfigAssignments } = await import("./runtime-config-collectors.js"));
}
}
async function ensureAuthCoverageRuntimeLoaded(): Promise<void> {
if (!collectAuthStoreAssignments) {
({ collectAuthStoreAssignments } = await import("./runtime-auth-collectors.js"));
}
}
async function ensureRuntimeWebToolsLoaded(): Promise<void> {
if (!resolveRuntimeWebTools) {
({ resolveRuntimeWebTools } = await import("./runtime-web-tools.js"));
}
}
function toConcretePathSegments(pathPattern: string, wildcardToken = "sample"): string[] {
const segments = pathPattern.split(".").filter(Boolean);
const out: string[] = [];
for (const segment of segments) {
if (segment === "*") {
out.push(wildcardToken);
continue;
}
if (segment.endsWith("[]")) {
out.push(segment.slice(0, -2), "0");
continue;
}
out.push(segment);
}
return out;
}
function resolveCoverageEnvId(entry: SecretRegistryEntry, fallbackEnvId: string): string {
return entry.id === "plugins.entries.firecrawl.config.webFetch.apiKey" ||
entry.id === "tools.web.fetch.firecrawl.apiKey"
? "FIRECRAWL_API_KEY"
: fallbackEnvId;
}
function resolveCoverageResolvedPath(entry: SecretRegistryEntry): string {
return canonicalizeSecretTargetCoverageId(entry.id);
}
function resolveCoverageWildcardToken(index: number): string {
return `sample-${index}`;
}
function resolveCoverageResolvedSegments(
entry: SecretRegistryEntry,
wildcardToken: string,
): string[] {
return toConcretePathSegments(resolveCoverageResolvedPath(entry), wildcardToken);
}
function buildCoverageLoadablePluginOrigins(
entries: readonly SecretRegistryEntry[],
): ReadonlyMap<string, PluginOrigin> {
const origins = new Map<string, PluginOrigin>();
for (const entry of entries) {
const [scope, entriesKey, pluginId] = entry.id.split(".");
if (scope === "plugins" && entriesKey === "entries" && pluginId) {
origins.set(pluginId, "bundled");
}
}
return origins;
}
function resolveCoverageBatchKey(entry: SecretRegistryEntry): string {
if (entry.id.startsWith("agents.defaults.")) {
return entry.id;
}
if (entry.id.startsWith("agents.list[].")) {
return entry.id;
}
if (entry.id.startsWith("gateway.auth.")) {
return entry.id;
}
if (entry.id.startsWith("gateway.remote.")) {
return entry.id;
}
if (entry.id.startsWith("models.providers.*.request.auth.")) {
return entry.id;
}
if (entry.id.startsWith("channels.")) {
const segments = entry.id.split(".");
const channelId = segments[1] ?? "unknown";
const field = segments.at(-1);
if (
field === "accessToken" ||
field === "password" ||
(channelId === "slack" &&
(field === "appToken" ||
field === "botToken" ||
field === "signingSecret" ||
field === "userToken"))
) {
return entry.id;
}
const scope = segments[2] === "accounts" ? "accounts" : "root";
return `channels.${channelId}.${scope}`;
}
if (entry.id.startsWith("messages.tts.providers.")) {
return "messages.tts.providers";
}
if (entry.id.startsWith("models.providers.")) {
return "models.providers";
}
if (entry.id.startsWith("plugins.entries.")) {
return entry.id;
}
if (entry.id.startsWith("skills.entries.")) {
return "skills.entries";
}
if (entry.id.startsWith("talk.providers.")) {
return "talk.providers";
}
if (entry.id.startsWith("talk.")) {
return "talk";
}
return entry.id;
}
function buildCoverageBatches(entries: readonly SecretRegistryEntry[]): SecretRegistryEntry[][] {
const batches = new Map<string, SecretRegistryEntry[]>();
for (const entry of entries) {
const batchKey = resolveCoverageBatchKey(entry);
const batch = batches.get(batchKey);
if (batch) {
batch.push(entry);
continue;
}
batches.set(batchKey, [entry]);
}
return [...batches.values()];
}
function logCoverageBatch(label: string, batch: readonly SecretRegistryEntry[]): void {
if (!DEBUG_COVERAGE_BATCHES || batch.length === 0) {
return;
}
process.stderr.write(
`[runtime.coverage] ${label} batch (${batch.length}): ${batch.map((entry) => entry.id).join(", ")}\n`,
);
}
function batchNeedsRuntimeWebTools(batch: readonly SecretRegistryEntry[]): boolean {
return batch.some(
(entry) =>
entry.id.startsWith("tools.web.") ||
(entry.id.startsWith("plugins.entries.") &&
(entry.id.includes(".config.webSearch.") || entry.id.includes(".config.webFetch."))),
);
}
function applyConfigForOpenClawTarget(
config: OpenClawConfig,
entry: SecretRegistryEntry,
envId: string,
wildcardToken: string,
): void {
const resolvedEnvId = resolveCoverageEnvId(entry, envId);
const refTargetPath =
entry.secretShape === "sibling_ref" && entry.refPathPattern // pragma: allowlist secret
? entry.refPathPattern
: entry.pathPattern;
setPathCreateStrict(config, toConcretePathSegments(refTargetPath, wildcardToken), {
source: "env",
provider: "default",
id: resolvedEnvId,
});
if (entry.id.startsWith("models.providers.")) {
setPathCreateStrict(
config,
["models", "providers", wildcardToken, "baseUrl"],
"https://api.example/v1",
);
setPathCreateStrict(config, ["models", "providers", wildcardToken, "models"], []);
}
if (entry.id.startsWith("plugins.entries.")) {
const pluginId = entry.id.split(".")[2];
if (pluginId) {
setPathCreateStrict(config, ["plugins", "entries", pluginId, "enabled"], true);
}
}
if (entry.id === "agents.defaults.memorySearch.remote.apiKey") {
setPathCreateStrict(config, ["agents", "list", "0", "id"], "sample-agent");
}
if (entry.id === "gateway.auth.password") {
setPathCreateStrict(config, ["gateway", "auth", "mode"], "password");
}
if (entry.id === "gateway.remote.token" || entry.id === "gateway.remote.password") {
setPathCreateStrict(config, ["gateway", "mode"], "remote");
setPathCreateStrict(config, ["gateway", "remote", "url"], "wss://gateway.example");
}
if (entry.id === "channels.telegram.webhookSecret") {
setPathCreateStrict(config, ["channels", "telegram", "webhookUrl"], "https://example.com/hook");
}
if (entry.id === "channels.telegram.accounts.*.webhookSecret") {
setPathCreateStrict(
config,
["channels", "telegram", "accounts", wildcardToken, "webhookUrl"],
"https://example.com/hook",
);
}
if (entry.id === "channels.slack.signingSecret") {
setPathCreateStrict(config, ["channels", "slack", "mode"], "http");
}
if (entry.id === "channels.slack.accounts.*.signingSecret") {
setPathCreateStrict(config, ["channels", "slack", "accounts", wildcardToken, "mode"], "http");
}
if (entry.id === "channels.zalo.webhookSecret") {
setPathCreateStrict(config, ["channels", "zalo", "webhookUrl"], "https://example.com/hook");
}
if (entry.id === "channels.zalo.accounts.*.webhookSecret") {
setPathCreateStrict(
config,
["channels", "zalo", "accounts", wildcardToken, "webhookUrl"],
"https://example.com/hook",
);
}
if (entry.id === "channels.feishu.verificationToken") {
setPathCreateStrict(config, ["channels", "feishu", "connectionMode"], "webhook");
}
if (entry.id === "channels.feishu.encryptKey") {
setPathCreateStrict(config, ["channels", "feishu", "connectionMode"], "webhook");
}
if (entry.id === "channels.feishu.accounts.*.verificationToken") {
setPathCreateStrict(
config,
["channels", "feishu", "accounts", wildcardToken, "connectionMode"],
"webhook",
);
}
if (entry.id === "channels.feishu.accounts.*.encryptKey") {
setPathCreateStrict(
config,
["channels", "feishu", "accounts", wildcardToken, "connectionMode"],
"webhook",
);
}
if (entry.id === "plugins.entries.brave.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "brave");
}
if (entry.id === "plugins.entries.google.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "gemini");
}
if (entry.id === "plugins.entries.xai.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "grok");
}
if (entry.id === "plugins.entries.moonshot.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "kimi");
}
if (entry.id === "plugins.entries.perplexity.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "perplexity");
}
if (entry.id === "plugins.entries.firecrawl.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "firecrawl");
}
if (entry.id === "plugins.entries.minimax.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "minimax");
}
if (entry.id === "plugins.entries.tavily.config.webSearch.apiKey") {
setPathCreateStrict(config, ["tools", "web", "search", "provider"], "tavily");
}
if (entry.id === "models.providers.*.request.auth.token") {
setPathCreateStrict(
config,
["models", "providers", wildcardToken, "request", "auth", "mode"],
"authorization-bearer",
);
}
if (entry.id === "models.providers.*.request.auth.value") {
setPathCreateStrict(
config,
["models", "providers", wildcardToken, "request", "auth", "mode"],
"header",
);
setPathCreateStrict(
config,
["models", "providers", wildcardToken, "request", "auth", "headerName"],
"x-api-key",
);
}
if (entry.id.startsWith("models.providers.*.request.proxy.tls.")) {
setPathCreateStrict(
config,
["models", "providers", wildcardToken, "request", "proxy", "mode"],
"explicit-proxy",
);
setPathCreateStrict(
config,
["models", "providers", wildcardToken, "request", "proxy", "url"],
"http://proxy.example:8080",
);
}
}
function applyAuthStoreTarget(
store: AuthProfileStore,
entry: SecretRegistryEntry,
envId: string,
wildcardToken: string,
): void {
if (entry.authProfileType === "token") {
setPathCreateStrict(store, ["profiles", wildcardToken], {
type: "token" as const,
provider: "sample-provider",
token: "legacy-token",
tokenRef: {
source: "env" as const,
provider: "default",
id: envId,
},
});
return;
}
setPathCreateStrict(store, ["profiles", wildcardToken], {
type: "api_key" as const,
provider: "sample-provider",
key: "legacy-key",
keyRef: {
source: "env" as const,
provider: "default",
id: envId,
},
});
}
async function prepareConfigCoverageSnapshot(params: {
config: OpenClawConfig;
env: NodeJS.ProcessEnv;
loadablePluginOrigins?: ReadonlyMap<string, PluginOrigin>;
includeRuntimeWebTools?: boolean;
}) {
await ensureConfigCoverageRuntimeLoaded();
const sourceConfig = structuredClone(params.config);
const resolvedConfig = structuredClone(params.config);
const context = createResolverContext({
sourceConfig,
env: params.env,
});
collectConfigAssignments({
config: resolvedConfig,
context,
loadablePluginOrigins: params.loadablePluginOrigins,
});
if (context.assignments.length > 0) {
const resolved = await resolveSecretRefValues(
context.assignments.map((assignment) => assignment.ref),
{
config: sourceConfig,
env: context.env,
cache: context.cache,
},
);
applyResolvedAssignments({
assignments: context.assignments,
resolved,
});
}
if (params.includeRuntimeWebTools) {
await ensureRuntimeWebToolsLoaded();
await resolveRuntimeWebTools({
sourceConfig,
resolvedConfig,
context,
});
}
return {
config: resolvedConfig,
warnings: context.warnings,
};
}
async function prepareAuthCoverageSnapshot(params: {
config: OpenClawConfig;
env: NodeJS.ProcessEnv;
agentDirs: string[];
loadAuthStore: (agentDir?: string) => AuthProfileStore;
}) {
await ensureAuthCoverageRuntimeLoaded();
const sourceConfig = structuredClone(params.config);
const context = createResolverContext({
sourceConfig,
env: params.env,
});
const authStores = params.agentDirs.map((agentDir) => {
const store = structuredClone(params.loadAuthStore(agentDir));
collectAuthStoreAssignments({
store,
context,
agentDir,
});
return { agentDir, store };
});
if (context.assignments.length > 0) {
const resolved = await resolveSecretRefValues(
context.assignments.map((assignment) => assignment.ref),
{
config: sourceConfig,
env: context.env,
cache: context.cache,
},
);
applyResolvedAssignments({
assignments: context.assignments,
resolved,
});
}
return {
authStores,
warnings: context.warnings,
};
}
describe("secrets runtime target coverage", () => {
beforeAll(async () => {
const [sharedRuntime, resolver] = await Promise.all([
import("./runtime-shared.js"),
import("./resolve.js"),
]);
({ applyResolvedAssignments, createResolverContext } = sharedRuntime);
({ resolveSecretRefValues } = resolver);
});
it("handles every openclaw.json registry target when configured as active", async () => {
const entries = COVERAGE_REGISTRY_ENTRIES.filter(
(entry) => entry.configFile === "openclaw.json",
);
for (const batch of buildCoverageBatches(entries)) {
logCoverageBatch("openclaw.json", batch);
const config = {} as OpenClawConfig;
const env: Record<string, string> = {};
for (const [index, entry] of batch.entries()) {
const envId = `OPENCLAW_SECRET_TARGET_${entry.id}`;
const runtimeEnvId = resolveCoverageEnvId(entry, envId);
const expectedValue = `resolved-${entry.id}`;
const wildcardToken = resolveCoverageWildcardToken(index);
env[runtimeEnvId] = expectedValue;
applyConfigForOpenClawTarget(config, entry, envId, wildcardToken);
}
const snapshot = await prepareConfigCoverageSnapshot({
config,
env,
loadablePluginOrigins: COVERAGE_LOADABLE_PLUGIN_ORIGINS,
includeRuntimeWebTools: batchNeedsRuntimeWebTools(batch),
});
for (const [index, entry] of batch.entries()) {
const resolved = getPath(
snapshot.config,
resolveCoverageResolvedSegments(entry, resolveCoverageWildcardToken(index)),
);
expect(resolved).toBe(`resolved-${entry.id}`);
}
}
});
it("handles every auth-profiles registry target", async () => {
const entries = COVERAGE_REGISTRY_ENTRIES.filter(
(entry) => entry.configFile === "auth-profiles.json",
);
for (const batch of buildCoverageBatches(entries)) {
logCoverageBatch("auth-profiles.json", batch);
const env: Record<string, string> = {};
const authStore: AuthProfileStore = {
version: 1,
profiles: {},
};
for (const [index, entry] of batch.entries()) {
const envId = `OPENCLAW_AUTH_SECRET_TARGET_${entry.id}`;
env[envId] = `resolved-${entry.id}`;
applyAuthStoreTarget(authStore, entry, envId, resolveCoverageWildcardToken(index));
}
const snapshot = await prepareAuthCoverageSnapshot({
config: {} as OpenClawConfig,
env,
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => authStore,
});
const resolvedStore = snapshot.authStores[0]?.store;
expect(resolvedStore).toBeDefined();
for (const [index, entry] of batch.entries()) {
const resolved = getPath(
resolvedStore,
toConcretePathSegments(entry.pathPattern, resolveCoverageWildcardToken(index)),
);
expect(resolved).toBe(`resolved-${entry.id}`);
}
}
});
});