Files
cadSet/SimpleCADAPI/scene-contract/test/canonical.test.ts

88 lines
3.3 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
canonicalJsonBytes,
parseCanonicalJson,
parseStrictJson,
validateNormalizedProduct,
withSceneRevision,
} from "../src/index.js";
test("canonical JSON renders finite Numbers from their value", () => {
assert.equal(canonicalJsonBytes(1e30).toString("utf8"), "1e+30");
assert.equal(canonicalJsonBytes(parseStrictJson("1e30")).toString("utf8"), "1e+30");
const nested = parseStrictJson('{"large":1e30,"small":[1E-7]}');
assert.equal(
canonicalJsonBytes(nested).toString("utf8"),
'{"large":1e+30,"small":[1e-7]}',
);
assert.deepEqual(parseCanonicalJson("1e+30"), 1e30);
assert.throws(() => parseCanonicalJson("1e30"), /canonical/);
});
test("canonical JSON normalizes negative zero at root and nested positions", () => {
const root = parseStrictJson("-0");
assert.equal(Object.is(root, -0), true);
assert.equal(canonicalJsonBytes(root).toString("utf8"), "0");
const nested = parseStrictJson('{"values":[-0,0e0]}');
assert.equal(canonicalJsonBytes(nested).toString("utf8"), '{"values":[0,0]}');
assert.throws(() => parseCanonicalJson("-0"), /canonical/);
});
test("cloning parsed values does not change canonical bytes or scene revision", () => {
const scene = parseStrictJson('{"measurement":1e30,"nested":{"epsilon":1e-7}}') as Record<
string,
unknown
>;
const clone = structuredClone(scene);
assert.deepEqual(canonicalJsonBytes(clone), canonicalJsonBytes(scene));
assert.deepEqual(withSceneRevision(clone), withSceneRevision(scene));
});
test("unsafe integer JSON is rejected lexically without affecting exponent Numbers", () => {
assert.throws(() => parseStrictJson("9007199254740992"), /safe domain/);
assert.throws(() => parseStrictJson('{"value":-9007199254740992}'), /safe domain/);
assert.equal(
canonicalJsonBytes(Number.MAX_SAFE_INTEGER + 1).toString("utf8"),
"9007199254740992",
);
assert.equal(canonicalJsonBytes(parseStrictJson("1e30")).toString("utf8"), "1e+30");
});
test("strict JSON rejects unpaired surrogates in values and object keys", () => {
assert.throws(() => parseStrictJson('{"value":"\\ud800"}'), /surrogate/);
assert.throws(() => parseStrictJson('{"\\ud800":0}'), /surrogate/);
});
test("serialized JSON over the depth limit reports a budget issue", () => {
let serialized = "0";
for (let depth = 0; depth < 65; depth += 1) serialized = `{"x":${serialized}}`;
const result = validateNormalizedProduct(serialized);
assert.equal(result.firstError?.code, "resource_limit_exceeded");
assert.equal(result.firstError?.phase, "budget");
assert.equal(result.firstError?.path, "/x".repeat(65));
});
test("serialized non-finite tokens and overflow report nonfinite_json_number", () => {
for (const serialized of [
"NaN",
"Infinity",
"-Infinity",
"1e999",
'[0,NaN]',
'{"value":1e999}',
]) {
const result = validateNormalizedProduct(serialized);
assert.equal(result.firstError?.code, "nonfinite_json_number", serialized);
assert.equal(result.firstError?.phase, "parse", serialized);
}
const programmatic = validateNormalizedProduct({ metadata: { value: Number.NaN } });
assert.equal(programmatic.firstError?.code, "nonfinite_json_number");
assert.equal(programmatic.firstError?.path, "/metadata/value");
});