@unrdf/diataxis-kit 26.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +425 -0
- package/bin/report.mjs +529 -0
- package/bin/run.mjs +114 -0
- package/bin/verify.mjs +356 -0
- package/capability-map.md +92 -0
- package/package.json +42 -0
- package/src/classify.mjs +584 -0
- package/src/diataxis-schema.mjs +425 -0
- package/src/evidence.mjs +268 -0
- package/src/hash.mjs +37 -0
- package/src/inventory.mjs +280 -0
- package/src/reference-extractor.mjs +324 -0
- package/src/scaffold.mjs +458 -0
- package/src/stable-json.mjs +113 -0
- package/src/verify-implementation.mjs +131 -0
- package/test/determinism.test.mjs +321 -0
- package/test/evidence.test.mjs +145 -0
- package/test/fixtures/scaffold-det1/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-det1/index.md +29 -0
- package/test/fixtures/scaffold-det1/reference/reference.md +34 -0
- package/test/fixtures/scaffold-det1/tutorials/tutorial-test-tutorial.md +37 -0
- package/test/fixtures/scaffold-det2/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-det2/index.md +29 -0
- package/test/fixtures/scaffold-det2/reference/reference.md +34 -0
- package/test/fixtures/scaffold-det2/tutorials/tutorial-test-tutorial.md +37 -0
- package/test/fixtures/scaffold-empty/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-empty/index.md +25 -0
- package/test/fixtures/scaffold-empty/reference/reference.md +34 -0
- package/test/fixtures/scaffold-escape/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-escape/index.md +29 -0
- package/test/fixtures/scaffold-escape/reference/reference.md +36 -0
- package/test/fixtures/scaffold-output/explanation/explanation.md +39 -0
- package/test/fixtures/scaffold-output/how-to/howto-configure-options.md +39 -0
- package/test/fixtures/scaffold-output/index.md +41 -0
- package/test/fixtures/scaffold-output/reference/reference.md +36 -0
- package/test/fixtures/scaffold-output/tutorials/tutorial-getting-started.md +41 -0
- package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-1.inventory.json +115 -0
- package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-2.inventory.json +93 -0
- package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-3.inventory.json +97 -0
- package/test/fixtures/test-package/LICENSE +1 -0
- package/test/fixtures/test-package/README.md +15 -0
- package/test/fixtures/test-package/docs/guide.md +3 -0
- package/test/fixtures/test-package/examples/basic.mjs +3 -0
- package/test/fixtures/test-package/src/index.mjs +3 -0
- package/test/inventory.test.mjs +199 -0
- package/test/reference-extractor.test.mjs +187 -0
- package/test/report.test.mjs +503 -0
- package/test/scaffold.test.mjs +242 -0
- package/test/verify-gate.test.mjs +634 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for Diátaxis verification gate
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { mkdir, writeFile, rm } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { execFile } from 'node:child_process';
|
|
11
|
+
import { promisify } from 'node:util';
|
|
12
|
+
import { main, verifyPackage, isReferencPopulated, isExplanationPopulated } from '../bin/verify.mjs';
|
|
13
|
+
import { stableStringify } from '../src/stable-json.mjs';
|
|
14
|
+
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a test workspace with inventory and package files
|
|
19
|
+
* @param {string} testDir - Test directory path
|
|
20
|
+
* @param {Object} config - Configuration
|
|
21
|
+
* @param {Array} config.packages - Array of package configurations
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
async function createTestWorkspace(testDir, config) {
|
|
25
|
+
const artifactsDir = join(testDir, 'ARTIFACTS', 'diataxis');
|
|
26
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
// Create inventory.json
|
|
29
|
+
const inventory = {
|
|
30
|
+
packages: config.packages.map(pkg => ({
|
|
31
|
+
name: pkg.name,
|
|
32
|
+
version: pkg.version || '1.0.0'
|
|
33
|
+
}))
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(artifactsDir, 'inventory.json'),
|
|
38
|
+
stableStringify(inventory)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Create per-package diataxis.json files
|
|
42
|
+
for (const pkg of config.packages) {
|
|
43
|
+
const packageDir = join(artifactsDir, pkg.name);
|
|
44
|
+
await mkdir(packageDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
if (pkg.diataxis) {
|
|
47
|
+
await writeFile(
|
|
48
|
+
join(packageDir, 'diataxis.json'),
|
|
49
|
+
stableStringify(pkg.diataxis)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a valid diataxis entry
|
|
57
|
+
* @param {string} packageName - Package name
|
|
58
|
+
* @param {Object} overrides - Override default values
|
|
59
|
+
* @returns {Object} Diataxis entry
|
|
60
|
+
*/
|
|
61
|
+
function createDiataxisEntry(packageName, overrides = {}) {
|
|
62
|
+
return {
|
|
63
|
+
packageName,
|
|
64
|
+
version: '1.0.0',
|
|
65
|
+
generatedAt: '2000-01-01T00:00:00.000Z',
|
|
66
|
+
confidence: {
|
|
67
|
+
tutorials: 0.8,
|
|
68
|
+
howtos: 0.9,
|
|
69
|
+
reference: 0.95,
|
|
70
|
+
explanation: 0.85
|
|
71
|
+
},
|
|
72
|
+
tutorials: [
|
|
73
|
+
{
|
|
74
|
+
id: 'getting-started',
|
|
75
|
+
title: 'Getting Started',
|
|
76
|
+
goal: 'Learn the basics',
|
|
77
|
+
prerequisites: [],
|
|
78
|
+
stepsOutline: ['Step 1', 'Step 2'],
|
|
79
|
+
confidenceScore: 0.8,
|
|
80
|
+
source: ['README.md']
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
howtos: [
|
|
84
|
+
{
|
|
85
|
+
id: 'install',
|
|
86
|
+
title: 'How to Install',
|
|
87
|
+
task: 'Install the package',
|
|
88
|
+
context: 'Setup',
|
|
89
|
+
steps: ['npm install'],
|
|
90
|
+
confidenceScore: 0.9,
|
|
91
|
+
source: ['README.md']
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'configure',
|
|
95
|
+
title: 'How to Configure',
|
|
96
|
+
task: 'Configure the package',
|
|
97
|
+
context: 'Setup',
|
|
98
|
+
steps: ['Edit config'],
|
|
99
|
+
confidenceScore: 0.85,
|
|
100
|
+
source: ['README.md']
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
reference: {
|
|
104
|
+
id: 'reference',
|
|
105
|
+
title: `${packageName} Reference`,
|
|
106
|
+
items: [
|
|
107
|
+
{
|
|
108
|
+
name: 'someFunction',
|
|
109
|
+
type: 'export',
|
|
110
|
+
description: 'A function',
|
|
111
|
+
example: 'someFunction()'
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
confidenceScore: 0.95,
|
|
115
|
+
source: ['src/index.js']
|
|
116
|
+
},
|
|
117
|
+
explanation: {
|
|
118
|
+
id: 'explanation',
|
|
119
|
+
title: `${packageName} Explanation`,
|
|
120
|
+
concepts: ['Core concept'],
|
|
121
|
+
architecture: 'Simple architecture',
|
|
122
|
+
tradeoffs: ['Tradeoff 1'],
|
|
123
|
+
confidenceScore: 0.85,
|
|
124
|
+
source: ['README.md']
|
|
125
|
+
},
|
|
126
|
+
evidence: {
|
|
127
|
+
readmeHeadings: ['Installation', 'Usage'],
|
|
128
|
+
docsFiles: ['README.md'],
|
|
129
|
+
examplesFiles: [],
|
|
130
|
+
fingerprint: 'abc123'
|
|
131
|
+
},
|
|
132
|
+
...overrides
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
describe('Diátaxis Verification Gate', () => {
|
|
137
|
+
it('Test 1: Pass when all packages have required stubs', async () => {
|
|
138
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-1`);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await createTestWorkspace(testDir, {
|
|
142
|
+
packages: [
|
|
143
|
+
{
|
|
144
|
+
name: '@unrdf/package-a',
|
|
145
|
+
diataxis: createDiataxisEntry('@unrdf/package-a')
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: '@unrdf/package-b',
|
|
149
|
+
diataxis: createDiataxisEntry('@unrdf/package-b')
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Run verification from test directory
|
|
155
|
+
const originalCwd = process.cwd();
|
|
156
|
+
try {
|
|
157
|
+
process.chdir(testDir);
|
|
158
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
159
|
+
assert.equal(exitCode, 0, 'Should exit with 0 when all packages pass');
|
|
160
|
+
} finally {
|
|
161
|
+
process.chdir(originalCwd);
|
|
162
|
+
}
|
|
163
|
+
} finally {
|
|
164
|
+
await rm(testDir, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('Test 2: Fail when a package missing tutorials', async () => {
|
|
169
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-2`);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await createTestWorkspace(testDir, {
|
|
173
|
+
packages: [
|
|
174
|
+
{
|
|
175
|
+
name: '@unrdf/package-a',
|
|
176
|
+
diataxis: createDiataxisEntry('@unrdf/package-a', {
|
|
177
|
+
tutorials: [] // Missing tutorials
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const originalCwd = process.cwd();
|
|
184
|
+
try {
|
|
185
|
+
process.chdir(testDir);
|
|
186
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
187
|
+
assert.equal(exitCode, 1, 'Should exit with 1 when tutorials missing');
|
|
188
|
+
} finally {
|
|
189
|
+
process.chdir(originalCwd);
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
await rm(testDir, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('Test 3: Fail when a package missing 2+ how-tos', async () => {
|
|
197
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-3`);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await createTestWorkspace(testDir, {
|
|
201
|
+
packages: [
|
|
202
|
+
{
|
|
203
|
+
name: '@unrdf/package-a',
|
|
204
|
+
diataxis: createDiataxisEntry('@unrdf/package-a', {
|
|
205
|
+
howtos: [
|
|
206
|
+
{
|
|
207
|
+
id: 'install',
|
|
208
|
+
title: 'How to Install',
|
|
209
|
+
task: 'Install',
|
|
210
|
+
context: 'Setup',
|
|
211
|
+
steps: [],
|
|
212
|
+
confidenceScore: 0.9,
|
|
213
|
+
source: []
|
|
214
|
+
}
|
|
215
|
+
] // Only 1 how-to, need 2
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const originalCwd = process.cwd();
|
|
222
|
+
try {
|
|
223
|
+
process.chdir(testDir);
|
|
224
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
225
|
+
assert.equal(exitCode, 1, 'Should exit with 1 when how-tos < 2');
|
|
226
|
+
} finally {
|
|
227
|
+
process.chdir(originalCwd);
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
await rm(testDir, { recursive: true, force: true });
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('Test 4: Fail when missing reference', async () => {
|
|
235
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-4`);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await createTestWorkspace(testDir, {
|
|
239
|
+
packages: [
|
|
240
|
+
{
|
|
241
|
+
name: '@unrdf/package-a',
|
|
242
|
+
diataxis: createDiataxisEntry('@unrdf/package-a', {
|
|
243
|
+
reference: {
|
|
244
|
+
id: 'reference',
|
|
245
|
+
title: 'Reference',
|
|
246
|
+
items: [], // Empty items
|
|
247
|
+
confidenceScore: 0,
|
|
248
|
+
source: []
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const originalCwd = process.cwd();
|
|
256
|
+
try {
|
|
257
|
+
process.chdir(testDir);
|
|
258
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
259
|
+
assert.equal(exitCode, 1, 'Should exit with 1 when reference empty');
|
|
260
|
+
} finally {
|
|
261
|
+
process.chdir(originalCwd);
|
|
262
|
+
}
|
|
263
|
+
} finally {
|
|
264
|
+
await rm(testDir, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('Test 5: Fail when missing explanation', async () => {
|
|
269
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-5`);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
await createTestWorkspace(testDir, {
|
|
273
|
+
packages: [
|
|
274
|
+
{
|
|
275
|
+
name: '@unrdf/package-a',
|
|
276
|
+
diataxis: createDiataxisEntry('@unrdf/package-a', {
|
|
277
|
+
explanation: {
|
|
278
|
+
id: 'explanation',
|
|
279
|
+
title: 'Explanation',
|
|
280
|
+
concepts: [],
|
|
281
|
+
architecture: '',
|
|
282
|
+
tradeoffs: [],
|
|
283
|
+
confidenceScore: 0,
|
|
284
|
+
source: []
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const originalCwd = process.cwd();
|
|
292
|
+
try {
|
|
293
|
+
process.chdir(testDir);
|
|
294
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
295
|
+
assert.equal(exitCode, 1, 'Should exit with 1 when explanation empty');
|
|
296
|
+
} finally {
|
|
297
|
+
process.chdir(originalCwd);
|
|
298
|
+
}
|
|
299
|
+
} finally {
|
|
300
|
+
await rm(testDir, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('Test 6: JSON output mode', async () => {
|
|
305
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-6`);
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
await createTestWorkspace(testDir, {
|
|
309
|
+
packages: [
|
|
310
|
+
{
|
|
311
|
+
name: '@unrdf/package-a',
|
|
312
|
+
diataxis: createDiataxisEntry('@unrdf/package-a')
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: '@unrdf/package-b',
|
|
316
|
+
diataxis: createDiataxisEntry('@unrdf/package-b', {
|
|
317
|
+
tutorials: []
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Capture stdout
|
|
324
|
+
const originalCwd = process.cwd();
|
|
325
|
+
const originalLog = console.log;
|
|
326
|
+
let output = '';
|
|
327
|
+
console.log = (msg) => { output += msg; };
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
process.chdir(testDir);
|
|
331
|
+
const exitCode = await main({ json: true, failFast: false, threshold: 0 });
|
|
332
|
+
|
|
333
|
+
assert.equal(exitCode, 1, 'Should exit with 1');
|
|
334
|
+
|
|
335
|
+
// Parse JSON output
|
|
336
|
+
const result = JSON.parse(output);
|
|
337
|
+
assert.equal(result.total, 2, 'Should report 2 total packages');
|
|
338
|
+
assert.equal(result.passing, 1, 'Should report 1 passing');
|
|
339
|
+
assert.equal(result.failing, 1, 'Should report 1 failing');
|
|
340
|
+
assert.equal(result.exitCode, 1, 'Should include exit code');
|
|
341
|
+
assert.ok(Array.isArray(result.failures), 'Should include failures array');
|
|
342
|
+
assert.equal(result.failures.length, 1, 'Should have 1 failure');
|
|
343
|
+
assert.equal(result.failures[0].package, '@unrdf/package-b', 'Should identify failing package');
|
|
344
|
+
} finally {
|
|
345
|
+
console.log = originalLog;
|
|
346
|
+
process.chdir(originalCwd);
|
|
347
|
+
}
|
|
348
|
+
} finally {
|
|
349
|
+
await rm(testDir, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('Test 7: --threshold option', async () => {
|
|
354
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-7`);
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
await createTestWorkspace(testDir, {
|
|
358
|
+
packages: [
|
|
359
|
+
{
|
|
360
|
+
name: '@unrdf/package-a',
|
|
361
|
+
diataxis: createDiataxisEntry('@unrdf/package-a', {
|
|
362
|
+
tutorials: []
|
|
363
|
+
})
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: '@unrdf/package-b',
|
|
367
|
+
diataxis: createDiataxisEntry('@unrdf/package-b')
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const originalCwd = process.cwd();
|
|
373
|
+
try {
|
|
374
|
+
process.chdir(testDir);
|
|
375
|
+
|
|
376
|
+
// With threshold = 0, should fail (1 failure > 0)
|
|
377
|
+
let exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
378
|
+
assert.equal(exitCode, 1, 'Should fail with threshold 0');
|
|
379
|
+
|
|
380
|
+
// With threshold = 1, should pass (1 failure <= 1)
|
|
381
|
+
exitCode = await main({ json: false, failFast: false, threshold: 1 });
|
|
382
|
+
assert.equal(exitCode, 0, 'Should pass with threshold 1');
|
|
383
|
+
|
|
384
|
+
// With threshold = 2, should pass (1 failure <= 2)
|
|
385
|
+
exitCode = await main({ json: false, failFast: false, threshold: 2 });
|
|
386
|
+
assert.equal(exitCode, 0, 'Should pass with threshold 2');
|
|
387
|
+
} finally {
|
|
388
|
+
process.chdir(originalCwd);
|
|
389
|
+
}
|
|
390
|
+
} finally {
|
|
391
|
+
await rm(testDir, { recursive: true, force: true });
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('Unit test: verifyPackage function', () => {
|
|
396
|
+
// Valid package
|
|
397
|
+
const validEntry = createDiataxisEntry('test-pkg');
|
|
398
|
+
const result1 = verifyPackage('test-pkg', validEntry);
|
|
399
|
+
assert.equal(result1.passing, true, 'Valid entry should pass');
|
|
400
|
+
assert.equal(result1.failures.length, 0, 'Valid entry should have no failures');
|
|
401
|
+
|
|
402
|
+
// Missing tutorials
|
|
403
|
+
const noTutorials = createDiataxisEntry('test-pkg', { tutorials: [] });
|
|
404
|
+
const result2 = verifyPackage('test-pkg', noTutorials);
|
|
405
|
+
assert.equal(result2.passing, false, 'Should fail without tutorials');
|
|
406
|
+
assert.ok(result2.failures.some(f => f.includes('tutorials')), 'Should mention tutorials');
|
|
407
|
+
|
|
408
|
+
// Not enough how-tos
|
|
409
|
+
const oneHowto = createDiataxisEntry('test-pkg', {
|
|
410
|
+
howtos: [
|
|
411
|
+
{
|
|
412
|
+
id: 'install',
|
|
413
|
+
title: 'Install',
|
|
414
|
+
task: 'Install',
|
|
415
|
+
context: 'Setup',
|
|
416
|
+
steps: [],
|
|
417
|
+
confidenceScore: 0.9,
|
|
418
|
+
source: []
|
|
419
|
+
}
|
|
420
|
+
]
|
|
421
|
+
});
|
|
422
|
+
const result3 = verifyPackage('test-pkg', oneHowto);
|
|
423
|
+
assert.equal(result3.passing, false, 'Should fail with only 1 how-to');
|
|
424
|
+
assert.ok(result3.failures.some(f => f.includes('how-tos')), 'Should mention how-tos');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('Unit test: isReferencePopulated function', () => {
|
|
428
|
+
// Reference with items
|
|
429
|
+
assert.equal(
|
|
430
|
+
isReferencPopulated({
|
|
431
|
+
items: [{ name: 'test', type: 'export', description: 'test' }],
|
|
432
|
+
confidenceScore: 0.9
|
|
433
|
+
}),
|
|
434
|
+
true,
|
|
435
|
+
'Reference with items should be populated'
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Reference with confidence but no items
|
|
439
|
+
assert.equal(
|
|
440
|
+
isReferencPopulated({
|
|
441
|
+
items: [],
|
|
442
|
+
confidenceScore: 0.5
|
|
443
|
+
}),
|
|
444
|
+
true,
|
|
445
|
+
'Reference with confidence > 0 should be populated'
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// Empty reference
|
|
449
|
+
assert.equal(
|
|
450
|
+
isReferencPopulated({
|
|
451
|
+
items: [],
|
|
452
|
+
confidenceScore: 0
|
|
453
|
+
}),
|
|
454
|
+
false,
|
|
455
|
+
'Reference with no items and 0 confidence should not be populated'
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
// Null/undefined
|
|
459
|
+
assert.equal(isReferencPopulated(null), false, 'Null should not be populated');
|
|
460
|
+
assert.equal(isReferencPopulated(undefined), false, 'Undefined should not be populated');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('Unit test: isExplanationPopulated function', () => {
|
|
464
|
+
// Explanation with concepts
|
|
465
|
+
assert.equal(
|
|
466
|
+
isExplanationPopulated({
|
|
467
|
+
concepts: ['Concept 1'],
|
|
468
|
+
architecture: '',
|
|
469
|
+
tradeoffs: [],
|
|
470
|
+
confidenceScore: 0
|
|
471
|
+
}),
|
|
472
|
+
true,
|
|
473
|
+
'Explanation with concepts should be populated'
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Explanation with architecture
|
|
477
|
+
assert.equal(
|
|
478
|
+
isExplanationPopulated({
|
|
479
|
+
concepts: [],
|
|
480
|
+
architecture: 'Some architecture',
|
|
481
|
+
tradeoffs: [],
|
|
482
|
+
confidenceScore: 0
|
|
483
|
+
}),
|
|
484
|
+
true,
|
|
485
|
+
'Explanation with architecture should be populated'
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Explanation with tradeoffs
|
|
489
|
+
assert.equal(
|
|
490
|
+
isExplanationPopulated({
|
|
491
|
+
concepts: [],
|
|
492
|
+
architecture: '',
|
|
493
|
+
tradeoffs: ['Tradeoff 1'],
|
|
494
|
+
confidenceScore: 0
|
|
495
|
+
}),
|
|
496
|
+
true,
|
|
497
|
+
'Explanation with tradeoffs should be populated'
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
// Explanation with confidence but no content
|
|
501
|
+
assert.equal(
|
|
502
|
+
isExplanationPopulated({
|
|
503
|
+
concepts: [],
|
|
504
|
+
architecture: '',
|
|
505
|
+
tradeoffs: [],
|
|
506
|
+
confidenceScore: 0.5
|
|
507
|
+
}),
|
|
508
|
+
true,
|
|
509
|
+
'Explanation with confidence > 0 should be populated'
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
// Empty explanation
|
|
513
|
+
assert.equal(
|
|
514
|
+
isExplanationPopulated({
|
|
515
|
+
concepts: [],
|
|
516
|
+
architecture: '',
|
|
517
|
+
tradeoffs: [],
|
|
518
|
+
confidenceScore: 0
|
|
519
|
+
}),
|
|
520
|
+
false,
|
|
521
|
+
'Explanation with no content should not be populated'
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
// Null/undefined
|
|
525
|
+
assert.equal(isExplanationPopulated(null), false, 'Null should not be populated');
|
|
526
|
+
assert.equal(isExplanationPopulated(undefined), false, 'Undefined should not be populated');
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('Edge case: --fail-fast option', async () => {
|
|
530
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-failfast`);
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
await createTestWorkspace(testDir, {
|
|
534
|
+
packages: [
|
|
535
|
+
{
|
|
536
|
+
name: '@unrdf/package-a',
|
|
537
|
+
diataxis: createDiataxisEntry('@unrdf/package-a', { tutorials: [] })
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: '@unrdf/package-b',
|
|
541
|
+
diataxis: createDiataxisEntry('@unrdf/package-b', { tutorials: [] })
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: '@unrdf/package-c',
|
|
545
|
+
diataxis: createDiataxisEntry('@unrdf/package-c')
|
|
546
|
+
}
|
|
547
|
+
]
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Capture stdout to check behavior
|
|
551
|
+
const originalCwd = process.cwd();
|
|
552
|
+
const originalLog = console.log;
|
|
553
|
+
let output = '';
|
|
554
|
+
console.log = (msg) => { output += msg; };
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
process.chdir(testDir);
|
|
558
|
+
const exitCode = await main({ json: true, failFast: true, threshold: 0 });
|
|
559
|
+
|
|
560
|
+
assert.equal(exitCode, 1, 'Should exit with 1');
|
|
561
|
+
|
|
562
|
+
const result = JSON.parse(output);
|
|
563
|
+
// With fail-fast, should stop after first failure
|
|
564
|
+
// Total should be 1 (only checked package-a)
|
|
565
|
+
assert.equal(result.total, 1, 'Should only check until first failure');
|
|
566
|
+
assert.equal(result.failing, 1, 'Should have 1 failing');
|
|
567
|
+
} finally {
|
|
568
|
+
console.log = originalLog;
|
|
569
|
+
process.chdir(originalCwd);
|
|
570
|
+
}
|
|
571
|
+
} finally {
|
|
572
|
+
await rm(testDir, { recursive: true, force: true });
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('Edge case: Missing diataxis.json file', async () => {
|
|
577
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-missing`);
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
// Create inventory but no diataxis.json files
|
|
581
|
+
const artifactsDir = join(testDir, 'ARTIFACTS', 'diataxis');
|
|
582
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
583
|
+
|
|
584
|
+
await writeFile(
|
|
585
|
+
join(artifactsDir, 'inventory.json'),
|
|
586
|
+
stableStringify({
|
|
587
|
+
packages: [{ name: '@unrdf/package-a', version: '1.0.0' }]
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const originalCwd = process.cwd();
|
|
592
|
+
try {
|
|
593
|
+
process.chdir(testDir);
|
|
594
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
595
|
+
assert.equal(exitCode, 1, 'Should fail when diataxis.json missing');
|
|
596
|
+
} finally {
|
|
597
|
+
process.chdir(originalCwd);
|
|
598
|
+
}
|
|
599
|
+
} finally {
|
|
600
|
+
await rm(testDir, { recursive: true, force: true });
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('Edge case: Invalid JSON in diataxis.json', async () => {
|
|
605
|
+
const testDir = join(tmpdir(), `verify-test-${Date.now()}-invalid`);
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
const artifactsDir = join(testDir, 'ARTIFACTS', 'diataxis');
|
|
609
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
610
|
+
|
|
611
|
+
await writeFile(
|
|
612
|
+
join(artifactsDir, 'inventory.json'),
|
|
613
|
+
stableStringify({
|
|
614
|
+
packages: [{ name: '@unrdf/package-a', version: '1.0.0' }]
|
|
615
|
+
})
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
const packageDir = join(artifactsDir, '@unrdf/package-a');
|
|
619
|
+
await mkdir(packageDir, { recursive: true });
|
|
620
|
+
await writeFile(join(packageDir, 'diataxis.json'), 'invalid json{');
|
|
621
|
+
|
|
622
|
+
const originalCwd = process.cwd();
|
|
623
|
+
try {
|
|
624
|
+
process.chdir(testDir);
|
|
625
|
+
const exitCode = await main({ json: false, failFast: false, threshold: 0 });
|
|
626
|
+
assert.equal(exitCode, 1, 'Should fail when JSON invalid');
|
|
627
|
+
} finally {
|
|
628
|
+
process.chdir(originalCwd);
|
|
629
|
+
}
|
|
630
|
+
} finally {
|
|
631
|
+
await rm(testDir, { recursive: true, force: true });
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
});
|