// git.test.mjs — Integration tests for the git-forensics scanner // // The evil-project-health fixture is not a standalone git repo, but it may sit // inside a parent git repo. The scanner uses `git rev-parse` which walks up the // directory tree, so it may detect the parent repo. Both 'skipped' (truly no git) // and 'ok' (parent repo detected) are valid outcomes. // // This test suite verifies: // - Graceful handling: status is 'ok' or 'skipped', never 'error' with no findings // - Correct structure of the scanner result envelope // - All findings (if any) have the DS-GIT- prefix import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { resetCounter } from '../../scanners/lib/output.mjs'; import { scan } from '../../scanners/git-forensics.mjs'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const FIXTURE = resolve(__dirname, '../../examples/malicious-skill-demo/evil-project-health'); // The plugin root — may or may not be a standalone git repo const PLUGIN_ROOT = resolve(__dirname, '../..'); describe('git-forensics integration', () => { beforeEach(() => { resetCounter(); }); it('returns skipped or ok for the fixture directory (graceful handling)', async () => { // If the fixture is inside a git repo, scanner returns 'ok'. // If it is a bare directory with no git ancestry, scanner returns 'skipped'. const result = await scan(FIXTURE, {}); const validStatuses = ['ok', 'skipped']; assert.ok( validStatuses.includes(result.status), `Expected 'skipped' or 'ok', got '${result.status}'` ); }); it('returns 0 or few findings for the fixture directory', async () => { // The fixture has no git history of its own. If the parent repo is detected, // findings reflect the parent repo's accumulated history. The cap is intentionally // loose so the test tolerates organic repo growth. const result = await scan(FIXTURE, {}); if (result.status === 'skipped') { assert.equal(result.findings.length, 0, 'skipped should produce 0 findings'); } else { assert.ok( result.findings.length <= 100, `Expected <= 100 findings for fixture dir (parent repo detected), got ${result.findings.length}` ); } }); it('scanner name is git-forensics', async () => { const result = await scan(FIXTURE, {}); assert.equal(result.scanner, 'git-forensics', `Expected 'git-forensics', got '${result.scanner}'`); }); it('returns ok or skipped for the plugin root (graceful handling)', async () => { resetCounter(); const result = await scan(PLUGIN_ROOT, {}); const validStatuses = ['ok', 'skipped', 'error']; assert.ok( validStatuses.includes(result.status), `Expected ok/skipped/error for plugin root, got '${result.status}'` ); }); it('findings count is reasonable for the plugin root', async () => { // Loose cap — git-forensics findings accumulate with repo history, so the // assertion tolerates growth while still catching runaway/pathological output. resetCounter(); const result = await scan(PLUGIN_ROOT, {}); if (result.status === 'skipped') { assert.equal(result.findings.length, 0); } else { assert.ok( result.findings.length <= 100, `Expected <= 100 findings for plugin root, got ${result.findings.length}` ); } }); it('all findings have DS-GIT- prefix', async () => { resetCounter(); const result = await scan(FIXTURE, {}); const wrongPrefix = result.findings.filter(f => !f.id.startsWith('DS-GIT-')); assert.equal( wrongPrefix.length, 0, `All git findings should have DS-GIT- prefix. Wrong: ${wrongPrefix.map(f => f.id).join(', ')}` ); }); it('counts object has expected severity keys', async () => { const result = await scan(FIXTURE, {}); const expectedKeys = ['critical', 'high', 'medium', 'low', 'info']; for (const key of expectedKeys) { assert.ok(key in result.counts, `counts should have key '${key}'`); } }); it('duration_ms is a non-negative number', async () => { const result = await scan(FIXTURE, {}); assert.ok(typeof result.duration_ms === 'number', 'duration_ms should be a number'); assert.ok(result.duration_ms >= 0, 'duration_ms should be non-negative'); }); });