refactor(cli): dedupe command secret gateway env fixtures

This commit is contained in:
Peter Steinberger 2026-03-07 17:15:57 +00:00
parent c1a8f8150e
commit 4e8fcc1d3d
1 changed files with 76 additions and 108 deletions

View File

@ -10,6 +10,60 @@ vi.mock("../gateway/call.js", () => ({
const { resolveCommandSecretRefsViaGateway } = await import("./command-secret-gateway.js");
describe("resolveCommandSecretRefsViaGateway", () => {
function makeTalkApiKeySecretRefConfig(envKey: string): OpenClawConfig {
return {
talk: {
apiKey: { source: "env", provider: "default", id: envKey },
},
} as OpenClawConfig;
}
async function withEnvValue(
envKey: string,
value: string | undefined,
fn: () => Promise<void>,
): Promise<void> {
const priorValue = process.env[envKey];
if (value === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = value;
}
try {
await fn();
} finally {
if (priorValue === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = priorValue;
}
}
}
async function resolveTalkApiKey(params: {
envKey: string;
commandName?: string;
mode?: "strict" | "summary";
}) {
return resolveCommandSecretRefsViaGateway({
config: makeTalkApiKeySecretRefConfig(params.envKey),
commandName: params.commandName ?? "memory status",
targetIds: new Set(["talk.apiKey"]),
mode: params.mode,
});
}
function expectTalkApiKeySecretRef(
result: Awaited<ReturnType<typeof resolveTalkApiKey>>,
envKey: string,
) {
expect(result.resolvedConfig.talk?.apiKey).toEqual({
source: "env",
provider: "default",
id: envKey,
});
}
it("returns config unchanged when no target SecretRefs are configured", async () => {
const config = {
talk: {
@ -153,58 +207,26 @@ describe("resolveCommandSecretRefsViaGateway", () => {
it("returns a version-skew hint when gateway does not support secrets.resolve", async () => {
const envKey = "TALK_API_KEY_UNSUPPORTED";
const priorValue = process.env[envKey];
delete process.env[envKey];
callGateway.mockRejectedValueOnce(new Error("unknown method: secrets.resolve"));
try {
await expect(
resolveCommandSecretRefsViaGateway({
config: {
talk: {
apiKey: { source: "env", provider: "default", id: envKey },
},
} as OpenClawConfig,
commandName: "memory status",
targetIds: new Set(["talk.apiKey"]),
}),
).rejects.toThrow(/does not support secrets\.resolve/i);
} finally {
if (priorValue === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = priorValue;
}
}
await withEnvValue(envKey, undefined, async () => {
await expect(resolveTalkApiKey({ envKey })).rejects.toThrow(
/does not support secrets\.resolve/i,
);
});
});
it("returns a version-skew hint when required-method capability check fails", async () => {
const envKey = "TALK_API_KEY_REQUIRED_METHOD";
const priorValue = process.env[envKey];
delete process.env[envKey];
callGateway.mockRejectedValueOnce(
new Error(
'active gateway does not support required method "secrets.resolve" for "secrets.resolve".',
),
);
try {
await expect(
resolveCommandSecretRefsViaGateway({
config: {
talk: {
apiKey: { source: "env", provider: "default", id: envKey },
},
} as OpenClawConfig,
commandName: "memory status",
targetIds: new Set(["talk.apiKey"]),
}),
).rejects.toThrow(/does not support secrets\.resolve/i);
} finally {
if (priorValue === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = priorValue;
}
}
await withEnvValue(envKey, undefined, async () => {
await expect(resolveTalkApiKey({ envKey })).rejects.toThrow(
/does not support secrets\.resolve/i,
);
});
});
it("fails when gateway returns an invalid secrets.resolve payload", async () => {
@ -276,21 +298,9 @@ describe("resolveCommandSecretRefsViaGateway", () => {
],
});
const result = await resolveCommandSecretRefsViaGateway({
config: {
talk: {
apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" },
},
} as OpenClawConfig,
commandName: "memory status",
targetIds: new Set(["talk.apiKey"]),
});
const result = await resolveTalkApiKey({ envKey: "TALK_API_KEY" });
expect(result.resolvedConfig.talk?.apiKey).toEqual({
source: "env",
provider: "default",
id: "TALK_API_KEY",
});
expectTalkApiKeySecretRef(result, "TALK_API_KEY");
expect(result.diagnostics).toEqual([
"talk.apiKey: secret ref is configured on an inactive surface; skipping command-time assignment.",
]);
@ -303,21 +313,9 @@ describe("resolveCommandSecretRefsViaGateway", () => {
inactiveRefPaths: ["talk.apiKey"],
});
const result = await resolveCommandSecretRefsViaGateway({
config: {
talk: {
apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" },
},
} as OpenClawConfig,
commandName: "memory status",
targetIds: new Set(["talk.apiKey"]),
});
const result = await resolveTalkApiKey({ envKey: "TALK_API_KEY" });
expect(result.resolvedConfig.talk?.apiKey).toEqual({
source: "env",
provider: "default",
id: "TALK_API_KEY",
});
expectTalkApiKeySecretRef(result, "TALK_API_KEY");
expect(result.diagnostics).toEqual(["talk api key inactive"]);
});
@ -359,25 +357,16 @@ describe("resolveCommandSecretRefsViaGateway", () => {
it("degrades unresolved refs in summary mode instead of throwing", async () => {
const envKey = "TALK_API_KEY_SUMMARY_MISSING";
const priorValue = process.env[envKey];
delete process.env[envKey];
callGateway.mockResolvedValueOnce({
assignments: [],
diagnostics: [],
});
try {
const result = await resolveCommandSecretRefsViaGateway({
config: {
talk: {
apiKey: { source: "env", provider: "default", id: envKey },
},
} as OpenClawConfig,
await withEnvValue(envKey, undefined, async () => {
const result = await resolveTalkApiKey({
envKey,
commandName: "status",
targetIds: new Set(["talk.apiKey"]),
mode: "summary",
});
expect(result.resolvedConfig.talk?.apiKey).toBeUndefined();
expect(result.hadUnresolvedTargets).toBe(true);
expect(result.targetStatesByPath["talk.apiKey"]).toBe("unresolved");
@ -386,36 +375,21 @@ describe("resolveCommandSecretRefsViaGateway", () => {
entry.includes("talk.apiKey is unavailable in this command path"),
),
).toBe(true);
} finally {
if (priorValue === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = priorValue;
}
}
});
});
it("uses targeted local fallback after an incomplete gateway snapshot", async () => {
const envKey = "TALK_API_KEY_PARTIAL_GATEWAY";
const priorValue = process.env[envKey];
process.env[envKey] = "recovered-locally";
callGateway.mockResolvedValueOnce({
assignments: [],
diagnostics: [],
});
try {
const result = await resolveCommandSecretRefsViaGateway({
config: {
talk: {
apiKey: { source: "env", provider: "default", id: envKey },
},
} as OpenClawConfig,
await withEnvValue(envKey, "recovered-locally", async () => {
const result = await resolveTalkApiKey({
envKey,
commandName: "status",
targetIds: new Set(["talk.apiKey"]),
mode: "summary",
});
expect(result.resolvedConfig.talk?.apiKey).toBe("recovered-locally");
expect(result.hadUnresolvedTargets).toBe(false);
expect(result.targetStatesByPath["talk.apiKey"]).toBe("resolved_local");
@ -426,13 +400,7 @@ describe("resolveCommandSecretRefsViaGateway", () => {
),
),
).toBe(true);
} finally {
if (priorValue === undefined) {
delete process.env[envKey];
} else {
process.env[envKey] = priorValue;
}
}
});
});
it("limits strict local fallback analysis to unresolved gateway paths", async () => {