176 lines
5.3 KiB
JavaScript
176 lines
5.3 KiB
JavaScript
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { join } from 'node:path';
|
|
import { rm, stat, readFile, mkdir } from 'node:fs/promises';
|
|
import { tmpdir, homedir } from 'node:os';
|
|
import {
|
|
saveBaseline,
|
|
loadBaseline,
|
|
listBaselines,
|
|
deleteBaseline,
|
|
getBaselinesDir,
|
|
} from '../../scanners/lib/baseline.mjs';
|
|
|
|
// We test against the real baselines dir but use unique names to avoid collisions.
|
|
const TEST_PREFIX = `_test_${Date.now()}_`;
|
|
|
|
function makeTestEnvelope(findingCount = 3) {
|
|
const findings = Array.from({ length: findingCount }, (_, i) => ({
|
|
id: `CA-CML-${String(i + 1).padStart(3, '0')}`,
|
|
scanner: 'CML',
|
|
severity: 'low',
|
|
title: `Finding ${i + 1}`,
|
|
file: 'CLAUDE.md',
|
|
}));
|
|
return {
|
|
meta: { target: '/test/path', timestamp: new Date().toISOString(), version: '2.0.0', tool: 'config-audit' },
|
|
scanners: [{
|
|
scanner: 'CML',
|
|
status: 'ok',
|
|
files_scanned: 1,
|
|
duration_ms: 5,
|
|
findings,
|
|
counts: { critical: 0, high: 0, medium: 0, low: findingCount, info: 0 },
|
|
}],
|
|
aggregate: { total_findings: findingCount, counts: { critical: 0, high: 0, medium: 0, low: findingCount, info: 0 }, risk_score: findingCount, risk_band: 'Low', verdict: 'PASS', scanners_ok: 1, scanners_error: 0, scanners_skipped: 0 },
|
|
};
|
|
}
|
|
|
|
// Cleanup helper
|
|
const savedNames = [];
|
|
async function cleanup() {
|
|
for (const name of savedNames) {
|
|
await deleteBaseline(name);
|
|
}
|
|
savedNames.length = 0;
|
|
}
|
|
|
|
describe('saveBaseline', () => {
|
|
afterEach(cleanup);
|
|
|
|
it('writes a file and returns path', async () => {
|
|
const name = `${TEST_PREFIX}save1`;
|
|
savedNames.push(name);
|
|
|
|
const envelope = makeTestEnvelope(2);
|
|
const result = await saveBaseline(envelope, name);
|
|
|
|
assert.ok(result.path.endsWith(`${name}.json`));
|
|
assert.equal(result.name, name);
|
|
|
|
// Verify file exists
|
|
const s = await stat(result.path);
|
|
assert.ok(s.isFile());
|
|
});
|
|
|
|
it('includes _baseline metadata', async () => {
|
|
const name = `${TEST_PREFIX}meta`;
|
|
savedNames.push(name);
|
|
|
|
const envelope = makeTestEnvelope(5);
|
|
await saveBaseline(envelope, name);
|
|
|
|
const loaded = await loadBaseline(name);
|
|
assert.ok(loaded._baseline);
|
|
assert.ok(loaded._baseline.saved_at);
|
|
assert.equal(loaded._baseline.target_path, '/test/path');
|
|
assert.equal(loaded._baseline.finding_count, 5);
|
|
assert.equal(typeof loaded._baseline.score, 'number');
|
|
});
|
|
|
|
it('defaults name to "default"', async () => {
|
|
const name = `${TEST_PREFIX}default_test`;
|
|
savedNames.push(name);
|
|
// We won't actually use the literal 'default' to avoid interfering with real baselines
|
|
const result = await saveBaseline(makeTestEnvelope(), name);
|
|
assert.equal(result.name, name);
|
|
});
|
|
|
|
it('overwrites existing baseline with same name', async () => {
|
|
const name = `${TEST_PREFIX}overwrite`;
|
|
savedNames.push(name);
|
|
|
|
await saveBaseline(makeTestEnvelope(2), name);
|
|
await saveBaseline(makeTestEnvelope(7), name);
|
|
|
|
const loaded = await loadBaseline(name);
|
|
assert.equal(loaded._baseline.finding_count, 7);
|
|
});
|
|
});
|
|
|
|
describe('loadBaseline', () => {
|
|
afterEach(cleanup);
|
|
|
|
it('loads a previously saved baseline', async () => {
|
|
const name = `${TEST_PREFIX}load`;
|
|
savedNames.push(name);
|
|
|
|
const envelope = makeTestEnvelope(3);
|
|
await saveBaseline(envelope, name);
|
|
|
|
const loaded = await loadBaseline(name);
|
|
assert.ok(loaded);
|
|
assert.equal(loaded.aggregate.total_findings, 3);
|
|
assert.equal(loaded.meta.target, '/test/path');
|
|
});
|
|
|
|
it('returns null for unknown name', async () => {
|
|
const result = await loadBaseline(`${TEST_PREFIX}nonexistent_${Date.now()}`);
|
|
assert.equal(result, null);
|
|
});
|
|
|
|
it('preserves all scanner data', async () => {
|
|
const name = `${TEST_PREFIX}preserve`;
|
|
savedNames.push(name);
|
|
|
|
const envelope = makeTestEnvelope(1);
|
|
await saveBaseline(envelope, name);
|
|
|
|
const loaded = await loadBaseline(name);
|
|
assert.equal(loaded.scanners.length, 1);
|
|
assert.equal(loaded.scanners[0].scanner, 'CML');
|
|
assert.equal(loaded.scanners[0].findings.length, 1);
|
|
});
|
|
});
|
|
|
|
describe('listBaselines', () => {
|
|
afterEach(cleanup);
|
|
|
|
it('lists saved baselines', async () => {
|
|
const name = `${TEST_PREFIX}list`;
|
|
savedNames.push(name);
|
|
|
|
await saveBaseline(makeTestEnvelope(4), name);
|
|
|
|
const result = await listBaselines();
|
|
assert.ok(Array.isArray(result.baselines));
|
|
const found = result.baselines.find(b => b.name === name);
|
|
assert.ok(found, 'Should find the saved baseline in list');
|
|
assert.equal(found.findingCount, 4);
|
|
assert.ok(found.savedAt);
|
|
});
|
|
|
|
it('returns empty array when no baselines', async () => {
|
|
// This test just verifies the function doesn't crash
|
|
const result = await listBaselines();
|
|
assert.ok(Array.isArray(result.baselines));
|
|
});
|
|
});
|
|
|
|
describe('deleteBaseline', () => {
|
|
it('deletes an existing baseline', async () => {
|
|
const name = `${TEST_PREFIX}delete`;
|
|
|
|
await saveBaseline(makeTestEnvelope(), name);
|
|
const result = await deleteBaseline(name);
|
|
assert.equal(result.deleted, true);
|
|
|
|
const loaded = await loadBaseline(name);
|
|
assert.equal(loaded, null);
|
|
});
|
|
|
|
it('returns false for non-existent baseline', async () => {
|
|
const result = await deleteBaseline(`${TEST_PREFIX}nope_${Date.now()}`);
|
|
assert.equal(result.deleted, false);
|
|
});
|
|
});
|