Filesystem counts are the source of truth; README badges parsed via
line-anchored substring (badge/<kind>-<N>-...). Emits readmeCheck object
with counts/badges/mismatches.
CLI: node scanners/self-audit.mjs --check-readme [--json]
API: runSelfAudit({ checkReadme: true }) → result.readmeCheck
Helper: checkReadmeBadges(pluginDir) for per-fixture testing
New fixture: readme-desynced/ (commands/foo + bar, README claims 1).
Note: alpha phase does NOT require result.readmeCheck.passed === true.
Self-test of real plugin currently fails (scanners 10 vs 9, tests 31 vs 543);
will be reconciled in Session 5 Step 28 (README sync).
582 → 586 tests, all green.
134 lines
5 KiB
JavaScript
134 lines
5 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { runSelfAudit, formatSelfAudit, checkReadmeBadges } from '../../scanners/self-audit.mjs';
|
|
|
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
const FIXTURES = resolve(__dirname, '../fixtures');
|
|
|
|
// ========================================
|
|
// runSelfAudit
|
|
// ========================================
|
|
describe('runSelfAudit', () => {
|
|
it('runs without crash', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.ok(result);
|
|
assert.ok(typeof result.configGrade === 'string');
|
|
assert.ok(typeof result.pluginGrade === 'string');
|
|
});
|
|
|
|
it('returns combined results (scanners + plugin health)', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.ok(result.configEnvelope);
|
|
assert.ok(result.pluginHealthResult);
|
|
assert.ok(Array.isArray(result.allFindings));
|
|
});
|
|
|
|
it('has valid exit code', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.ok([0, 1, 2].includes(result.exitCode));
|
|
});
|
|
|
|
it('includes verdict', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.ok(['PASS', 'WARN', 'FAIL'].includes(result.verdict));
|
|
});
|
|
|
|
it('has numeric scores', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.ok(typeof result.configScore === 'number');
|
|
assert.ok(typeof result.pluginScore === 'number');
|
|
assert.ok(result.configScore >= 0 && result.configScore <= 100);
|
|
assert.ok(result.pluginScore >= 0 && result.pluginScore <= 100);
|
|
});
|
|
|
|
it('points to correct plugin directory', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.ok(result.pluginDir.includes('config-audit'));
|
|
});
|
|
});
|
|
|
|
// ========================================
|
|
// fixture filtering delegation
|
|
// ========================================
|
|
describe('runSelfAudit — fixture filtering', () => {
|
|
it('does not include fixture findings in allFindings', async () => {
|
|
const result = await runSelfAudit();
|
|
for (const f of result.allFindings) {
|
|
const p = f.file || f.path || f.location || '';
|
|
assert.ok(
|
|
!p.includes('/tests/fixtures/'),
|
|
`allFindings should not contain fixture paths: ${p}`
|
|
);
|
|
}
|
|
});
|
|
|
|
it('configEnvelope has fixture_findings from orchestrator', async () => {
|
|
const result = await runSelfAudit();
|
|
// The orchestrator filters fixtures and attaches them to the envelope
|
|
if (result.configEnvelope.fixture_findings) {
|
|
assert.ok(Array.isArray(result.configEnvelope.fixture_findings));
|
|
assert.ok(result.configEnvelope.fixture_findings.length > 0);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ========================================
|
|
// --check-readme (v5 F6)
|
|
// ========================================
|
|
describe('checkReadmeBadges (v5 F6)', () => {
|
|
it('detects mismatch in readme-desynced fixture', async () => {
|
|
const path = resolve(FIXTURES, 'readme-desynced');
|
|
const result = await checkReadmeBadges(path);
|
|
assert.equal(typeof result.passed, 'boolean');
|
|
assert.equal(result.passed, false, 'expected mismatch');
|
|
const cmd = result.mismatches.find(m => m.kind === 'commands');
|
|
assert.ok(cmd, `expected commands mismatch; got: ${JSON.stringify(result.mismatches)}`);
|
|
assert.equal(cmd.expected, 2, `filesystem count should be 2`);
|
|
assert.equal(cmd.foundInReadme, 1, `README badge claims 1`);
|
|
});
|
|
|
|
it('returns counts and badges objects', async () => {
|
|
const path = resolve(FIXTURES, 'readme-desynced');
|
|
const result = await checkReadmeBadges(path);
|
|
assert.equal(typeof result.counts, 'object');
|
|
assert.equal(typeof result.badges, 'object');
|
|
assert.equal(result.counts.commands, 2);
|
|
assert.equal(result.badges.commands, 1);
|
|
});
|
|
});
|
|
|
|
describe('runSelfAudit({ checkReadme: true }) (v5 F6)', () => {
|
|
it('attaches readmeCheck object to the result', async () => {
|
|
const result = await runSelfAudit({ checkReadme: true });
|
|
assert.ok(result.readmeCheck, 'expected result.readmeCheck');
|
|
assert.equal(typeof result.readmeCheck.passed, 'boolean');
|
|
// Do NOT assert passed === true during alpha/beta phases — see plan Step 16.
|
|
});
|
|
|
|
it('omits readmeCheck when flag not set', async () => {
|
|
const result = await runSelfAudit();
|
|
assert.equal(result.readmeCheck, undefined);
|
|
});
|
|
});
|
|
|
|
// ========================================
|
|
// formatSelfAudit
|
|
// ========================================
|
|
describe('formatSelfAudit', () => {
|
|
it('produces terminal output with Self-Audit header', async () => {
|
|
const result = await runSelfAudit();
|
|
const output = formatSelfAudit(result);
|
|
assert.ok(output.includes('Self-Audit'));
|
|
assert.ok(output.includes('Plugin health:'));
|
|
assert.ok(output.includes('Config quality:'));
|
|
});
|
|
|
|
it('includes verdict', async () => {
|
|
const result = await runSelfAudit();
|
|
const output = formatSelfAudit(result);
|
|
assert.ok(output.includes('Self-audit:'));
|
|
assert.ok(output.includes('PASS') || output.includes('WARN') || output.includes('FAIL'));
|
|
});
|
|
});
|