172 lines
7.7 KiB
JavaScript
172 lines
7.7 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { resolve, dirname, sep } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { isFixturePath, FIXTURE_DIR_NAMES, runAllScanners } from '../../scanners/scan-orchestrator.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PLUGIN_ROOT = resolve(__dirname, '../..');
|
|
const FIXTURES = resolve(__dirname, '../fixtures');
|
|
|
|
// ========================================
|
|
// isFixturePath
|
|
// ========================================
|
|
describe('isFixturePath', () => {
|
|
const target = '/repo';
|
|
|
|
it('matches tests/ subdirectory relative to target', () => {
|
|
assert.strictEqual(isFixturePath({ file: '/repo/tests/fixtures/CLAUDE.md' }, target), true);
|
|
});
|
|
|
|
it('matches examples/ subdirectory relative to target', () => {
|
|
assert.strictEqual(isFixturePath({ file: '/repo/examples/demo/settings.json' }, target), true);
|
|
});
|
|
|
|
it('matches __tests__/ subdirectory', () => {
|
|
assert.strictEqual(isFixturePath({ file: '/repo/__tests__/config/CLAUDE.md' }, target), true);
|
|
});
|
|
|
|
it('does not match production paths', () => {
|
|
assert.strictEqual(isFixturePath({ file: '/repo/CLAUDE.md' }, target), false);
|
|
assert.strictEqual(isFixturePath({ file: '/repo/plugins/config-audit/CLAUDE.md' }, target), false);
|
|
});
|
|
|
|
it('does not filter when target IS a fixture directory', () => {
|
|
// If we're scanning tests/fixtures/broken-project directly, its files should NOT be filtered
|
|
const fixtureTarget = '/repo/tests/fixtures/broken-project';
|
|
assert.strictEqual(
|
|
isFixturePath({ file: '/repo/tests/fixtures/broken-project/CLAUDE.md' }, fixtureTarget),
|
|
false,
|
|
'Files at the root of the scanned target should not be filtered'
|
|
);
|
|
});
|
|
|
|
it('falls back to path field', () => {
|
|
assert.strictEqual(isFixturePath({ path: '/repo/tests/broken/hooks.json' }, target), true);
|
|
});
|
|
|
|
it('falls back to location field', () => {
|
|
assert.strictEqual(isFixturePath({ location: '/repo/examples/bad.md' }, target), true);
|
|
});
|
|
|
|
it('returns false when file is null (GAP findings)', () => {
|
|
assert.strictEqual(isFixturePath({ file: null }, target), false);
|
|
});
|
|
|
|
it('returns false for empty finding (no file/path/location)', () => {
|
|
assert.strictEqual(isFixturePath({}, target), false);
|
|
});
|
|
|
|
it('returns false when file is outside target path', () => {
|
|
assert.strictEqual(isFixturePath({ file: '/other/tests/foo.md' }, target), false);
|
|
});
|
|
|
|
it('uses platform-native separator (path.sep)', () => {
|
|
// On macOS/Linux sep='/', on Windows sep='\\'
|
|
// This test verifies the function works with the native separator
|
|
const nativePath = `${target}${sep}tests${sep}fixtures${sep}CLAUDE.md`;
|
|
assert.strictEqual(isFixturePath({ file: nativePath }, target), true);
|
|
});
|
|
});
|
|
|
|
// ========================================
|
|
// FIXTURE_DIR_NAMES
|
|
// ========================================
|
|
describe('FIXTURE_DIR_NAMES', () => {
|
|
it('contains expected directory names', () => {
|
|
assert.ok(FIXTURE_DIR_NAMES.includes('tests'));
|
|
assert.ok(FIXTURE_DIR_NAMES.includes('examples'));
|
|
assert.ok(FIXTURE_DIR_NAMES.includes('__tests__'));
|
|
});
|
|
});
|
|
|
|
// ========================================
|
|
// runAllScanners — fixture filtering
|
|
// ========================================
|
|
describe('runAllScanners — fixture filtering', () => {
|
|
it('excludes fixture findings by default when scanning plugin root', async () => {
|
|
const env = await runAllScanners(PLUGIN_ROOT);
|
|
// The plugin has test fixtures in tests/fixtures/ — those should be filtered out
|
|
const allFindingFiles = env.scanners.flatMap(s => s.findings.map(f => f.file)).filter(Boolean);
|
|
const fixtureInResults = allFindingFiles.filter(f => f.includes('/tests/'));
|
|
assert.strictEqual(fixtureInResults.length, 0, 'No fixture findings should appear in scanner results');
|
|
});
|
|
|
|
it('stores excluded findings in env.fixture_findings', async () => {
|
|
const env = await runAllScanners(PLUGIN_ROOT);
|
|
// Plugin has intentionally broken fixtures — at least some findings should be excluded
|
|
if (env.fixture_findings) {
|
|
assert.ok(Array.isArray(env.fixture_findings));
|
|
assert.ok(env.fixture_findings.length > 0, 'Expected fixture findings to be captured');
|
|
// All fixture findings should have test/example paths
|
|
for (const f of env.fixture_findings) {
|
|
const p = f.file || f.path || f.location || '';
|
|
assert.ok(
|
|
p.includes('/tests/') || p.includes('/examples/'),
|
|
`Fixture finding path should contain /tests/ or /examples/: ${p}`
|
|
);
|
|
}
|
|
}
|
|
// Note: if no fixture findings exist (unlikely but possible), test still passes
|
|
});
|
|
|
|
it('includes fixture findings when filterFixtures is false', async () => {
|
|
const env = await runAllScanners(PLUGIN_ROOT, { filterFixtures: false });
|
|
assert.strictEqual(env.fixture_findings, undefined, 'No fixture_findings field when filtering disabled');
|
|
// Some findings should have test fixture paths
|
|
const allFindingFiles = env.scanners.flatMap(s => s.findings.map(f => f.file)).filter(Boolean);
|
|
const fixtureInResults = allFindingFiles.filter(f => f.includes('/tests/'));
|
|
assert.ok(fixtureInResults.length > 0, 'Fixture findings should be present when filtering disabled');
|
|
});
|
|
|
|
it('does not filter GAP findings (file is null)', async () => {
|
|
const env = await runAllScanners(PLUGIN_ROOT);
|
|
const gapScanner = env.scanners.find(s => s.scanner === 'GAP');
|
|
assert.ok(gapScanner, 'GAP scanner should be present');
|
|
// GAP findings have file: null — they should never be filtered
|
|
for (const f of gapScanner.findings) {
|
|
assert.strictEqual(f.file, null, 'GAP findings have null file and should not be filtered');
|
|
}
|
|
});
|
|
|
|
it('recalculates scanner counts after fixture filtering', async () => {
|
|
const env = await runAllScanners(PLUGIN_ROOT);
|
|
for (const scanner of env.scanners) {
|
|
// Verify counts match actual findings
|
|
const expected = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
for (const f of scanner.findings) {
|
|
if (expected[f.severity] !== undefined) expected[f.severity]++;
|
|
}
|
|
assert.deepStrictEqual(scanner.counts, expected,
|
|
`Scanner ${scanner.scanner}: counts should match actual findings after filtering`);
|
|
}
|
|
});
|
|
|
|
it('total_findings in aggregate excludes fixtures', async () => {
|
|
const withFilter = await runAllScanners(PLUGIN_ROOT, { filterFixtures: true });
|
|
const withoutFilter = await runAllScanners(PLUGIN_ROOT, { filterFixtures: false });
|
|
// With filter should have fewer or equal findings
|
|
assert.ok(
|
|
withFilter.aggregate.total_findings <= withoutFilter.aggregate.total_findings,
|
|
`Filtered total (${withFilter.aggregate.total_findings}) should be <= unfiltered (${withoutFilter.aggregate.total_findings})`
|
|
);
|
|
});
|
|
|
|
it('fixture filtering and suppression are independent', async () => {
|
|
// Both enabled (default)
|
|
const both = await runAllScanners(PLUGIN_ROOT, { filterFixtures: true, suppress: true });
|
|
// Only fixtures
|
|
const fixturesOnly = await runAllScanners(PLUGIN_ROOT, { filterFixtures: true, suppress: false });
|
|
// Only suppression
|
|
const suppressOnly = await runAllScanners(PLUGIN_ROOT, { filterFixtures: false, suppress: true });
|
|
|
|
// fixture_findings should be present in both fixture-filtered runs
|
|
if (both.fixture_findings) {
|
|
assert.ok(fixturesOnly.fixture_findings, 'fixture_findings should be present regardless of suppress flag');
|
|
}
|
|
// suppressed_findings should be present in both suppression-enabled runs (if any suppressions exist)
|
|
if (both.suppressed_findings) {
|
|
assert.ok(suppressOnly.suppressed_findings, 'suppressed_findings should be present regardless of filterFixtures flag');
|
|
}
|
|
});
|
|
});
|