mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 09:28:37 +00:00
refactor(cli): dedupe command secret gateway env fixtures
This commit is contained in:
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user