doc-store.test.js
4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* 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('<p>hi</p>', 'prd');
expect(r.ok).toBe(false);
expect(r.errors.join(' ')).toContain('article');
});
it('rejects wrong doc kind', () => {
const r = docStore.validateHTML('<article data-doc="sdd" data-rev="1"><section><h3>x</h3><p>y</p></section></article>', 'prd');
expect(r.ok).toBe(false);
});
it('accepts valid PRD chapter', () => {
const html = `<article data-doc="prd" data-rev="2">
<section data-rev="1"><h3>Title</h3><p>body</p></section>
<section data-rev="2"><h3>Other</h3><p>more</p></section>
</article>`;
const r = docStore.validateHTML(html, 'prd');
expect(r.ok).toBe(true);
});
it('detects section without h3', () => {
const html = `<article data-doc="prd" data-rev="1">
<section><p>only p</p></section>
</article>`;
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 = `<article data-doc="prd" data-rev="3">
<section data-rev="2"><h3>x</h3><p>y</p></section>
<section data-rev="1"><h3>x</h3><p>y</p></section>
</article>`;
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));
});
});
});