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

View File

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