// canonicalJson 共享导出测试(Plan 3 Task 1,前置事实 6):从 register.service.ts 提取到 // apps/api/src/common/canonical.ts——register 侧改 import 零行为变更(既有 media_bundle 清单 // 哈希测试继续绿的回归责任在 tests/integration/source-register.test.ts,此处钉导出本身的 // 序列化定义:对象键递归排序、无空白、数组保序)。 // 独立重算对照:期望串手写钉死,不 import 实现——定义漂移即爆红。 import { describe, expect, it } from 'vitest' import { canonicalJson } from '../../apps/api/src/common/canonical.js' describe('canonicalJson(共享导出,前置事实 6)', () => { it('对象键递归排序:任意嵌套层级都按键名字典序', () => { expect(canonicalJson({ b: 1, a: 2 })).toBe('{"a":2,"b":1}') expect(canonicalJson({ b: 1, a: { d: 2, c: 3 } })).toBe('{"a":{"c":3,"d":2},"b":1}') expect(canonicalJson({ z: { y: { x: { w: 1, v: 2 } } } })).toBe('{"z":{"y":{"x":{"v":2,"w":1}}}}') }) it('无空白:键与值、数组元素间均无空格/换行', () => { expect(canonicalJson({ a: [1, 2, 3], b: { c: 'x' } })).toBe('{"a":[1,2,3],"b":{"c":"x"}}') expect(canonicalJson({ a: 1, b: 2 })).not.toContain(' ') expect(canonicalJson({ a: { b: { c: [1, { d: 2 }] } } })).not.toMatch(/\s/) }) it('数组保序:元素顺序是语义的一部分,不排序', () => { expect(canonicalJson([3, 1, 2])).toBe('[3,1,2]') expect(canonicalJson([{ z: 1, a: 2 }, { y: 3, b: 4 }])).toBe('[{"a":2,"z":1},{"b":4,"y":3}]') }) it('原始值走 JSON.stringify 语义:字符串/数字/布尔/null', () => { expect(canonicalJson('x')).toBe('"x"') expect(canonicalJson('a"b')).toBe('"a\\"b"') expect(canonicalJson(3)).toBe('3') expect(canonicalJson(true)).toBe('true') expect(canonicalJson(null)).toBe('null') expect(canonicalJson({ a: null, b: true, c: 0, d: '' })).toBe('{"a":null,"b":true,"c":0,"d":""}') }) it('键排序按 UTF-16 码元(与 Array.prototype.sort 默认一致)——大写在前、非 ASCII 原样', () => { expect(canonicalJson({ b: 1, A: 2, a: 3 })).toBe('{"A":2,"a":3,"b":1}') expect(canonicalJson({ 图: 1, 中: 2 })).toBe(JSON.stringify({ 中: 2, 图: 1 })) }) it('空对象/空数组序列化', () => { expect(canonicalJson({})).toBe('{}') expect(canonicalJson([])).toBe('[]') expect(canonicalJson({ a: {}, b: [] })).toBe('{"a":{},"b":[]}') }) })