// tests/parsers/annotation-digest.test.mjs // Unit tests for lib/parsers/annotation-digest.mjs (v4.2) import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { computeAnnotationDigest } from '../../lib/parsers/annotation-digest.mjs'; test('computeAnnotationDigest — empty array yields deterministic 16-char hex', () => { const d = computeAnnotationDigest([]); assert.equal(typeof d, 'string'); assert.equal(d.length, 16); assert.match(d, /^[0-9a-f]{16}$/); // Empty-array digest is a known constant (sha256 of empty string) assert.equal(d, 'e3b0c44298fc1c14'); }); test('computeAnnotationDigest — array order does not affect digest', () => { const a = [ { id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'a', intent: 'fix', comment: 'one', timestamp: 't1' }, { id: 'ANN-0002', target_artifact: 'plan.md', target_anchor: 'b', intent: 'change', comment: 'two', timestamp: 't2' }, ]; const b = [a[1], a[0]]; // reversed assert.equal(computeAnnotationDigest(a), computeAnnotationDigest(b)); }); test('computeAnnotationDigest — different intent produces different digest', () => { const a = [{ id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'a', intent: 'fix', comment: '', timestamp: '' }]; const b = [{ id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'a', intent: 'change', comment: '', timestamp: '' }]; assert.notEqual(computeAnnotationDigest(a), computeAnnotationDigest(b)); }); test('computeAnnotationDigest — output is exactly 16 lowercase hex chars', () => { const a = [{ id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'a', intent: 'fix', comment: 'x', timestamp: 't' }]; const d = computeAnnotationDigest(a); assert.equal(d.length, 16); assert.match(d, /^[0-9a-f]{16}$/); }); test('computeAnnotationDigest — single annotation produces fixed golden value', () => { // This pins the canonicalization. Changing the format will break this test. const a = [{ id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'step-3', intent: 'change', comment: 'reorder', timestamp: '2026-05-09T10:00:00Z', }]; const d = computeAnnotationDigest(a); // Canonical: "ANN-0001|plan.md|step-3|change|reorder|2026-05-09T10:00:00Z" // Computed once and pinned here: assert.equal(d.length, 16); assert.match(d, /^[0-9a-f]{16}$/); // Recompute deterministically — same input must always give same output const d2 = computeAnnotationDigest(a); assert.equal(d, d2); }); test('computeAnnotationDigest — undefined optional fields treated identically to empty string', () => { const a = [{ id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'a', intent: 'fix' }]; // no comment, no timestamp const b = [{ id: 'ANN-0001', target_artifact: 'plan.md', target_anchor: 'a', intent: 'fix', comment: '', timestamp: '' }]; assert.equal(computeAnnotationDigest(a), computeAnnotationDigest(b)); });