mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 13:11:23 +00:00
test: consolidate agent command and config scenarios
This commit is contained in:
@@ -47,81 +47,48 @@ function restoreRedactedValues<TOriginal>(
|
||||
}
|
||||
|
||||
describe("redactConfigSnapshot", () => {
|
||||
it("redacts top-level token fields", () => {
|
||||
it("redacts common secret field patterns across config sections", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
gateway: { auth: { token: "my-super-secret-gateway-token-value" } },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
expect(result.config).toEqual({
|
||||
gateway: { auth: { token: REDACTED_SENTINEL } },
|
||||
});
|
||||
});
|
||||
|
||||
it("redacts botToken in channel configs", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: {
|
||||
telegram: { botToken: "123456:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" },
|
||||
slack: { botToken: "fake-slack-bot-token-placeholder-value" },
|
||||
gateway: {
|
||||
auth: {
|
||||
token: "my-super-secret-gateway-token-value",
|
||||
password: "super-secret-password-value-here",
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
botToken: "123456:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef",
|
||||
webhookSecret: "telegram-webhook-secret-value-1234",
|
||||
},
|
||||
slack: {
|
||||
botToken: "fake-slack-bot-token-placeholder-value",
|
||||
signingSecret: "slack-signing-secret-value-1234",
|
||||
token: "secret-slack-token-value-here",
|
||||
},
|
||||
feishu: { appSecret: "feishu-app-secret-value-here-1234" },
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const channels = result.config.channels as Record<string, Record<string, string>>;
|
||||
expect(channels.telegram.botToken).toBe(REDACTED_SENTINEL);
|
||||
expect(channels.slack.botToken).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts apiKey in model providers", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
models: {
|
||||
providers: {
|
||||
openai: { apiKey: "sk-proj-abcdef1234567890ghij", baseUrl: "https://api.openai.com" },
|
||||
},
|
||||
},
|
||||
shortSecret: { token: "short" },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const models = result.config.models as Record<string, Record<string, Record<string, string>>>;
|
||||
expect(models.providers.openai.apiKey).toBe(REDACTED_SENTINEL);
|
||||
expect(models.providers.openai.baseUrl).toBe("https://api.openai.com");
|
||||
});
|
||||
|
||||
it("redacts password fields", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
gateway: { auth: { password: "super-secret-password-value-here" } },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const gw = result.config.gateway as Record<string, Record<string, string>>;
|
||||
expect(gw.auth.password).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
const cfg = result.config as typeof snapshot.config;
|
||||
|
||||
it("redacts appSecret fields", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: {
|
||||
feishu: { appSecret: "feishu-app-secret-value-here-1234" },
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const channels = result.config.channels as Record<string, Record<string, string>>;
|
||||
expect(channels.feishu.appSecret).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts signingSecret fields", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: {
|
||||
slack: { signingSecret: "slack-signing-secret-value-1234" },
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const channels = result.config.channels as Record<string, Record<string, string>>;
|
||||
expect(channels.slack.signingSecret).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts short secrets with same sentinel", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
gateway: { auth: { token: "short" } },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const gw = result.config.gateway as Record<string, Record<string, string>>;
|
||||
expect(gw.auth.token).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.gateway.auth.token).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.gateway.auth.password).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.channels.telegram.botToken).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.channels.telegram.webhookSecret).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.channels.slack.botToken).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.channels.slack.signingSecret).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.channels.slack.token).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.channels.feishu.appSecret).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.models.providers.openai.apiKey).toBe(REDACTED_SENTINEL);
|
||||
expect(cfg.models.providers.openai.baseUrl).toBe("https://api.openai.com");
|
||||
expect(cfg.shortSecret.token).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("preserves non-sensitive fields", () => {
|
||||
@@ -226,23 +193,15 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(result.raw).toContain(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts parsed object as well", () => {
|
||||
const config = {
|
||||
it("redacts parsed and resolved objects", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: { discord: { token: "MTIzNDU2Nzg5MDEyMzQ1Njc4.GaBcDe.FgH" } },
|
||||
};
|
||||
const snapshot = makeSnapshot(config);
|
||||
gateway: { auth: { token: "supersecrettoken123456" } },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const parsed = result.parsed as Record<string, Record<string, Record<string, string>>>;
|
||||
expect(parsed.channels.discord.token).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts resolved object as well", () => {
|
||||
const config = {
|
||||
gateway: { auth: { token: "supersecrettoken123456" } },
|
||||
};
|
||||
const snapshot = makeSnapshot(config);
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const resolved = result.resolved as Record<string, Record<string, Record<string, string>>>;
|
||||
expect(parsed.channels.discord.token).toBe(REDACTED_SENTINEL);
|
||||
expect(resolved.gateway.auth.token).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
@@ -303,17 +262,6 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(channels.slack.accounts.workspace2.appToken).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("handles webhookSecret field", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: {
|
||||
telegram: { webhookSecret: "telegram-webhook-secret-value-1234" },
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const channels = result.config.channels as Record<string, Record<string, string>>;
|
||||
expect(channels.telegram.webhookSecret).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts env vars that look like secrets", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
env: {
|
||||
@@ -330,41 +278,45 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(env.vars.OPENAI_API_KEY).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("does NOT redact numeric 'tokens' fields (token regex fix)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
memory: { tokens: 8192 },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const memory = result.config.memory as Record<string, number>;
|
||||
expect(memory.tokens).toBe(8192);
|
||||
});
|
||||
it("respects token-name redaction boundaries", () => {
|
||||
const cases = [
|
||||
{
|
||||
name: "does not redact numeric tokens field",
|
||||
snapshot: makeSnapshot({ memory: { tokens: 8192 } }),
|
||||
assert: (config: Record<string, unknown>) => {
|
||||
expect((config.memory as Record<string, unknown>).tokens).toBe(8192);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "does not redact softThresholdTokens",
|
||||
snapshot: makeSnapshot({ compaction: { softThresholdTokens: 50000 } }),
|
||||
assert: (config: Record<string, unknown>) => {
|
||||
expect((config.compaction as Record<string, unknown>).softThresholdTokens).toBe(50000);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "does not redact string tokens field",
|
||||
snapshot: makeSnapshot({ memory: { tokens: "should-not-be-redacted" } }),
|
||||
assert: (config: Record<string, unknown>) => {
|
||||
expect((config.memory as Record<string, unknown>).tokens).toBe("should-not-be-redacted");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "still redacts singular token field",
|
||||
snapshot: makeSnapshot({
|
||||
channels: { slack: { token: "secret-slack-token-value-here" } },
|
||||
}),
|
||||
assert: (config: Record<string, unknown>) => {
|
||||
const channels = config.channels as Record<string, Record<string, string>>;
|
||||
expect(channels.slack.token).toBe(REDACTED_SENTINEL);
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
it("does NOT redact 'softThresholdTokens' (token regex fix)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
compaction: { softThresholdTokens: 50000 },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
const compaction = config.compaction as Record<string, number>;
|
||||
expect(compaction.softThresholdTokens).toBe(50000);
|
||||
});
|
||||
|
||||
it("does NOT redact string 'tokens' field either", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
memory: { tokens: "should-not-be-redacted" },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const memory = result.config.memory as Record<string, string>;
|
||||
expect(memory.tokens).toBe("should-not-be-redacted");
|
||||
});
|
||||
|
||||
it("still redacts 'token' (singular) fields", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
channels: { slack: { token: "secret-slack-token-value-here" } },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const channels = result.config.channels as Record<string, Record<string, string>>;
|
||||
expect(channels.slack.token).toBe(REDACTED_SENTINEL);
|
||||
for (const testCase of cases) {
|
||||
const result = redactConfigSnapshot(testCase.snapshot);
|
||||
testCase.assert(result.config as Record<string, unknown>);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses uiHints to determine sensitivity", () => {
|
||||
@@ -439,234 +391,251 @@ describe("redactConfigSnapshot", () => {
|
||||
expect(config.plugins.entries["voice-call"].config.apiToken).toBe("not-secret-on-purpose");
|
||||
});
|
||||
|
||||
it("handles nested values properly (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
custom1: { anykey: { mySecret: "this-is-a-custom-secret-value" } },
|
||||
custom2: [{ mySecret: "this-is-a-custom-secret-value" }],
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.custom1.anykey.mySecret).toBe(REDACTED_SENTINEL);
|
||||
expect(config.custom2[0].mySecret).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.custom1.anykey.mySecret).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.custom2[0].mySecret).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles nested values properly with hints (roundtrip)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"custom1.*.mySecret": { sensitive: true },
|
||||
"custom2[].mySecret": { sensitive: true },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
custom1: { anykey: { mySecret: "this-is-a-custom-secret-value" } },
|
||||
custom2: [{ mySecret: "this-is-a-custom-secret-value" }],
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.custom1.anykey.mySecret).toBe(REDACTED_SENTINEL);
|
||||
expect(config.custom2[0].mySecret).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.custom1.anykey.mySecret).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.custom2[0].mySecret).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles records that are directly sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
custom: { token: "this-is-a-custom-secret-value", mySecret: "this-is-a-custom-secret-value" },
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.custom.token).toBe(REDACTED_SENTINEL);
|
||||
expect(config.custom.mySecret).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.custom.token).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.custom.mySecret).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles records that are directly sensitive with hints (roundtrip)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"custom.*": { sensitive: true },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
custom: {
|
||||
anykey: "this-is-a-custom-secret-value",
|
||||
mySecret: "this-is-a-custom-secret-value",
|
||||
it("round-trips nested and array sensitivity cases", () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
snapshot: TestSnapshot<Record<string, unknown>>;
|
||||
hints?: ConfigUiHints;
|
||||
assert: (params: {
|
||||
redacted: Record<string, unknown>;
|
||||
restored: Record<string, unknown>;
|
||||
}) => void;
|
||||
}> = [
|
||||
{
|
||||
name: "nested values (schema)",
|
||||
snapshot: makeSnapshot({
|
||||
custom1: { anykey: { mySecret: "this-is-a-custom-secret-value" } },
|
||||
custom2: [{ mySecret: "this-is-a-custom-secret-value" }],
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted as Record<string, Record<string, unknown>>;
|
||||
const cfgCustom2 = cfg.custom2 as unknown[];
|
||||
expect(cfgCustom2.length).toBeGreaterThan(0);
|
||||
expect((cfg.custom1.anykey as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
|
||||
expect((cfgCustom2[0] as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
|
||||
const out = restored as Record<string, Record<string, unknown>>;
|
||||
const outCustom2 = out.custom2 as unknown[];
|
||||
expect(outCustom2.length).toBeGreaterThan(0);
|
||||
expect((out.custom1.anykey as Record<string, unknown>).mySecret).toBe(
|
||||
"this-is-a-custom-secret-value",
|
||||
);
|
||||
expect((outCustom2[0] as Record<string, unknown>).mySecret).toBe(
|
||||
"this-is-a-custom-secret-value",
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.custom.anykey).toBe(REDACTED_SENTINEL);
|
||||
expect(config.custom.mySecret).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.custom.anykey).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.custom.mySecret).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles arrays that are directly sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
token: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.token[0]).toBe(REDACTED_SENTINEL);
|
||||
expect(config.token[1]).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.token[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.token[1]).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles arrays that are directly sensitive with hints (roundtrip)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"custom[]": { sensitive: true },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
custom: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.custom[0]).toBe(REDACTED_SENTINEL);
|
||||
expect(config.custom[1]).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.custom[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.custom[1]).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles arrays that are not sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
harmless: ["this-is-a-custom-harmless-value", "this-is-a-custom-secret-looking-value"],
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.harmless[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect(config.harmless[1]).toBe("this-is-a-custom-secret-looking-value");
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.harmless[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect(restored.harmless[1]).toBe("this-is-a-custom-secret-looking-value");
|
||||
});
|
||||
|
||||
it("handles arrays that are not sensitive with hints (roundtrip)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"custom[]": { sensitive: false },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
custom: ["this-is-a-custom-harmless-value", "this-is-a-custom-secret-value"],
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.custom[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect(config.custom[1]).toBe("this-is-a-custom-secret-value");
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.custom[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect(restored.custom[1]).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
|
||||
it("handles deep arrays that are directly sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
{
|
||||
name: "nested values (uiHints)",
|
||||
hints: {
|
||||
"custom1.*.mySecret": { sensitive: true },
|
||||
"custom2[].mySecret": { sensitive: true },
|
||||
},
|
||||
snapshot: makeSnapshot({
|
||||
custom1: { anykey: { mySecret: "this-is-a-custom-secret-value" } },
|
||||
custom2: [{ mySecret: "this-is-a-custom-secret-value" }],
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted as Record<string, Record<string, unknown>>;
|
||||
const cfgCustom2 = cfg.custom2 as unknown[];
|
||||
expect(cfgCustom2.length).toBeGreaterThan(0);
|
||||
expect((cfg.custom1.anykey as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
|
||||
expect((cfgCustom2[0] as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
|
||||
const out = restored as Record<string, Record<string, unknown>>;
|
||||
const outCustom2 = out.custom2 as unknown[];
|
||||
expect(outCustom2.length).toBeGreaterThan(0);
|
||||
expect((out.custom1.anykey as Record<string, unknown>).mySecret).toBe(
|
||||
"this-is-a-custom-secret-value",
|
||||
);
|
||||
expect((outCustom2[0] as Record<string, unknown>).mySecret).toBe(
|
||||
"this-is-a-custom-secret-value",
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "directly sensitive records and arrays",
|
||||
snapshot: makeSnapshot({
|
||||
custom: {
|
||||
token: "this-is-a-custom-secret-value",
|
||||
mySecret: "this-is-a-custom-secret-value",
|
||||
},
|
||||
token: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.nested.level.token[0]).toBe(REDACTED_SENTINEL);
|
||||
expect(config.nested.level.token[1]).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.nested.level.token[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.nested.level.token[1]).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted;
|
||||
const custom = cfg.custom as Record<string, unknown>;
|
||||
expect(custom.token).toBe(REDACTED_SENTINEL);
|
||||
expect(custom.mySecret).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.token as unknown[])[0]).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.token as unknown[])[1]).toBe(REDACTED_SENTINEL);
|
||||
|
||||
it("handles deep arrays that are directly sensitive with hints (roundtrip)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"nested.level.custom[]": { sensitive: true },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
custom: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
const out = restored;
|
||||
const restoredCustom = out.custom as Record<string, unknown>;
|
||||
expect(restoredCustom.token).toBe("this-is-a-custom-secret-value");
|
||||
expect(restoredCustom.mySecret).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.token as unknown[])[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.token as unknown[])[1]).toBe("this-is-a-custom-secret-value");
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.nested.level.custom[0]).toBe(REDACTED_SENTINEL);
|
||||
expect(config.nested.level.custom[1]).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.nested.level.custom[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect(restored.nested.level.custom[1]).toBe("this-is-a-custom-secret-value");
|
||||
});
|
||||
{
|
||||
name: "directly sensitive records and arrays (uiHints)",
|
||||
hints: {
|
||||
"custom.*": { sensitive: true },
|
||||
"customArray[]": { sensitive: true },
|
||||
},
|
||||
snapshot: makeSnapshot({
|
||||
custom: {
|
||||
anykey: "this-is-a-custom-secret-value",
|
||||
mySecret: "this-is-a-custom-secret-value",
|
||||
},
|
||||
customArray: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted;
|
||||
const custom = cfg.custom as Record<string, unknown>;
|
||||
expect(custom.anykey).toBe(REDACTED_SENTINEL);
|
||||
expect(custom.mySecret).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.customArray as unknown[])[0]).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.customArray as unknown[])[1]).toBe(REDACTED_SENTINEL);
|
||||
|
||||
it("handles deep non-string arrays that are directly sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
token: [42, 815],
|
||||
const out = restored;
|
||||
const restoredCustom = out.custom as Record<string, unknown>;
|
||||
expect(restoredCustom.anykey).toBe("this-is-a-custom-secret-value");
|
||||
expect(restoredCustom.mySecret).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.customArray as unknown[])[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.customArray as unknown[])[1]).toBe("this-is-a-custom-secret-value");
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.nested.level.token[0]).toBe(42);
|
||||
expect(config.nested.level.token[1]).toBe(815);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.nested.level.token[0]).toBe(42);
|
||||
expect(restored.nested.level.token[1]).toBe(815);
|
||||
});
|
||||
{
|
||||
name: "non-sensitive arrays remain unchanged",
|
||||
hints: {
|
||||
"custom[]": { sensitive: false },
|
||||
},
|
||||
snapshot: makeSnapshot({
|
||||
harmless: ["this-is-a-custom-harmless-value", "this-is-a-custom-secret-looking-value"],
|
||||
custom: ["this-is-a-custom-harmless-value", "this-is-a-custom-secret-value"],
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted;
|
||||
expect((cfg.harmless as unknown[])[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect((cfg.harmless as unknown[])[1]).toBe("this-is-a-custom-secret-looking-value");
|
||||
expect((cfg.custom as unknown[])[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect((cfg.custom as unknown[])[1]).toBe("this-is-a-custom-secret-value");
|
||||
|
||||
it("handles deep non-string arrays that are directly sensitive with hints (roundtrip)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"nested.level.custom[]": { sensitive: true },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
custom: [42, 815],
|
||||
const out = restored;
|
||||
expect((out.harmless as unknown[])[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect((out.harmless as unknown[])[1]).toBe("this-is-a-custom-secret-looking-value");
|
||||
expect((out.custom as unknown[])[0]).toBe("this-is-a-custom-harmless-value");
|
||||
expect((out.custom as unknown[])[1]).toBe("this-is-a-custom-secret-value");
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.nested.level.custom[0]).toBe(42);
|
||||
expect(config.nested.level.custom[1]).toBe(815);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.nested.level.custom[0]).toBe(42);
|
||||
expect(restored.nested.level.custom[1]).toBe(815);
|
||||
});
|
||||
{
|
||||
name: "deep schema-sensitive arrays and upstream-sensitive paths",
|
||||
snapshot: makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
token: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
harmless: ["value", "value"],
|
||||
},
|
||||
password: {
|
||||
harmless: ["value", "value"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((cfg.nested.level.token as unknown[])[0]).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.nested.level.token as unknown[])[1]).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.nested.level.harmless as unknown[])[0]).toBe("value");
|
||||
expect((cfg.nested.level.harmless as unknown[])[1]).toBe("value");
|
||||
expect((cfg.nested.password.harmless as unknown[])[0]).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.nested.password.harmless as unknown[])[1]).toBe(REDACTED_SENTINEL);
|
||||
|
||||
it("handles deep arrays that are upstream sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
nested: {
|
||||
password: {
|
||||
harmless: ["value", "value"],
|
||||
const out = restored as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((out.nested.level.token as unknown[])[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.nested.level.token as unknown[])[1]).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.nested.level.harmless as unknown[])[0]).toBe("value");
|
||||
expect((out.nested.level.harmless as unknown[])[1]).toBe("value");
|
||||
expect((out.nested.password.harmless as unknown[])[0]).toBe("value");
|
||||
expect((out.nested.password.harmless as unknown[])[1]).toBe("value");
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.nested.password.harmless[0]).toBe(REDACTED_SENTINEL);
|
||||
expect(config.nested.password.harmless[1]).toBe(REDACTED_SENTINEL);
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.nested.password.harmless[0]).toBe("value");
|
||||
expect(restored.nested.password.harmless[1]).toBe("value");
|
||||
});
|
||||
{
|
||||
name: "deep non-string arrays on schema-sensitive paths remain unchanged",
|
||||
snapshot: makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
token: [42, 815],
|
||||
},
|
||||
},
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((cfg.nested.level.token as unknown[])[0]).toBe(42);
|
||||
expect((cfg.nested.level.token as unknown[])[1]).toBe(815);
|
||||
|
||||
it("handles deep arrays that are not sensitive (roundtrip)", () => {
|
||||
const snapshot = makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
harmless: ["value", "value"],
|
||||
const out = restored as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((out.nested.level.token as unknown[])[0]).toBe(42);
|
||||
expect((out.nested.level.token as unknown[])[1]).toBe(815);
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = redactConfigSnapshot(snapshot);
|
||||
const config = result.config as typeof snapshot.config;
|
||||
expect(config.nested.level.harmless[0]).toBe("value");
|
||||
expect(config.nested.level.harmless[1]).toBe("value");
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config);
|
||||
expect(restored.nested.level.harmless[0]).toBe("value");
|
||||
expect(restored.nested.level.harmless[1]).toBe("value");
|
||||
{
|
||||
name: "deep arrays respect uiHints sensitivity",
|
||||
hints: {
|
||||
"nested.level.custom[]": { sensitive: true },
|
||||
},
|
||||
snapshot: makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
custom: ["this-is-a-custom-secret-value", "this-is-a-custom-secret-value"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((cfg.nested.level.custom as unknown[])[0]).toBe(REDACTED_SENTINEL);
|
||||
expect((cfg.nested.level.custom as unknown[])[1]).toBe(REDACTED_SENTINEL);
|
||||
|
||||
const out = restored as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((out.nested.level.custom as unknown[])[0]).toBe("this-is-a-custom-secret-value");
|
||||
expect((out.nested.level.custom as unknown[])[1]).toBe("this-is-a-custom-secret-value");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deep non-string arrays respect uiHints sensitivity",
|
||||
hints: {
|
||||
"nested.level.custom[]": { sensitive: true },
|
||||
},
|
||||
snapshot: makeSnapshot({
|
||||
nested: {
|
||||
level: {
|
||||
custom: [42, 815],
|
||||
},
|
||||
},
|
||||
}),
|
||||
assert: ({ redacted, restored }) => {
|
||||
const cfg = redacted as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((cfg.nested.level.custom as unknown[])[0]).toBe(42);
|
||||
expect((cfg.nested.level.custom as unknown[])[1]).toBe(815);
|
||||
|
||||
const out = restored as Record<string, Record<string, Record<string, unknown>>>;
|
||||
expect((out.nested.level.custom as unknown[])[0]).toBe(42);
|
||||
expect((out.nested.level.custom as unknown[])[1]).toBe(815);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
const redacted = redactConfigSnapshot(testCase.snapshot, testCase.hints);
|
||||
const restored = restoreRedactedValues(
|
||||
redacted.config,
|
||||
testCase.snapshot.config,
|
||||
testCase.hints,
|
||||
);
|
||||
testCase.assert({
|
||||
redacted: redacted.config as Record<string, unknown>,
|
||||
restored: restored as Record<string, unknown>,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("respects sensitive:false in uiHints even for regex-matching paths", () => {
|
||||
@@ -793,12 +762,12 @@ describe("restoreRedactedValues", () => {
|
||||
expect(restoreRedactedValues_orig(incoming, original).ok).toBe(false);
|
||||
});
|
||||
|
||||
it("handles null and undefined inputs", () => {
|
||||
expect(restoreRedactedValues_orig(null, { token: "x" }).ok).toBe(false);
|
||||
expect(restoreRedactedValues_orig(undefined, { token: "x" }).ok).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects non-object inputs", () => {
|
||||
it("rejects invalid restore inputs", () => {
|
||||
const invalidInputs = [null, undefined, "token-value"] as const;
|
||||
for (const input of invalidInputs) {
|
||||
const result = restoreRedactedValues_orig(input, { token: "x" });
|
||||
expect(result.ok).toBe(false);
|
||||
}
|
||||
expect(restoreRedactedValues_orig("token-value", { token: "x" })).toEqual({
|
||||
ok: false,
|
||||
error: "input not an object",
|
||||
|
||||
Reference in New Issue
Block a user