also looking at this
test: measure coverage, and call the three files nothing called
#1442
4 files
+210
-0
| @@ -0,0 +1,70 @@ | ||
| 1 | import { afterEach, describe, expect, it } from 'bun:test' | |
| 2 | import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' | |
| 3 | import { tmpdir } from 'node:os' | |
| 4 | import { join } from 'node:path' | |
| 5 | import { loadWorkflows } from '../../src/security/loader' | |
| 6 | ||
| 7 | const VALID = `name: CI | |
| 8 | on: push | |
| 9 | jobs: | |
| 10 | test: | |
| 11 | runs-on: ubuntu-latest | |
| 12 | steps: | |
| 13 | - run: bun test | |
| 14 | ` | |
| 15 | ||
| 16 | /** | |
| 17 | * The loader is the front door of every `buddy security` audit — `audit()` | |
| 18 | * begins with it — and had 0% function coverage: the rules are tested | |
| 19 | * against hand-built fixtures, so a discovery bug (a skipped extension, a | |
| 20 | * traversed subdirectory) would fail every audit while every test stayed | |
| 21 | * green. | |
| 22 | */ | |
| 23 | describe('workflow loader', () => { | |
| 24 | const cleanups: Array<() => void> = [] | |
| 25 | ||
| 26 | afterEach(() => { | |
| 27 | while (cleanups.length > 0) | |
| 28 | cleanups.pop()?.() | |
| 29 | }) | |
| 30 | ||
| 31 | /** A temp repo root, removed after the test. */ | |
| 32 | function root(): string { | |
| 33 | const dir = mkdtempSync(join(tmpdir(), 'buddy-loader-')) | |
| 34 | cleanups.push(() => rmSync(dir, { recursive: true, force: true })) | |
| 35 | return dir | |
| 36 | } | |
| 37 | ||
| 38 | it('success case - finds yml and yaml, and nothing else', async () => { | |
| 39 | const dir = root() | |
| 40 | mkdirSync(join(dir, '.github', 'workflows'), { recursive: true }) | |
| 41 | writeFileSync(join(dir, '.github', 'workflows', 'ci.yml'), VALID) | |
| 42 | writeFileSync(join(dir, '.github', 'workflows', 'release.yaml'), VALID) | |
| 43 | writeFileSync(join(dir, '.github', 'workflows', 'README.md'), '# not a workflow') | |
| 44 | ||
| 45 | const workflows = await loadWorkflows(dir) | |
| 46 | ||
| 47 | expect(workflows.map(workflow => workflow.file).sort()).toEqual([ | |
| 48 | '.github/workflows/ci.yml', | |
| 49 | '.github/workflows/release.yaml', | |
| 50 | ]) | |
| 51 | expect(workflows[0].raw).toContain('runs-on') | |
| 52 | }) | |
| 53 | ||
| 54 | it('edge case - subdirectories are not traversed', async () => { | |
| 55 | // GitHub itself only loads top-level files; a nested yml is a composite | |
| 56 | // action or a template, not a triggered workflow. | |
| 57 | const dir = root() | |
| 58 | mkdirSync(join(dir, '.github', 'workflows', 'templates'), { recursive: true }) | |
| 59 | writeFileSync(join(dir, '.github', 'workflows', 'ci.yml'), VALID) | |
| 60 | writeFileSync(join(dir, '.github', 'workflows', 'templates', 'nested.yml'), VALID) | |
| 61 | ||
| 62 | const workflows = await loadWorkflows(dir) | |
| 63 | ||
| 64 | expect(workflows.map(workflow => workflow.file)).toEqual(['.github/workflows/ci.yml']) | |
| 65 | }) | |
| 66 | ||
| 67 | it('edge case - a repository without workflows loads as empty', async () => { | |
| 68 | expect(await loadWorkflows(root())).toEqual([]) | |
| 69 | }) | |
| 70 | }) | |