ReviewOS

also looking at this

stacks/buddy

test: measure coverage, and call the three files nothing called

#1442
Merged glennmichael123 wants to merge test/zero-called-files into main
4 files +210 -0

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

package.jsonmodified+1-0
Changes to package.json
@@ -59,6 +59,7 @@
5959 "fresh": "bunx rimraf node_modules/ bun.lock && bun i",
6060 "prepublishOnly": "bun run build",
6161 "test": "bun test",
62 "test:coverage": "bun test --coverage",
6263 "lint": "bunx --bun pickier .",
6364 "lint:fix": "bunx --bun pickier . --fix",
6465 "format": "bunx --bun pickier . --format",
test/analyzers.test.tsmodified+72-0
Changes to test/analyzers.test.ts
@@ -374,3 +374,75 @@ describe('analysis configuration', () => {
374374 expect(validateConfig({ analysis: { tools: { shellcheck: 'off' as never } } })).toHaveLength(1)
375375 })
376376})
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 */
384describe('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
393on: push
394permissions: write-all
395jobs:
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
test/pantry-parser.test.tsadded+67-0
Changes to test/pantry-parser.test.ts
@@ -0,0 +1,67 @@
1import { describe, expect, it } from 'bun:test'
2import { isPantryLockFile, parsePantryLockFile } from '../src/utils/pantry-parser'
3
4/**
5 * 0% function coverage before this file: the parser is wired into the
6 * package scanner and was never once called by a test, so a regression here
7 * would surface as pantry.lock silently vanishing from the dashboard.
8 */
9describe('pantry-parser', () => {
10 describe('isPantryLockFile', () => {
11 it('success case - matches the lock file wherever it lives', () => {
12 expect(isPantryLockFile('pantry.lock')).toBe(true)
13 expect(isPantryLockFile('packages/app/pantry.lock')).toBe(true)
14 })
15
16 it('failure case - near-misses are not lock files', () => {
17 expect(isPantryLockFile('pantry.lock.bak')).toBe(false)
18 expect(isPantryLockFile('pantry.yaml')).toBe(false)
19 expect(isPantryLockFile('bun.lock')).toBe(false)
20 })
21 })
22
23 describe('parsePantryLockFile', () => {
24 const lock = {
25 version: '1',
26 packages: {
27 'bun.com': { name: 'bun.com', version: '1.2.19', resolved: 'https://example.com/bun' },
28 'node': { name: 'node', version: '22.3.0', resolved: 'https://example.com/node' },
29 },
30 }
31
32 it('success case - extracts every resolved package', async () => {
33 const parsed = await parsePantryLockFile('pantry.lock', JSON.stringify(lock))
34
35 expect(parsed?.type).toBe('pantry.lock')
36 expect(parsed?.dependencies).toEqual([
37 { name: 'bun.com', currentVersion: '1.2.19', type: 'dependencies', file: 'pantry.lock' },
38 { name: 'node', currentVersion: '22.3.0', type: 'dependencies', file: 'pantry.lock' },
39 ])
40 })
41
42 it('edge case - entries missing a name or version are skipped', async () => {
43 const partial = {
44 version: '1',
45 packages: {
46 ok: { name: 'ok', version: '1.0.0', resolved: '' },
47 nameless: { version: '2.0.0', resolved: '' },
48 versionless: { name: 'versionless', resolved: '' },
49 },
50 }
51
52 const parsed = await parsePantryLockFile('pantry.lock', JSON.stringify(partial))
53
54 expect(parsed?.dependencies.map(dep => dep.name)).toEqual(['ok'])
55 })
56
57 it('edge case - a lock without packages parses to no dependencies', async () => {
58 const parsed = await parsePantryLockFile('pantry.lock', JSON.stringify({ version: '1' }))
59
60 expect(parsed?.dependencies).toEqual([])
61 })
62
63 it('failure case - malformed JSON returns null rather than throwing', async () => {
64 expect(await parsePantryLockFile('pantry.lock', 'not json {')).toBeNull()
65 })
66 })
67})
test/security/loader.test.tsadded+70-0
Changes to test/security/loader.test.ts
@@ -0,0 +1,70 @@
1import { afterEach, describe, expect, it } from 'bun:test'
2import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
3import { tmpdir } from 'node:os'
4import { join } from 'node:path'
5import { loadWorkflows } from '../../src/security/loader'
6
7const VALID = `name: CI
8on: push
9jobs:
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 */
23describe('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})