import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { resolve, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { readFileSync, mkdtempSync } from 'node:fs'; import { tmpdir } from 'node:os'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const CLI = resolve(__dirname, '../../scanners/knowledge-refresh-cli.mjs'); // The bundled register's seed entries are all verified 2026-06-20, so a reference // date of 2026-06-21 makes every entry exactly 1 day old — deterministic regardless // of the real clock. We exploit that to exercise both the fresh and stale branches. const REF = '2026-06-21'; function runCli(extraArgs) { try { const stdout = execFileSync('node', [CLI, ...extraArgs], { encoding: 'utf-8', timeout: 15000 }); return { status: 0, stdout }; } catch (err) { return { status: err.status, stdout: err.stdout || '', stderr: err.stderr || '' }; } } describe('knowledge-refresh-cli — exit codes', () => { it('exits 0 when every entry is fresh', () => { const { status, stdout } = runCli(['--reference-date', REF, '--stale-after', '90']); assert.equal(status, 0); const out = JSON.parse(stdout); assert.equal(out.counts.stale, 0); }); it('exits 1 (advisory) when one or more entries are stale', () => { // stale-after 0 → anything verified before the reference date is stale. const { status, stdout } = runCli(['--reference-date', REF, '--stale-after', '0']); assert.equal(status, 1); const out = JSON.parse(stdout); assert.ok(out.counts.stale > 0); assert.equal(out.counts.fresh, 0); }); it('exits 3 on an invalid --stale-after', () => { assert.equal(runCli(['--stale-after', 'abc']).status, 3); assert.equal(runCli(['--stale-after', '-5']).status, 3); }); it('exits 3 on a malformed --reference-date', () => { assert.equal(runCli(['--reference-date', '20-06-2026']).status, 3); }); }); describe('knowledge-refresh-cli — payload shape', () => { it('emits the expected keys + a consistent count triple', () => { const { stdout } = runCli(['--reference-date', REF, '--stale-after', '90']); const out = JSON.parse(stdout); assert.equal(out.status, 'ok'); assert.match(out.registerPath, /knowledge\/best-practices\.json$/); assert.equal(out.referenceDate, '2026-06-21'); assert.equal(out.staleAfterDays, 90); assert.ok(Array.isArray(out.stale)); assert.ok(Array.isArray(out.fresh)); assert.equal(out.counts.total, out.counts.stale + out.counts.fresh); assert.ok(out.counts.total > 0, 'bundled register is non-empty'); }); it('is always dryRun:true regardless of the flag, and echoes the request', () => { const without = JSON.parse(runCli(['--reference-date', REF]).stdout); assert.equal(without.dryRun, true); assert.equal(without.requestedDryRun, false); const withFlag = JSON.parse(runCli(['--reference-date', REF, '--dry-run']).stdout); assert.equal(withFlag.dryRun, true); assert.equal(withFlag.requestedDryRun, true); }); it('stale entries carry provenance (id, verified, ageDays, url, claim)', () => { const out = JSON.parse(runCli(['--reference-date', REF, '--stale-after', '0']).stdout); const s = out.stale[0]; assert.match(s.id, /^BP-[A-Z]+-\d{3}$/); assert.match(s.verified, /^\d{4}-\d{2}-\d{2}$/); assert.equal(s.ageDays, 1); assert.ok(s.url && s.claim); }); }); describe('knowledge-refresh-cli — --output-file', () => { it('writes JSON to the file and not to stdout', () => { const dir = mkdtempSync(join(tmpdir(), 'kr-cli-')); const out = join(dir, 'refresh.json'); const { stdout } = runCli(['--reference-date', REF, '--stale-after', '90', '--output-file', out]); assert.equal(stdout.trim(), '', 'stdout is silent when --output-file is given'); const written = JSON.parse(readFileSync(out, 'utf-8')); assert.equal(written.status, 'ok'); assert.equal(written.counts.stale, 0); }); });