/**
* 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('', 'prd');
expect(r.ok).toBe(false);
});
it('accepts valid PRD chapter', () => {
const html = `
`;
const r = docStore.validateHTML(html, 'prd');
expect(r.ok).toBe(true);
});
it('detects section without h3', () => {
const html = `
`;
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 = `
`;
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));
});
});
});