fix(conformance): preserve own JSON keys during comparison (#573)
This commit is contained in:
parent
f5013c50ca
commit
3460eda9ca
|
|
@ -319,11 +319,11 @@ function sortKeys(value: unknown): unknown {
|
|||
if (value === null || typeof value !== "object") return value;
|
||||
if (Array.isArray(value)) return value.map(sortKeys);
|
||||
if (!isRecord(value)) return value;
|
||||
const sorted: Record<string, unknown> = {};
|
||||
for (const key of Object.keys(value).sort()) {
|
||||
sorted[key] = sortKeys(value[key]);
|
||||
}
|
||||
return sorted;
|
||||
return Object.fromEntries(
|
||||
Object.keys(value)
|
||||
.sort()
|
||||
.map((key) => [key, sortKeys(value[key])]),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -262,3 +262,53 @@ describe("conformance replay package boundary", () => {
|
|||
).rejects.toThrow(/replay_plans references unknown scenario missing/u);
|
||||
});
|
||||
});
|
||||
|
||||
describe("conformance structured JSON drift", () => {
|
||||
it("detects an added own __proto__ field in captured JSON as drift", async () => {
|
||||
const expected = createEvidence(
|
||||
undefined,
|
||||
{ id: "fixture-provider", name: "Fixture provider", version: "1" },
|
||||
{ operation: "run", parameters: {}, result: { filesystem: {} } },
|
||||
);
|
||||
const pkg = createConformancePackage({
|
||||
...validPackageInput,
|
||||
expected_evidence: [
|
||||
{
|
||||
scenario_id: "s1",
|
||||
envelopes: [expected],
|
||||
bundle: null,
|
||||
required_dimensions: ["filesystem"],
|
||||
},
|
||||
],
|
||||
verifier_contracts: [
|
||||
{
|
||||
scenario_id: "s1",
|
||||
dimensions: [
|
||||
{ name: "filesystem", required: true, comparison: "semantic" },
|
||||
],
|
||||
timing_tolerance_ms: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
const captured: unknown = JSON.parse('{"__proto__":{"added":true}}');
|
||||
const actualEvidenceId = `ev_${"4".repeat(64)}`;
|
||||
const result = await replayConformancePackage(
|
||||
pkg,
|
||||
{
|
||||
s1: {
|
||||
evidence_id: actualEvidenceId,
|
||||
normalized_result: { filesystem: captured },
|
||||
},
|
||||
},
|
||||
passingRunner,
|
||||
);
|
||||
|
||||
expect(result.drift_detected).toBe(true);
|
||||
expect(result.trust_gate_results[0]?.verdict).toBe("fail");
|
||||
expect(result.first_drift).toMatchObject({
|
||||
scenario_id: "s1",
|
||||
dimension: "filesystem",
|
||||
evidence_id: actualEvidenceId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -198,3 +198,39 @@ describe("conformance trust gates", () => {
|
|||
expect(result.verdict).toBe("fail");
|
||||
});
|
||||
});
|
||||
|
||||
describe("conformance structured JSON comparison", () => {
|
||||
it.each([
|
||||
['{"__proto__":{"version":1}}', '{"__proto__":{"version":2}}'],
|
||||
['{"__proto__":"before"}', '{"__proto__":"after"}'],
|
||||
["{}", '{"__proto__":null}'],
|
||||
[
|
||||
'{"entries":[{"payload":{"__proto__":{"version":1}}}]}',
|
||||
'{"entries":[{"payload":{"__proto__":{"version":2}}}]}',
|
||||
],
|
||||
['{"constructor":1}', '{"constructor":2}'],
|
||||
['{"prototype":1}', '{"prototype":2}'],
|
||||
])("preserves changed own JSON keys in %s", (before, after) => {
|
||||
const expected: unknown = JSON.parse(before);
|
||||
const actual: unknown = JSON.parse(after);
|
||||
|
||||
expect(compareDimension("filesystem", expected, actual)).toMatchObject({
|
||||
status: "mismatch",
|
||||
});
|
||||
expect(JSON.stringify(expected)).toBe(before);
|
||||
expect(JSON.stringify(actual)).toBe(after);
|
||||
});
|
||||
|
||||
it("ignores key order while preserving own JSON keys in nested objects", () => {
|
||||
const expected: unknown = JSON.parse(
|
||||
'{"__proto__":{"a":1,"b":2},"constructor":[{"x":1,"prototype":2}]}',
|
||||
);
|
||||
const actual: unknown = JSON.parse(
|
||||
'{"constructor":[{"prototype":2,"x":1}],"__proto__":{"b":2,"a":1}}',
|
||||
);
|
||||
|
||||
expect(compareDimension("filesystem", expected, actual)).toMatchObject({
|
||||
status: "match",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue