import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { writeFileSync, mkdirSync, rmSync } from 'fs'; import { resolve } from 'path'; import { runDisclosureGate } from '../src/gates/disclosure'; import { runRfcGate } from '../src/gates/rfc'; import { runChecklistGate } from '../src/gates/checklist'; import { runQualityGate } from '../src/gates/quality'; import * as gitModule from '../src/git'; const TMP = resolve(__dirname, 'tmp-gates'); function clean() { try { rmSync(TMP, { recursive: true, force: true }); } catch {} } describe('disclosure gate', () => { beforeEach(() => { clean(); mkdirSync(TMP, { recursive: true }); }); afterEach(() => { clean(); }); it('passes when all files have marker', () => { writeFileSync(resolve(TMP, 'a.ts'), '// AI-GENERATED: this file'); writeFileSync(resolve(TMP, 'b.ts'), '/* AI-ASSISTED */'); const r = runDisclosureGate([resolve(TMP, 'a.ts'), resolve(TMP, 'b.ts')], {}); expect(r.passed).toBe(true); }); it('fails when a file lacks marker', () => { writeFileSync(resolve(TMP, 'a.ts'), '// AI-GENERATED: this file'); writeFileSync(resolve(TMP, 'b.ts'), 'no marker here'); const r = runDisclosureGate([resolve(TMP, 'a.ts'), resolve(TMP, 'b.ts')], {}); expect(r.passed).toBe(false); expect(r.details?.length).toBe(1); expect(r.details?.[0]).toContain('b.ts'); }); it('passes when no files to check (all excluded)', () => { const r = runDisclosureGate([], { excludePaths: ['node_modules/'] }); expect(r.passed).toBe(true); }); it('excludes configured paths', () => { mkdirSync(resolve(TMP, 'node_modules'), { recursive: true }); mkdirSync(resolve(TMP, 'src'), { recursive: true }); writeFileSync(resolve(TMP, 'node_modules/x.js'), 'no marker'); writeFileSync(resolve(TMP, 'src/app.ts'), '// AI-GENERATED'); const r = runDisclosureGate([resolve(TMP, 'node_modules/x.js'), resolve(TMP, 'src/app.ts')], { excludePaths: ['node_modules/'] }); expect(r.passed).toBe(true); }); }); describe('rfc gate', () => { beforeEach(() => { vi.restoreAllMocks(); }); it('passes when commit message has issue ref', () => { vi.spyOn(gitModule, 'getCommitMessage').mockReturnValue('Fix bug #123'); const r = runRfcGate({ source: 'commit-message' }); expect(r.passed).toBe(true); }); it('fails when commit message lacks issue ref', () => { vi.spyOn(gitModule, 'getCommitMessage').mockReturnValue('Fix bug'); const r = runRfcGate({ source: 'commit-message' }); expect(r.passed).toBe(false); }); it('passes when env var matches pattern', () => { process.env.CODEGOV_RFC_REF = 'TICKET-42'; const r = runRfcGate({ source: 'env', envVar: 'CODEGOV_RFC_REF' }); expect(r.passed).toBe(true); delete process.env.CODEGOV_RFC_REF; }); it('fails when env var is missing', () => { delete process.env.CODEGOV_RFC_REF; const r = runRfcGate({ source: 'env', envVar: 'CODEGOV_RFC_REF' }); expect(r.passed).toBe(false); }); it('passes with custom pattern', () => { vi.spyOn(gitModule, 'getCommitMessage').mockReturnValue('Fix [JIRA-99]'); const r = runRfcGate({ source: 'commit-message', pattern: 'JIRA-[0-9]+' }); expect(r.passed).toBe(true); }); }); describe('checklist gate', () => { beforeEach(() => { clean(); mkdirSync(TMP, { recursive: true }); }); afterEach(() => { clean(); }); it('passes when all items checked', () => { writeFileSync(resolve(TMP, 'REQ.md'), '- [x] Feature A\n- [x] Feature B\n'); const r = runChecklistGate({ filePath: resolve(TMP, 'REQ.md') }); expect(r.passed).toBe(true); }); it('fails when an item is unchecked', () => { writeFileSync(resolve(TMP, 'REQ.md'), '- [x] Feature A\n- [ ] Feature B\n'); const r = runChecklistGate({ filePath: resolve(TMP, 'REQ.md') }); expect(r.passed).toBe(false); expect(r.details?.length).toBe(1); }); it('fails when file is missing', () => { const r = runChecklistGate({ filePath: resolve(TMP, 'MISSING.md') }); expect(r.passed).toBe(false); }); it('fails when no checked items exist', () => { writeFileSync(resolve(TMP, 'REQ.md'), '- [ ] Feature A\n'); const r = runChecklistGate({ filePath: resolve(TMP, 'REQ.md') }); expect(r.passed).toBe(false); }); }); describe('quality gate', () => { it('passes when no commands configured', () => { const r = runQualityGate({ commands: [] }); expect(r.passed).toBe(true); }); it('passes when all commands succeed', () => { const r = runQualityGate({ commands: ['echo ok'] }); expect(r.passed).toBe(true); }); it('fails when a command fails', () => { const r = runQualityGate({ commands: ['exit 1'] }); expect(r.passed).toBe(false); }); it('runs multiple commands and reports failures', () => { const r = runQualityGate({ commands: ['echo ok', 'exit 1', 'exit 0'] }); expect(r.passed).toBe(false); expect(r.details?.length).toBe(1); }); });