CLI: speed up command secret gateway tests

This commit is contained in:
Peter Steinberger
2026-04-07 01:23:42 +08:00
parent a47c49bbf3
commit 6a052ca4b8
2 changed files with 261 additions and 134 deletions

View File

@@ -6,7 +6,10 @@ import {
TALK_TEST_PROVIDER_API_KEY_PATH,
TALK_TEST_PROVIDER_API_KEY_PATH_SEGMENTS,
} from "../test-utils/talk-test-provider.js";
import { resolveCommandSecretRefsViaGateway } from "./command-secret-gateway.js";
import {
__testing as commandSecretGatewayTesting,
resolveCommandSecretRefsViaGateway,
} from "./command-secret-gateway.js";
const mocks = vi.hoisted(() => ({
callGateway: vi.fn(),
@@ -29,6 +32,7 @@ vi.mock("../utils/message-channel.js", () => ({
beforeEach(() => {
callGateway.mockReset();
commandSecretGatewayTesting.resetDepsForTest();
});
describe("resolveCommandSecretRefsViaGateway", () => {
@@ -172,6 +176,42 @@ describe("resolveCommandSecretRefsViaGateway", () => {
});
it("enforces unresolved checks only for allowed paths when provided", async () => {
const restoreDeps = commandSecretGatewayTesting.setDepsForTest({
analyzeCommandSecretAssignmentsFromSnapshot: () =>
({
assignments: [
{
path: "channels.discord.accounts.ops.token",
pathSegments: ["channels", "discord", "accounts", "ops", "token"],
value: "ops-token",
},
],
diagnostics: [],
inactive: [],
unresolved: [],
}) as never,
collectConfigAssignments: ({ context }) => {
context.assignments.push(
{ path: "channels.discord.accounts.ops.token" } as never,
{ path: "channels.discord.accounts.chat.token" } as never,
);
},
discoverConfigSecretTargetsByIds: () =>
[
{
entry: { expectedResolvedValue: "string" },
path: "channels.discord.accounts.ops.token",
pathSegments: ["channels", "discord", "accounts", "ops", "token"],
value: { source: "env", provider: "default", id: "DISCORD_OPS_TOKEN" },
},
{
entry: { expectedResolvedValue: "string" },
path: "channels.discord.accounts.chat.token",
pathSegments: ["channels", "discord", "accounts", "chat", "token"],
value: { source: "env", provider: "default", id: "DISCORD_CHAT_TOKEN" },
},
] as never,
});
callGateway.mockResolvedValueOnce({
assignments: [
{
@@ -183,31 +223,35 @@ describe("resolveCommandSecretRefsViaGateway", () => {
diagnostics: [],
});
const result = await resolveCommandSecretRefsViaGateway({
config: {
channels: {
discord: {
accounts: {
ops: {
token: { source: "env", provider: "default", id: "DISCORD_OPS_TOKEN" },
},
chat: {
token: { source: "env", provider: "default", id: "DISCORD_CHAT_TOKEN" },
try {
const result = await resolveCommandSecretRefsViaGateway({
config: {
channels: {
discord: {
accounts: {
ops: {
token: { source: "env", provider: "default", id: "DISCORD_OPS_TOKEN" },
},
chat: {
token: { source: "env", provider: "default", id: "DISCORD_CHAT_TOKEN" },
},
},
},
},
},
} as OpenClawConfig,
commandName: "message",
targetIds: new Set(["channels.discord.accounts.*.token"]),
allowedPaths: new Set(["channels.discord.accounts.ops.token"]),
});
} as OpenClawConfig,
commandName: "message",
targetIds: new Set(["channels.discord.accounts.*.token"]),
allowedPaths: new Set(["channels.discord.accounts.ops.token"]),
});
expect(result.resolvedConfig.channels?.discord?.accounts?.ops?.token).toBe("ops-token");
expect(result.targetStatesByPath).toEqual({
"channels.discord.accounts.ops.token": "resolved_gateway",
});
expect(result.hadUnresolvedTargets).toBe(false);
expect(result.resolvedConfig.channels?.discord?.accounts?.ops?.token).toBe("ops-token");
expect(result.targetStatesByPath).toEqual({
"channels.discord.accounts.ops.token": "resolved_gateway",
});
expect(result.hadUnresolvedTargets).toBe(false);
} finally {
restoreDeps();
}
});
it("fails fast when gateway-backed resolution is unavailable", async () => {
@@ -275,128 +319,173 @@ describe("resolveCommandSecretRefsViaGateway", () => {
});
it("falls back to local resolution for web search SecretRefs when gateway is unavailable", async () => {
const restoreDeps = commandSecretGatewayTesting.setDepsForTest({
collectConfigAssignments: ({ context }) => {
context.assignments.push({
path: "plugins.entries.google.config.webSearch.apiKey",
} as never);
},
resolveManifestContractOwnerPluginId: (params) =>
params.contract === "webSearchProviders" && params.value === "gemini"
? "google"
: undefined,
});
const envKey = "WEB_SEARCH_GEMINI_API_KEY_LOCAL_FALLBACK";
await withEnvValue(envKey, "gemini-local-fallback-key", async () => {
try {
callGateway.mockRejectedValueOnce(new Error("gateway closed"));
const result = await resolveCommandSecretRefsViaGateway({
config: {
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: envKey },
},
},
},
},
},
tools: {
web: {
search: {
provider: "gemini",
},
},
},
} as unknown as OpenClawConfig,
commandName: "agent",
targetIds: new Set(["plugins.entries.google.config.webSearch.apiKey"]),
});
const googleWebSearchConfig = result.resolvedConfig.plugins?.entries?.google?.config as
| { webSearch?: { apiKey?: unknown } }
| undefined;
expect(googleWebSearchConfig?.webSearch?.apiKey).toBe("gemini-local-fallback-key");
expect(result.targetStatesByPath["plugins.entries.google.config.webSearch.apiKey"]).toBe(
"resolved_local",
);
expectGatewayUnavailableLocalFallbackDiagnostics(result);
} finally {
restoreDeps();
}
});
}, 300_000);
it("falls back to local resolution for web fetch provider SecretRefs when gateway is unavailable", async () => {
const restoreDeps = commandSecretGatewayTesting.setDepsForTest({
collectConfigAssignments: ({ context }) => {
context.assignments.push({
path: "plugins.entries.firecrawl.config.webFetch.apiKey",
} as never);
},
resolveManifestContractOwnerPluginId: (params) =>
params.contract === "webFetchProviders" && params.value === "firecrawl"
? "firecrawl"
: undefined,
});
const envKey = "WEB_FETCH_FIRECRAWL_API_KEY_LOCAL_FALLBACK";
await withEnvValue(envKey, "firecrawl-local-fallback-key", async () => {
try {
callGateway.mockRejectedValueOnce(new Error("gateway closed"));
const result = await resolveCommandSecretRefsViaGateway({
config: {
plugins: {
entries: {
firecrawl: {
config: {
webFetch: {
apiKey: { source: "env", provider: "default", id: envKey },
},
},
},
},
},
tools: {
web: {
fetch: {
provider: "firecrawl",
},
},
},
} as unknown as OpenClawConfig,
commandName: "agent",
targetIds: new Set(["plugins.entries.firecrawl.config.webFetch.apiKey"]),
});
const firecrawlConfig = result.resolvedConfig.plugins?.entries?.firecrawl?.config as
| { webFetch?: { apiKey?: unknown } }
| undefined;
expect(firecrawlConfig?.webFetch?.apiKey).toBe("firecrawl-local-fallback-key");
expect(result.targetStatesByPath["plugins.entries.firecrawl.config.webFetch.apiKey"]).toBe(
"resolved_local",
);
expectGatewayUnavailableLocalFallbackDiagnostics(result);
} finally {
restoreDeps();
}
});
});
it("marks web SecretRefs inactive when the web surface is disabled during local fallback", async () => {
const restoreDeps = commandSecretGatewayTesting.setDepsForTest({
collectConfigAssignments: ({ context }) => {
context.assignments.push({
path: "plugins.entries.google.config.webSearch.apiKey",
} as never);
},
resolveManifestContractOwnerPluginId: (params) =>
params.contract === "webSearchProviders" && params.value === "gemini"
? "google"
: undefined,
});
try {
callGateway.mockRejectedValueOnce(new Error("gateway closed"));
const result = await resolveCommandSecretRefsViaGateway({
config: {
tools: {
web: {
search: {
enabled: false,
provider: "gemini",
},
},
},
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: envKey },
apiKey: {
source: "env",
provider: "default",
id: "WEB_SEARCH_DISABLED_KEY",
},
},
},
},
},
},
tools: {
web: {
search: {
provider: "gemini",
},
},
},
} as unknown as OpenClawConfig,
} as OpenClawConfig,
commandName: "agent",
targetIds: new Set(["plugins.entries.google.config.webSearch.apiKey"]),
});
const googleWebSearchConfig = result.resolvedConfig.plugins?.entries?.google?.config as
| { webSearch?: { apiKey?: unknown } }
| undefined;
expect(googleWebSearchConfig?.webSearch?.apiKey).toBe("gemini-local-fallback-key");
expect(result.hadUnresolvedTargets).toBe(false);
expect(result.targetStatesByPath["plugins.entries.google.config.webSearch.apiKey"]).toBe(
"resolved_local",
"inactive_surface",
);
expectGatewayUnavailableLocalFallbackDiagnostics(result);
});
}, 300_000);
it("falls back to local resolution for web fetch provider SecretRefs when gateway is unavailable", async () => {
const envKey = "WEB_FETCH_FIRECRAWL_API_KEY_LOCAL_FALLBACK";
await withEnvValue(envKey, "firecrawl-local-fallback-key", async () => {
callGateway.mockRejectedValueOnce(new Error("gateway closed"));
const result = await resolveCommandSecretRefsViaGateway({
config: {
plugins: {
entries: {
firecrawl: {
config: {
webFetch: {
apiKey: { source: "env", provider: "default", id: envKey },
},
},
},
},
},
tools: {
web: {
fetch: {
provider: "firecrawl",
},
},
},
} as unknown as OpenClawConfig,
commandName: "agent",
targetIds: new Set(["plugins.entries.firecrawl.config.webFetch.apiKey"]),
});
const firecrawlConfig = result.resolvedConfig.plugins?.entries?.firecrawl?.config as
| { webFetch?: { apiKey?: unknown } }
| undefined;
expect(firecrawlConfig?.webFetch?.apiKey).toBe("firecrawl-local-fallback-key");
expect(result.targetStatesByPath["plugins.entries.firecrawl.config.webFetch.apiKey"]).toBe(
"resolved_local",
);
expectGatewayUnavailableLocalFallbackDiagnostics(result);
});
});
it("marks web SecretRefs inactive when the web surface is disabled during local fallback", async () => {
callGateway.mockRejectedValueOnce(new Error("gateway closed"));
const result = await resolveCommandSecretRefsViaGateway({
config: {
tools: {
web: {
search: {
enabled: false,
provider: "gemini",
},
},
},
plugins: {
entries: {
google: {
config: {
webSearch: {
apiKey: {
source: "env",
provider: "default",
id: "WEB_SEARCH_DISABLED_KEY",
},
},
},
},
},
},
} as OpenClawConfig,
commandName: "agent",
targetIds: new Set(["plugins.entries.google.config.webSearch.apiKey"]),
});
expect(result.hadUnresolvedTargets).toBe(false);
expect(result.targetStatesByPath["plugins.entries.google.config.webSearch.apiKey"]).toBe(
"inactive_surface",
);
expect(
result.diagnostics.some((entry) =>
entry.includes(
"plugins.entries.google.config.webSearch.apiKey: tools.web.search is disabled.",
expect(
result.diagnostics.some((entry) =>
entry.includes(
"plugins.entries.google.config.webSearch.apiKey: tools.web.search is disabled.",
),
),
),
).toBe(true);
).toBe(true);
} finally {
restoreDeps();
}
});
it("returns a version-skew hint when gateway does not support secrets.resolve", async () => {

View File

@@ -58,6 +58,41 @@ type GatewaySecretsResolveResult = {
const WEB_RUNTIME_SECRET_TARGET_ID_PREFIXES = ["tools.web.search", "plugins.entries."] as const;
const WEB_RUNTIME_SECRET_PATH_PREFIXES = ["tools.web.search.", "plugins.entries."] as const;
type CommandSecretGatewayDeps = {
analyzeCommandSecretAssignmentsFromSnapshot: typeof analyzeCommandSecretAssignmentsFromSnapshot;
collectConfigAssignments: typeof collectConfigAssignments;
discoverConfigSecretTargetsByIds: typeof discoverConfigSecretTargetsByIds;
resolveManifestContractOwnerPluginId: typeof resolveManifestContractOwnerPluginId;
resolveRuntimeWebTools: typeof resolveRuntimeWebTools;
};
const commandSecretGatewayDeps: CommandSecretGatewayDeps = {
analyzeCommandSecretAssignmentsFromSnapshot,
collectConfigAssignments,
discoverConfigSecretTargetsByIds,
resolveManifestContractOwnerPluginId,
resolveRuntimeWebTools,
};
export const __testing = {
setDepsForTest(overrides: Partial<CommandSecretGatewayDeps>): () => void {
const previous = { ...commandSecretGatewayDeps };
Object.assign(commandSecretGatewayDeps, overrides);
return () => {
Object.assign(commandSecretGatewayDeps, previous);
};
},
resetDepsForTest(): void {
Object.assign(commandSecretGatewayDeps, {
analyzeCommandSecretAssignmentsFromSnapshot,
collectConfigAssignments,
discoverConfigSecretTargetsByIds,
resolveManifestContractOwnerPluginId,
resolveRuntimeWebTools,
});
},
};
function pluginIdFromRuntimeWebPath(path: string): string | undefined {
const match = /^plugins\.entries\.([^.]+)\.config\.(webSearch|webFetch)\.apiKey$/.exec(path);
return match?.[1];
@@ -117,7 +152,7 @@ function classifyRuntimeWebTargetPathState(params: {
if (!configuredProvider) {
return "active";
}
return resolveManifestContractOwnerPluginId({
return commandSecretGatewayDeps.resolveManifestContractOwnerPluginId({
contract: "webFetchProviders",
value: configuredProvider,
origin: "bundled",
@@ -135,7 +170,7 @@ function classifyRuntimeWebTargetPathState(params: {
if (!configuredProvider) {
return "active";
}
return resolveManifestContractOwnerPluginId({
return commandSecretGatewayDeps.resolveManifestContractOwnerPluginId({
contract: "webSearchProviders",
value: configuredProvider,
origin: "bundled",
@@ -195,7 +230,7 @@ function describeInactiveRuntimeWebTargetPath(params: {
const configuredProvider =
typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : "";
const configuredPluginId = configuredProvider
? resolveManifestContractOwnerPluginId({
? commandSecretGatewayDeps.resolveManifestContractOwnerPluginId({
contract: "webSearchProviders",
value: configuredProvider,
origin: "bundled",
@@ -254,7 +289,10 @@ function collectConfiguredTargetRefPaths(params: {
}): Set<string> {
const defaults = params.config.secrets?.defaults;
const configuredTargetRefPaths = new Set<string>();
for (const target of discoverConfigSecretTargetsByIds(params.config, params.targetIds)) {
for (const target of commandSecretGatewayDeps.discoverConfigSecretTargetsByIds(
params.config,
params.targetIds,
)) {
if (params.allowedPaths && !params.allowedPaths.has(target.path)) {
continue;
}
@@ -289,7 +327,7 @@ function classifyConfiguredTargetRefs(params: {
sourceConfig: params.config,
env: process.env,
});
collectConfigAssignments({
commandSecretGatewayDeps.collectConfigAssignments({
config: structuredClone(params.config),
context,
});
@@ -394,13 +432,13 @@ async function resolveCommandSecretRefsLocally(params: {
env: process.env,
});
const localResolutionDiagnostics: string[] = [];
const discoveredTargets = discoverConfigSecretTargetsByIds(sourceConfig, params.targetIds).filter(
(target) => !params.allowedPaths || params.allowedPaths.has(target.path),
);
const discoveredTargets = commandSecretGatewayDeps
.discoverConfigSecretTargetsByIds(sourceConfig, params.targetIds)
.filter((target) => !params.allowedPaths || params.allowedPaths.has(target.path));
const runtimeWebTargets = discoveredTargets.filter((target) =>
targetsRuntimeWebPath(target.path),
);
collectConfigAssignments({
commandSecretGatewayDeps.collectConfigAssignments({
config: structuredClone(params.config),
context,
});
@@ -412,7 +450,7 @@ async function resolveCommandSecretRefsLocally(params: {
!runtimeWebTargets.every((target) => isDirectRuntimeWebTargetPath(target.path))
) {
try {
await resolveRuntimeWebTools({
await commandSecretGatewayDeps.resolveRuntimeWebTools({
sourceConfig,
resolvedConfig,
context,
@@ -474,7 +512,7 @@ async function resolveCommandSecretRefsLocally(params: {
localResolutionDiagnostics,
});
}
const analyzed = analyzeCommandSecretAssignmentsFromSnapshot({
const analyzed = commandSecretGatewayDeps.analyzeCommandSecretAssignmentsFromSnapshot({
sourceConfig,
resolvedConfig,
targetIds: params.targetIds,
@@ -728,7 +766,7 @@ export async function resolveCommandSecretRefsViaGateway(params: {
parsed.inactiveRefPaths.length > 0
? new Set(parsed.inactiveRefPaths)
: collectInactiveSurfacePathsFromDiagnostics(parsed.diagnostics);
const analyzed = analyzeCommandSecretAssignmentsFromSnapshot({
const analyzed = commandSecretGatewayDeps.analyzeCommandSecretAssignmentsFromSnapshot({
sourceConfig: params.config,
resolvedConfig,
targetIds: params.targetIds,