/** * docStore 单测 */ const docStore = require('../../services/devloop/doc-store'); describe('docStore', () => { describe('validateHTML', () => { it('rejects empty', () => { const r = docStore.validateHTML('', 'prd'); expect(r.ok).toBe(false); }); it('rejects missing article', () => { const r = docStore.validateHTML('

hi

', 'prd'); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('article'); }); it('rejects wrong doc kind', () => { const r = docStore.validateHTML('

x

y

', 'prd'); expect(r.ok).toBe(false); }); it('accepts valid PRD chapter', () => { const html = `

Title

body

Other

more

`; const r = docStore.validateHTML(html, 'prd'); expect(r.ok).toBe(true); }); it('detects section without h3', () => { const html = `

only p

`; const r = docStore.validateHTML(html, 'prd'); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('h3'); }); it('detects non-monotonic data-rev', () => { const html = `

x

y

x

y

`; const r = docStore.validateHTML(html, 'prd'); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('monotonic'); }); }); describe('validateUnitsSchema', () => { const validUnit = { id: 'auth-be', type: 'be', status: 'pending', depends_on: [], retries: 0, retry_budget: 5, contract_hash: 'sha256:abc', }; it('accepts valid units.json', () => { const json = { project_id: 'p-test', prd_rev: '1', sdd_rev: '1', units: [validUnit], l2_rounds: 0, l2_budget: 3, }; const r = docStore.validateUnitsSchema(json); expect(r.ok).toBe(true); }); it('rejects invalid unit id', () => { const json = { project_id: 'p', units: [{ ...validUnit, id: 'AuthBE' }], }; const r = docStore.validateUnitsSchema(json); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('naming'); }); it('rejects duplicate ids', () => { const json = { project_id: 'p', units: [validUnit, validUnit], }; const r = docStore.validateUnitsSchema(json); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('duplicate'); }); it('rejects unknown depends_on', () => { const json = { project_id: 'p', units: [{ ...validUnit, depends_on: ['ghost-be'] }], }; const r = docStore.validateUnitsSchema(json); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('unknown unit'); }); it('detects cycle', () => { const a = { id: 'a-be', type: 'be', status: 'pending', depends_on: ['b-be'], retries: 0, retry_budget: 5, contract_hash: 'sha256:1' }; const b = { id: 'b-be', type: 'be', status: 'pending', depends_on: ['a-be'], retries: 0, retry_budget: 5, contract_hash: 'sha256:2' }; const json = { project_id: 'p', units: [a, b] }; const r = docStore.validateUnitsSchema(json); expect(r.ok).toBe(false); expect(r.errors.join(' ')).toContain('cycle'); }); }); describe('hashContract', () => { it('deterministic for same content', () => { const c = { unit_id: 'x', exports: { http: [{ path: '/x' }] } }; const h1 = docStore.hashContract(c); const h2 = docStore.hashContract(c); expect(h1).toBe(h2); expect(h1).toMatch(/^sha256:[a-f0-9]{16}$/); }); it('different content → different hash', () => { const c1 = { unit_id: 'x', exports: { http: [{ path: '/x' }] } }; const c2 = { unit_id: 'x', exports: { http: [{ path: '/y' }] } }; expect(docStore.hashContract(c1)).not.toBe(docStore.hashContract(c2)); }); it('canonical: key ordering does not affect hash', () => { const c1 = { exports: { http: [{ a: 1, b: 2 }] } }; const c2 = { exports: { http: [{ b: 2, a: 1 }] } }; expect(docStore.hashContract(c1)).toBe(docStore.hashContract(c2)); }); }); });