also looking at this
test: measure coverage, and call the three files nothing called
#1442
4 files
+210
-0
| @@ -374,3 +374,75 @@ describe('analysis configuration', () => { | ||
| 374 | 374 | expect(validateConfig({ analysis: { tools: { shellcheck: 'off' as never } } })).toHaveLength(1) |
| 375 | 375 | }) |
| 376 | 376 | }) |
| 377 | ||
| 378 | /** | |
| 379 | * The workflow-audit analyzer had 0% function coverage: the six security | |
| 380 | * rules it wraps are tested directly, but the wrapper — severity mapping, | |
| 381 | * changed-file filtering, the empty-input short-circuit — was not, and the | |
| 382 | * wrapper is what review actually calls. | |
| 383 | */ | |
| 384 | describe('workflow-audit analyzer', () => { | |
| 385 | const cleanups: Array<() => void> = [] | |
| 386 | ||
| 387 | afterEach(() => { | |
| 388 | while (cleanups.length > 0) | |
| 389 | cleanups.pop()?.() | |
| 390 | }) | |
| 391 | ||
| 392 | const RISKY = `name: CI | |
| 393 | on: push | |
| 394 | permissions: write-all | |
| 395 | jobs: | |
| 396 | test: | |
| 397 | runs-on: ubuntu-latest | |
| 398 | steps: | |
| 399 | - run: bun test | |
| 400 | ` | |
| 401 | ||
| 402 | async function repoRoot(): Promise<string> { | |
| 403 | const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import('node:fs') | |
| 404 | const { tmpdir } = await import('node:os') | |
| 405 | const { join } = await import('node:path') | |
| 406 | ||
| 407 | const dir = mkdtempSync(join(tmpdir(), 'buddy-wf-audit-')) | |
| 408 | cleanups.push(() => rmSync(dir, { recursive: true, force: true })) | |
| 409 | mkdirSync(join(dir, '.github', 'workflows'), { recursive: true }) | |
| 410 | writeFileSync(join(dir, '.github', 'workflows', 'touched.yml'), RISKY) | |
| 411 | writeFileSync(join(dir, '.github', 'workflows', 'untouched.yml'), RISKY) | |
| 412 | return dir | |
| 413 | } | |
| 414 | ||
| 415 | it('success case - findings land as review findings with mapped severities', async () => { | |
| 416 | const { githubActionsAnalyzer } = await import('../src/analysis/analyzers/github-actions') | |
| 417 | const root = await repoRoot() | |
| 418 | ||
| 419 | const findings = await githubActionsAnalyzer.run(['.github/workflows/touched.yml'], root) | |
| 420 | ||
| 421 | expect(findings.length).toBeGreaterThan(0) | |
| 422 | for (const finding of findings) { | |
| 423 | expect(finding.tool).toBe('workflow-audit') | |
| 424 | expect(finding.path).toBe('.github/workflows/touched.yml') | |
| 425 | expect(['critical', 'major', 'minor', 'nit']).toContain(finding.severity) | |
| 426 | } | |
| 427 | // `permissions: write-all` is an error-level rule; errors map to major. | |
| 428 | expect(findings.some(finding => finding.severity === 'major')).toBe(true) | |
| 429 | }) | |
| 430 | ||
| 431 | it('success case - only the changed workflows are reported', async () => { | |
| 432 | // The auditor scans the whole repository; a review must not comment on | |
| 433 | // files the pull request never touched. | |
| 434 | const { githubActionsAnalyzer } = await import('../src/analysis/analyzers/github-actions') | |
| 435 | const root = await repoRoot() | |
| 436 | ||
| 437 | const findings = await githubActionsAnalyzer.run(['.github/workflows/touched.yml'], root) | |
| 438 | ||
| 439 | expect(findings.every(finding => finding.path !== '.github/workflows/untouched.yml')).toBe(true) | |
| 440 | }) | |
| 441 | ||
| 442 | it('edge case - no changed workflows means no audit at all', async () => { | |
| 443 | const { githubActionsAnalyzer } = await import('../src/analysis/analyzers/github-actions') | |
| 444 | ||
| 445 | expect(await githubActionsAnalyzer.run([], '/nonexistent-root')).toEqual([]) | |
| 446 | }) | |
| 447 | }) | |
| 448 | ||