import { describe, expect, it } from 'vitest' import { canonicalHash, canonicalize } from '../canonical-hash.js' describe('canonicalHash', () => { it('is deterministic — same input yields the same hash (test 1)', () => { const spec = { screenCode: 'SCR-X-001', entity: 'TypeAffaire', view: 'list', columns: [{ key: 'code' }] } expect(canonicalHash(spec)).toBe(canonicalHash(spec)) expect(canonicalHash(spec)).toMatch(/^sha256-/) }) it('is insensitive to object key order (test 2)', () => { const a = { entity: 'TypeAffaire', view: 'list', permission: 'gaf.x.read' } const b = { permission: 'gaf.x.read', view: 'list', entity: 'TypeAffaire' } expect(canonicalHash(a)).toBe(canonicalHash(b)) }) it('is sensitive to value changes (test 3)', () => { const base = { entity: 'TypeAffaire', view: 'list', columns: [{ key: 'code', labelKey: 'list.columns.code' }] } const changed = { entity: 'TypeAffaire', view: 'list', columns: [{ key: 'code', labelKey: 'list.columns.libelle' }] } expect(canonicalHash(base)).not.toBe(canonicalHash(changed)) }) it('treats array order as significant (display order matters)', () => { expect(canonicalHash({ columns: [{ key: 'a' }, { key: 'b' }] })).not.toBe( canonicalHash({ columns: [{ key: 'b' }, { key: 'a' }] }), ) }) it('ignores undefined values (an absent key === an explicit undefined)', () => { expect(canonicalHash({ a: 1, b: undefined })).toBe(canonicalHash({ a: 1 })) }) }) describe('canonicalize', () => { it('sorts nested object keys recursively', () => { const out = canonicalize({ b: { d: 2, c: 1 }, a: 0 }) as Record expect(JSON.stringify(out)).toBe('{"a":0,"b":{"c":1,"d":2}}') }) it('passes primitives and null through untouched', () => { expect(canonicalize('s')).toBe('s') expect(canonicalize(42)).toBe(42) expect(canonicalize(null)).toBeNull() }) })