test: consolidate agent command and config scenarios

This commit is contained in:
Peter Steinberger
2026-02-21 21:43:14 +00:00
parent a1ccd03da0
commit 5d9e7c942c

View File

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