mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 13:57:26 +00:00
refactor: dedupe redact snapshot restore prelude
This commit is contained in:
@@ -798,6 +798,37 @@ describe("restoreRedactedValues", () => {
|
||||
expect(restoreRedactedValues_orig(undefined, { token: "x" }).ok).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects non-object inputs", () => {
|
||||
expect(restoreRedactedValues_orig("token-value", { token: "x" })).toEqual({
|
||||
ok: false,
|
||||
error: "input not an object",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a human-readable error when sentinel cannot be restored", () => {
|
||||
const incoming = {
|
||||
channels: { newChannel: { token: REDACTED_SENTINEL } },
|
||||
};
|
||||
const result = restoreRedactedValues_orig(incoming, {});
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.humanReadableMessage).toContain(REDACTED_SENTINEL);
|
||||
expect(result.humanReadableMessage).toContain("channels.newChannel.token");
|
||||
});
|
||||
|
||||
it("keeps unmatched wildcard array entries unchanged outside extension paths", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"custom.*": { sensitive: true },
|
||||
};
|
||||
const incoming = {
|
||||
custom: { items: [REDACTED_SENTINEL] },
|
||||
};
|
||||
const original = {
|
||||
custom: { items: ["original-secret-value"] },
|
||||
};
|
||||
const result = restoreRedactedValues(incoming, original, hints) as typeof incoming;
|
||||
expect(result.custom.items[0]).toBe(REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("round-trips config through redact → restore", () => {
|
||||
const originalConfig = {
|
||||
gateway: { auth: { token: "gateway-auth-secret-token-value" }, port: 18789 },
|
||||
|
||||
@@ -404,6 +404,20 @@ function toObjectRecord(value: unknown): Record<string, unknown> {
|
||||
return {};
|
||||
}
|
||||
|
||||
function shouldPassThroughRestoreValue(incoming: unknown): boolean {
|
||||
return incoming === null || incoming === undefined || typeof incoming !== "object";
|
||||
}
|
||||
|
||||
function toRestoreArrayContext(
|
||||
incoming: unknown,
|
||||
prefix: string,
|
||||
): { incoming: unknown[]; path: string } | null {
|
||||
if (!Array.isArray(incoming)) {
|
||||
return null;
|
||||
}
|
||||
return { incoming, path: `${prefix}[]` };
|
||||
}
|
||||
|
||||
function restoreArrayItemWithLookup(params: {
|
||||
item: unknown;
|
||||
index: number;
|
||||
@@ -478,26 +492,25 @@ function restoreRedactedValuesWithLookup(
|
||||
prefix: string,
|
||||
hints: ConfigUiHints,
|
||||
): unknown {
|
||||
if (incoming === null || incoming === undefined) {
|
||||
if (shouldPassThroughRestoreValue(incoming)) {
|
||||
return incoming;
|
||||
}
|
||||
if (typeof incoming !== "object") {
|
||||
return incoming;
|
||||
}
|
||||
if (Array.isArray(incoming)) {
|
||||
|
||||
const arrayContext = toRestoreArrayContext(incoming, prefix);
|
||||
if (arrayContext) {
|
||||
// Note: If the user removed an item in the middle of the array,
|
||||
// we have no way of knowing which one. In this case, the last
|
||||
// element(s) get(s) chopped off. Not good, so please don't put
|
||||
// sensitive string array in the config...
|
||||
const path = `${prefix}[]`;
|
||||
const { incoming: incomingArray, path } = arrayContext;
|
||||
if (!lookup.has(path)) {
|
||||
if (!isExtensionPath(prefix)) {
|
||||
return incoming;
|
||||
return incomingArray;
|
||||
}
|
||||
return restoreRedactedValuesGuessing(incoming, original, prefix, hints);
|
||||
return restoreRedactedValuesGuessing(incomingArray, original, prefix, hints);
|
||||
}
|
||||
return mapRedactedArray({
|
||||
incoming,
|
||||
incoming: incomingArray,
|
||||
original,
|
||||
path,
|
||||
mapItem: (item, index, originalArray) =>
|
||||
@@ -551,19 +564,18 @@ function restoreRedactedValuesGuessing(
|
||||
prefix: string,
|
||||
hints?: ConfigUiHints,
|
||||
): unknown {
|
||||
if (incoming === null || incoming === undefined) {
|
||||
if (shouldPassThroughRestoreValue(incoming)) {
|
||||
return incoming;
|
||||
}
|
||||
if (typeof incoming !== "object") {
|
||||
return incoming;
|
||||
}
|
||||
if (Array.isArray(incoming)) {
|
||||
|
||||
const arrayContext = toRestoreArrayContext(incoming, prefix);
|
||||
if (arrayContext) {
|
||||
// Note: If the user removed an item in the middle of the array,
|
||||
// we have no way of knowing which one. In this case, the last
|
||||
// element(s) get(s) chopped off. Not good, so please don't put
|
||||
// sensitive string array in the config...
|
||||
const path = `${prefix}[]`;
|
||||
return restoreGuessingArray(incoming, original, path, hints);
|
||||
const { incoming: incomingArray, path } = arrayContext;
|
||||
return restoreGuessingArray(incomingArray, original, path, hints);
|
||||
}
|
||||
const orig = toObjectRecord(original);
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
Reference in New Issue
Block a user