mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 09:31:27 +00:00
test: clear config cli broad matchers
This commit is contained in:
@@ -143,6 +143,41 @@ async function runValidateJsonAndGetPayload() {
|
||||
};
|
||||
}
|
||||
|
||||
function requireWriteOptions(): { unsetPaths?: string[][]; explicitSetPaths?: string[][] } {
|
||||
const options = mockWriteConfigFile.mock.calls[0]?.[1];
|
||||
if (!options) {
|
||||
throw new Error("expected write options");
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function expectLogIncludes(text: string) {
|
||||
expect(mockLog.mock.calls.some((call) => String(call[0]).includes(text))).toBe(true);
|
||||
}
|
||||
|
||||
function expectLogExcludes(text: string) {
|
||||
expect(mockLog.mock.calls.some((call) => String(call[0]).includes(text))).toBe(false);
|
||||
}
|
||||
|
||||
function expectErrorIncludes(text: string) {
|
||||
expect(mockError.mock.calls.some((call) => String(call[0]).includes(text))).toBe(true);
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new Error(`expected ${label} to be an object`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function requireResolveSecretRefCall(index: number): [unknown, unknown] {
|
||||
const call = mockResolveSecretRefValue.mock.calls[index];
|
||||
if (!call) {
|
||||
throw new Error(`expected SecretRef resolver call ${index}`);
|
||||
}
|
||||
return call as [unknown, unknown];
|
||||
}
|
||||
|
||||
let registerConfigCli: typeof import("./config-cli.js").registerConfigCli;
|
||||
let sharedProgram: Command;
|
||||
|
||||
@@ -247,9 +282,9 @@ describe("config cli", () => {
|
||||
await runConfigCommand(["config", "set", "channels.telegram.dmPolicy", "pairing"]);
|
||||
|
||||
expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
|
||||
expect(mockWriteConfigFile.mock.calls[0]?.[1]).toMatchObject({
|
||||
explicitSetPaths: [["channels", "telegram", "dmPolicy"]],
|
||||
});
|
||||
expect(requireWriteOptions().explicitSetPaths).toEqual([
|
||||
["channels", "telegram", "dmPolicy"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("marks object set paths explicit so nested default-equal writes persist", async () => {
|
||||
@@ -280,9 +315,7 @@ describe("config cli", () => {
|
||||
]);
|
||||
|
||||
expect(mockWriteConfigFile).toHaveBeenCalledTimes(1);
|
||||
expect(mockWriteConfigFile.mock.calls[0]?.[1]).toMatchObject({
|
||||
explicitSetPaths: [["channels", "telegram"]],
|
||||
});
|
||||
expect(requireWriteOptions().explicitSetPaths).toEqual([["channels", "telegram"]]);
|
||||
});
|
||||
|
||||
it("does not inject runtime defaults into the written config", async () => {
|
||||
@@ -404,11 +437,9 @@ describe("config cli", () => {
|
||||
expect(written.agents?.defaults?.models).toEqual({
|
||||
"google/gemini-3.1-pro-preview": { alias: "gemini" },
|
||||
});
|
||||
expect(mockWriteConfigFile.mock.calls[0]?.[1]).toMatchObject({
|
||||
explicitSetPaths: [
|
||||
["agents", "defaults", "models", "google/gemini-3.1-pro-preview", "alias"],
|
||||
],
|
||||
});
|
||||
expect(requireWriteOptions().explicitSetPaths).toEqual([
|
||||
["agents", "defaults", "models", "google/gemini-3.1-pro-preview", "alias"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects plugin install record config updates", async () => {
|
||||
@@ -424,12 +455,8 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("openclaw plugins install <spec>"),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("openclaw plugins update <plugin-id>"),
|
||||
);
|
||||
expectErrorIncludes("openclaw plugins install <spec>");
|
||||
expectErrorIncludes("openclaw plugins update <plugin-id>");
|
||||
});
|
||||
|
||||
it("rejects protected model map replacement unless explicitly requested", async () => {
|
||||
@@ -456,9 +483,7 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Refusing to replace agents.defaults.models"),
|
||||
);
|
||||
expectErrorIncludes("Refusing to replace agents.defaults.models");
|
||||
});
|
||||
|
||||
it("merges protected model map values with --merge", async () => {
|
||||
@@ -546,11 +571,7 @@ describe("config cli", () => {
|
||||
token: "token-keep",
|
||||
allowTailscale: true,
|
||||
});
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Removed inactive gateway.auth.password for gateway.auth.mode=token",
|
||||
),
|
||||
);
|
||||
expectLogIncludes("Removed inactive gateway.auth.password for gateway.auth.mode=token");
|
||||
});
|
||||
|
||||
it("drops gateway.auth.token when switching mode to password", async () => {
|
||||
@@ -573,11 +594,7 @@ describe("config cli", () => {
|
||||
mode: "password",
|
||||
password: "password-keep", // pragma: allowlist secret
|
||||
});
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Removed inactive gateway.auth.token for gateway.auth.mode=password",
|
||||
),
|
||||
);
|
||||
expectLogIncludes("Removed inactive gateway.auth.token for gateway.auth.mode=password");
|
||||
});
|
||||
|
||||
it("applies mode-based credential cleanup using the final batch result", async () => {
|
||||
@@ -605,11 +622,7 @@ describe("config cli", () => {
|
||||
mode: "token",
|
||||
token: "token-keep",
|
||||
});
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Removed inactive gateway.auth.password for gateway.auth.mode=token",
|
||||
),
|
||||
);
|
||||
expectLogIncludes("Removed inactive gateway.auth.password for gateway.auth.mode=token");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -641,7 +654,7 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockExit).not.toHaveBeenCalled();
|
||||
expect(mockError).not.toHaveBeenCalled();
|
||||
expect(mockLog).toHaveBeenCalledWith(expect.stringContaining("Config valid:"));
|
||||
expectLogIncludes("Config valid:");
|
||||
});
|
||||
|
||||
it("prints issues and exits 1 when config is invalid", async () => {
|
||||
@@ -658,10 +671,8 @@ describe("config cli", () => {
|
||||
|
||||
await expect(runConfigCommand(["config", "validate"])).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("config is invalid"));
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("agents.defaults.suppressToolErrorWarnings"),
|
||||
);
|
||||
expectErrorIncludes("config is invalid");
|
||||
expectErrorIncludes("agents.defaults.suppressToolErrorWarnings");
|
||||
expect(mockLog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -723,7 +734,7 @@ describe("config cli", () => {
|
||||
});
|
||||
|
||||
await expect(runConfigCommand(["config", "validate"])).rejects.toThrow("__exit__:1");
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("Config file not found:"));
|
||||
expectErrorIncludes("Config file not found:");
|
||||
expect(mockLog).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -754,24 +765,19 @@ describe("config cli", () => {
|
||||
| { title?: string; description?: string }
|
||||
| undefined;
|
||||
expect(payload.properties?.$schema).toEqual({ type: "string" });
|
||||
expect(gatewayPort).toMatchObject({
|
||||
title: "Gateway Port",
|
||||
description: expect.stringContaining("TCP port used by the gateway listener"),
|
||||
});
|
||||
expect(payload.properties?.channels).toMatchObject({
|
||||
title: "Channels",
|
||||
properties: {},
|
||||
additionalProperties: true,
|
||||
});
|
||||
expect(payload.properties?.plugins).toMatchObject({
|
||||
title: "Plugins",
|
||||
description: expect.stringContaining("Plugin system controls"),
|
||||
properties: {
|
||||
entries: {
|
||||
title: "Plugin Entries",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(gatewayPort?.title).toBe("Gateway Port");
|
||||
expect(gatewayPort?.description).toContain("TCP port used by the gateway listener");
|
||||
const channels = requireRecord(payload.properties?.channels, "schema channels");
|
||||
expect(channels.title).toBe("Channels");
|
||||
expect(channels.properties).toEqual({});
|
||||
expect(channels.additionalProperties).toBe(true);
|
||||
const plugins = requireRecord(payload.properties?.plugins, "schema plugins");
|
||||
expect(plugins.title).toBe("Plugins");
|
||||
expect(plugins.description).toContain("Plugin system controls");
|
||||
const pluginProperties = requireRecord(plugins.properties, "schema plugin properties");
|
||||
expect(requireRecord(pluginProperties.entries, "schema plugin entries").title).toBe(
|
||||
"Plugin Entries",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back cleanly when best-effort schema loading returns channel-only data", async () => {
|
||||
@@ -802,10 +808,9 @@ describe("config cli", () => {
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
expect(payload.properties?.$schema).toEqual({ type: "string" });
|
||||
expect(payload.properties?.channels).toMatchObject({
|
||||
type: "object",
|
||||
properties: { telegram: { type: "object" } },
|
||||
});
|
||||
const channels = requireRecord(payload.properties?.channels, "schema channels");
|
||||
expect(channels.type).toBe("object");
|
||||
expect(channels.properties).toEqual({ telegram: { type: "object" } });
|
||||
expect(payload.properties?.plugins).toBeUndefined();
|
||||
expect(mockError).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -830,12 +835,8 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockReadConfigFileSnapshot).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not parse "{bad" as JSON for --strict-json.'),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("For plain strings, omit --strict-json."),
|
||||
);
|
||||
expectErrorIncludes('Could not parse "{bad" as JSON for --strict-json.');
|
||||
expectErrorIncludes("For plain strings, omit --strict-json.");
|
||||
});
|
||||
|
||||
it("keeps --json as a strict parsing alias", async () => {
|
||||
@@ -954,10 +955,8 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Config policy validation failed: unsupported SecretRef usage"),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("hooks.token"));
|
||||
expectErrorIncludes("Config policy validation failed: unsupported SecretRef usage");
|
||||
expectErrorIncludes("hooks.token");
|
||||
});
|
||||
|
||||
it("fails early when parent-object writes include unsupported SecretRef objects", async () => {
|
||||
@@ -977,10 +976,8 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Config policy validation failed: unsupported SecretRef usage"),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("hooks.token"));
|
||||
expectErrorIncludes("Config policy validation failed: unsupported SecretRef usage");
|
||||
expectErrorIncludes("hooks.token");
|
||||
});
|
||||
|
||||
it("supports provider builder mode under secrets.providers.<alias>", async () => {
|
||||
@@ -1038,16 +1035,13 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledWith(
|
||||
{
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "DISCORD_BOT_TOKEN",
|
||||
},
|
||||
expect.objectContaining({
|
||||
env: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
|
||||
expect(secretRef).toEqual({
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "DISCORD_BOT_TOKEN",
|
||||
});
|
||||
expect(requireRecord(resolveOptions, "resolve options").env).toBeTypeOf("object");
|
||||
});
|
||||
|
||||
it("requires schema validation in JSON dry-run mode", async () => {
|
||||
@@ -1068,9 +1062,7 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: config schema validation failed."),
|
||||
);
|
||||
expectErrorIncludes("Dry run failed: config schema validation failed.");
|
||||
});
|
||||
|
||||
it("fails dry-run when unsupported mutable paths receive SecretRef objects in value/json mode", async () => {
|
||||
@@ -1096,10 +1088,8 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: config schema validation failed."),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("hooks.token"));
|
||||
expectErrorIncludes("Dry run failed: config schema validation failed.");
|
||||
expectErrorIncludes("hooks.token");
|
||||
});
|
||||
|
||||
it("aggregates policy failures across batch entries", async () => {
|
||||
@@ -1119,10 +1109,8 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("hooks.token"));
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("commands.ownerDisplaySecret"),
|
||||
);
|
||||
expectErrorIncludes("hooks.token");
|
||||
expectErrorIncludes("commands.ownerDisplaySecret");
|
||||
});
|
||||
|
||||
it("does not duplicate policy errors in --dry-run --json mode for parent-object writes", async () => {
|
||||
@@ -1170,14 +1158,8 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Dry run note: value mode does not run schema/resolvability checks.",
|
||||
),
|
||||
);
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run successful: 1 update(s) validated"),
|
||||
);
|
||||
expectLogIncludes("Dry run note: value mode does not run schema/resolvability checks.");
|
||||
expectLogIncludes("Dry run successful: 1 update(s) validated");
|
||||
});
|
||||
|
||||
it("supports batch mode for refs/providers in dry-run", async () => {
|
||||
@@ -1233,10 +1215,8 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Dry run note: skipped 1 exec SecretRef resolvability check(s). Re-run with --allow-exec",
|
||||
),
|
||||
expectLogIncludes(
|
||||
"Dry run note: skipped 1 exec SecretRef resolvability check(s). Re-run with --allow-exec",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1271,17 +1251,13 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
source: "exec",
|
||||
provider: "runner",
|
||||
id: "openai",
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(mockLog).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run note: skipped 1 exec SecretRef resolvability check(s)."),
|
||||
);
|
||||
const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
|
||||
const secretRefRecord = requireRecord(secretRef, "exec SecretRef");
|
||||
expect(secretRefRecord.source).toBe("exec");
|
||||
expect(secretRefRecord.provider).toBe("runner");
|
||||
expect(secretRefRecord.id).toBe("openai");
|
||||
expect(resolveOptions).toBeTypeOf("object");
|
||||
expectLogExcludes("Dry run note: skipped 1 exec SecretRef resolvability check(s).");
|
||||
});
|
||||
|
||||
it("rejects --allow-exec without --dry-run", async () => {
|
||||
@@ -1295,9 +1271,7 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("config set mode error: --allow-exec requires --dry-run."),
|
||||
);
|
||||
expectErrorIncludes("config set mode error: --allow-exec requires --dry-run.");
|
||||
});
|
||||
|
||||
it("fails dry-run when skipped exec refs use an unconfigured provider", async () => {
|
||||
@@ -1325,9 +1299,7 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Secret provider "runner" is not configured'),
|
||||
);
|
||||
expectErrorIncludes('Secret provider "runner" is not configured');
|
||||
});
|
||||
|
||||
it("fails dry-run when skipped exec refs use a provider with mismatched source", async () => {
|
||||
@@ -1359,11 +1331,7 @@ describe("config cli", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockResolveSecretRefValue).not.toHaveBeenCalled();
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Secret provider "runner" has source "env" but ref requests "exec".',
|
||||
),
|
||||
);
|
||||
expectErrorIncludes('Secret provider "runner" has source "env" but ref requests "exec".');
|
||||
});
|
||||
|
||||
it("writes sibling SecretRef paths when target uses sibling-ref shape", async () => {
|
||||
@@ -1416,9 +1384,7 @@ describe("config cli", () => {
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("config set mode error: choose exactly one mode"),
|
||||
);
|
||||
expectErrorIncludes("config set mode error: choose exactly one mode");
|
||||
});
|
||||
|
||||
it("rejects mixing batch mode with builder flags", async () => {
|
||||
@@ -1437,10 +1403,8 @@ describe("config cli", () => {
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"config set mode error: batch mode (--batch-json/--batch-file) cannot be combined",
|
||||
),
|
||||
expectErrorIncludes(
|
||||
"config set mode error: batch mode (--batch-json/--batch-file) cannot be combined",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1529,9 +1493,7 @@ describe("config cli", () => {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
}
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("--batch-file must be a JSON array."),
|
||||
);
|
||||
expectErrorIncludes("--batch-file must be a JSON array.");
|
||||
});
|
||||
|
||||
it("patches config from one object in one write", async () => {
|
||||
@@ -1710,10 +1672,9 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledWith(
|
||||
{ source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
|
||||
expect.any(Object),
|
||||
);
|
||||
const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
|
||||
expect(secretRef).toEqual({ source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" });
|
||||
expect(resolveOptions).toBeTypeOf("object");
|
||||
});
|
||||
|
||||
it("schema-validates SecretRef-only config patch operations", async () => {
|
||||
@@ -1751,11 +1712,9 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(1);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: config schema validation failed."),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("gateway"));
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining('"typo"'));
|
||||
expectErrorIncludes("Dry run failed: config schema validation failed.");
|
||||
expectErrorIncludes("gateway");
|
||||
expectErrorIncludes('"typo"');
|
||||
});
|
||||
|
||||
it("dry-runs nested SecretRefs inside config patch replacements", async () => {
|
||||
@@ -1812,18 +1771,14 @@ describe("config cli", () => {
|
||||
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledTimes(2);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: 2 SecretRef assignment(s) could not be resolved."),
|
||||
);
|
||||
expectErrorIncludes("Dry run failed: 2 SecretRef assignment(s) could not be resolved.");
|
||||
});
|
||||
|
||||
it("rejects config patch --json without dry-run", async () => {
|
||||
await expect(runConfigCommand(["config", "patch", "--stdin", "--json"])).rejects.toThrow(
|
||||
"__exit__:1",
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("config patch mode error: --json requires --dry-run."),
|
||||
);
|
||||
expectErrorIncludes("config patch mode error: --json requires --dry-run.");
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1893,9 +1848,7 @@ describe("config cli", () => {
|
||||
.channels as Record<string, unknown>,
|
||||
).toEqual({ maintainers: { enabled: true, requireMention: true } });
|
||||
expect((written.channels as Record<string, unknown>).slack).not.toHaveProperty("appToken");
|
||||
expect(mockWriteConfigFile.mock.calls[0]?.[1]).toMatchObject({
|
||||
unsetPaths: [["channels", "slack", "appToken"]],
|
||||
});
|
||||
expect(requireWriteOptions().unsetPaths).toEqual([["channels", "slack", "appToken"]]);
|
||||
});
|
||||
|
||||
it("rejects unused config patch replace paths", async () => {
|
||||
@@ -1931,10 +1884,8 @@ describe("config cli", () => {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
}
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"config patch mode error: --replace-path channels.discord.guilds did not match any value in the input patch.",
|
||||
),
|
||||
expectErrorIncludes(
|
||||
"config patch mode error: --replace-path channels.discord.guilds did not match any value in the input patch.",
|
||||
);
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -1949,9 +1900,7 @@ describe("config cli", () => {
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("must include exactly one of: value, ref, provider"),
|
||||
);
|
||||
expectErrorIncludes("must include exactly one of: value, ref, provider");
|
||||
});
|
||||
|
||||
it("fails dry-run when a builder-assigned SecretRef is unresolved", async () => {
|
||||
@@ -1981,9 +1930,7 @@ describe("config cli", () => {
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: 1 SecretRef assignment(s) could not be resolved."),
|
||||
);
|
||||
expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
|
||||
});
|
||||
|
||||
it("emits structured JSON for --dry-run --json success", async () => {
|
||||
@@ -2188,7 +2135,8 @@ describe("config cli", () => {
|
||||
};
|
||||
expect(payload.ok).toBe(false);
|
||||
const errorKinds = (payload.errors ?? []).map((entry) => entry.kind);
|
||||
expect(errorKinds).toEqual(expect.arrayContaining(["schema", "resolvability"]));
|
||||
expect(errorKinds).toContain("schema");
|
||||
expect(errorKinds).toContain("resolvability");
|
||||
const errorRefs = (payload.errors ?? []).map((entry) => entry.ref ?? "");
|
||||
const discordTokenRefs = errorRefs.filter((ref) => ref.includes("default:DISCORD_BOT_TOKEN"));
|
||||
expect(discordTokenRefs.length).toBeGreaterThan(0);
|
||||
@@ -2231,10 +2179,8 @@ describe("config cli", () => {
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: 1 SecretRef assignment(s) could not be resolved."),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("provider mismatch"));
|
||||
expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
|
||||
expectErrorIncludes("provider mismatch");
|
||||
});
|
||||
|
||||
it("fails dry-run for nested provider edits that make existing refs unresolvable", async () => {
|
||||
@@ -2274,17 +2220,13 @@ describe("config cli", () => {
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(mockResolveSecretRefValue).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "vaultfile",
|
||||
id: "/providers/search/apiKey",
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Dry run failed: 1 SecretRef assignment(s) could not be resolved."),
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("provider mismatch"));
|
||||
const [secretRef, resolveOptions] = requireResolveSecretRefCall(0);
|
||||
const secretRefRecord = requireRecord(secretRef, "existing SecretRef");
|
||||
expect(secretRefRecord.provider).toBe("vaultfile");
|
||||
expect(secretRefRecord.id).toBe("/providers/search/apiKey");
|
||||
expect(resolveOptions).toBeTypeOf("object");
|
||||
expectErrorIncludes("Dry run failed: 1 SecretRef assignment(s) could not be resolved.");
|
||||
expectErrorIncludes("provider mismatch");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user