Files
dev-puppeteer/packages/core/src/hash.test.ts
2026-05-09 23:56:10 +09:00

64 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { canonicalize, hash } from "./hash.js";
describe("content hashing", () => {
it("canonicalizes object keys lexicographically while preserving array order", () => {
expect(canonicalize({ z: 1, a: [{ b: true, a: null }] })).toBe(
'{"a":[{"a":null,"b":true}],"z":1}',
);
});
it("hashes equivalent object key orders to the same sha256 hex", () => {
const left = hash({ z: 1, a: 2 });
const right = hash({ a: 2, z: 1 });
expect(left).toBe(right);
expect(left).toMatch(/^[a-f0-9]{64}$/);
});
it("preserves own __proto__ keys while canonicalizing objects", () => {
const withProtoKey = JSON.parse('{"__proto__":{"x":1},"a":2}') as unknown;
expect(canonicalize(withProtoKey)).toBe('{"__proto__":{"x":1},"a":2}');
expect(hash(withProtoKey)).not.toBe(hash({ a: 2 }));
});
it("rejects hidden object keys that would be ignored by JSON rendering", () => {
const withSymbol = { a: 1, [Symbol("x")]: 2 };
const withHidden = { a: 1 };
Object.defineProperty(withHidden, "hidden", { value: 2, enumerable: false });
expect(() => canonicalize(withSymbol)).toThrow(/non-enumerable or symbol/);
expect(() => canonicalize(withHidden)).toThrow(/non-enumerable or symbol/);
});
it("rejects non-index array object keys that would be ignored by JSON rendering", () => {
const withStringKey = [1] as number[] & { extra?: number };
withStringKey.extra = 2;
const withSymbol = [1] as unknown[];
Object.defineProperty(withSymbol, Symbol("x"), { value: 2, enumerable: true });
expect(() => canonicalize(withStringKey)).toThrow(/non-index array/);
expect(() => canonicalize(withSymbol)).toThrow(/non-index array/);
});
it("renders the shortest round-trippable number literals without plus signs", () => {
expect(canonicalize([100, 1000, 11000, 123000, 1e20, 1e21, 0.000001, 0.0000001])).toBe(
"[100,1e3,11e3,123e3,1e20,1e21,1e-6,1e-7]",
);
});
it("rejects values that are not JSON-safe", () => {
const sparse = Array<number>(3);
sparse[0] = 1;
sparse[2] = 3;
expect(() => canonicalize({ date: new Date("2026-05-09T00:00:00Z") })).toThrow(
/non-plain object/,
);
expect(() => canonicalize({ missing: undefined })).toThrow(/undefined/);
expect(() => canonicalize(sparse)).toThrow(/sparse array/);
});
});