/** * Unit Tests - ProhibitedTermsScanner * Tests for proactive content scanning (Framework Phase 1) */ const fs = require('fs').promises; const path = require('path'); const ProhibitedTermsScanner = require('../../scripts/framework-components/ProhibitedTermsScanner'); describe('ProhibitedTermsScanner', () => { let scanner; const testFilesDir = path.join(__dirname, '../tmp-scanner-test'); beforeEach(() => { scanner = new ProhibitedTermsScanner({ silent: true, basePath: testFilesDir // Only scan test directory }); }); afterEach(async () => { // Clean up test files try { await fs.rm(testFilesDir, { recursive: true, force: true }); } catch (err) { // Ignore cleanup errors } }); describe('Pattern Detection - inst_017 (Absolute Assurance)', () => { test('should detect "guarantee" variations', async () => { const testContent = ` This guarantees safety. We guarantee results. This is guaranteed to work. We are guaranteeing success. `; // Create test file await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const inst017Violations = violations.filter(v => v.rule === 'inst_017'); expect(inst017Violations.length).toBeGreaterThanOrEqual(4); expect(inst017Violations.some(v => v.match.toLowerCase().includes('guarantee'))).toBe(true); }); test('should detect "ensures 100%"', async () => { const testContent = 'This ensures 100% accuracy.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.html'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const inst017Violations = violations.filter(v => v.rule === 'inst_017'); expect(inst017Violations.length).toBeGreaterThan(0); expect(inst017Violations.some(v => v.match.toLowerCase().includes('ensures'))).toBe(true); }); test('should detect "eliminates all"', async () => { const testContent = 'This eliminates all bugs.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.js'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); expect(violations.length).toBeGreaterThan(0); expect(violations.some(v => v.match.toLowerCase().includes('eliminates'))).toBe(true); }); test('should detect "never fails"', async () => { const testContent = 'This system never fails.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); expect(violations.length).toBeGreaterThan(0); expect(violations.some(v => v.match.toLowerCase().includes('never'))).toBe(true); }); }); describe('Pattern Detection - inst_018 (Unverified Claims)', () => { test('should detect "production-ready" without context', async () => { const testContent = 'This is production-ready software.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const inst018Violations = violations.filter(v => v.rule === 'inst_018'); expect(inst018Violations.length).toBeGreaterThan(0); }); test('should detect "battle-tested"', async () => { const testContent = 'Our battle-tested framework.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.html'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); expect(violations.some(v => v.match.toLowerCase().includes('battle-tested'))).toBe(true); }); test('should allow "production-ready development tool"', async () => { const testContent = 'Tractatus is a production-ready development tool.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const inst018Violations = violations.filter(v => v.rule === 'inst_018' && v.match.toLowerCase().includes('production-ready') ); expect(inst018Violations.length).toBe(0); }); test('should allow "production-ready proof-of-concept"', async () => { const testContent = 'This is a production-ready proof-of-concept.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const inst018Violations = violations.filter(v => v.rule === 'inst_018' && v.match.toLowerCase().includes('production-ready') ); expect(inst018Violations.length).toBe(0); }); }); describe('Context Awareness', () => { test('should allow prohibited terms in comments about rules', async () => { const testContent = ` // inst_017: Never use "guarantee" // inst_017 prohibits guaranteed language `; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.js'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); // Should not flag violations in comments about the rules expect(violations.length).toBe(0); }); test('should exclude test files from scanning', async () => { // Test files should be excluded by pattern const scanner2 = new ProhibitedTermsScanner({ silent: true }); expect(scanner2.excludePatterns).toContain('**/tests/**/*.test.js'); expect(scanner2.excludePatterns).toContain('**/tests/**/*.spec.js'); }); test('should exclude GOVERNANCE-RULE-LIBRARY.md', async () => { expect(scanner.excludePatterns).toContain('**/GOVERNANCE-RULE-LIBRARY.md'); }); test('should exclude case studies', async () => { expect(scanner.excludePatterns).toContain('**/docs/case-studies/**'); }); }); describe('Suggestions', () => { test('should suggest "enforcement" for "guarantee"', () => { const suggestions = scanner.patterns.find(p => p.id === 'inst_017').suggestions; expect(suggestions['guarantee']).toBe('enforcement'); expect(suggestions['guarantees']).toBe('enforces'); expect(suggestions['guaranteed']).toBe('enforced'); }); test('should suggest replacements for "ensures 100%"', () => { const suggestions = scanner.patterns.find(p => p.id === 'inst_017').suggestions; expect(suggestions['ensures 100%']).toBe('helps ensure'); }); test('should suggest replacements for inst_018 terms', () => { const suggestions = scanner.patterns.find(p => p.id === 'inst_018').suggestions; expect(suggestions['production-ready']).toBe('proof-of-concept'); expect(suggestions['battle-tested']).toBe('in development'); }); test('should get suggestion for matched term', () => { const patternSet = scanner.patterns.find(p => p.id === 'inst_017'); const suggestion = scanner.getSuggestion('guarantee', patternSet.suggestions); expect(suggestion).toBe('enforcement'); }); test('should handle case-insensitive matches', () => { const patternSet = scanner.patterns.find(p => p.id === 'inst_017'); const suggestion = scanner.getSuggestion('GUARANTEE', patternSet.suggestions); expect(suggestion).toBe('enforcement'); }); }); describe('Auto-fix Functionality', () => { test('should fix simple violations', async () => { const testContent = 'This guarantees safety.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const results = await scanner.autoFix(violations); expect(results.fixed).toBeGreaterThan(0); const fixedContent = await fs.readFile(testFile, 'utf8'); expect(fixedContent).toContain('enforces'); // guarantees → enforces expect(fixedContent).not.toContain('guarantees'); }); test('should preserve file structure during fix', async () => { const testContent = `Line 1: Normal content Line 2: This guarantees results Line 3: More content Line 4: This guaranteed safety Line 5: Final line`; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); await scanner.autoFix(violations); const fixedContent = await fs.readFile(testFile, 'utf8'); const lines = fixedContent.split('\n'); expect(lines.length).toBe(5); expect(lines[0]).toBe('Line 1: Normal content'); expect(lines[4]).toBe('Line 5: Final line'); }); test('should handle multiple violations in same file', async () => { const testContent = ` We guarantee success. This is guaranteed to work. Our guarantees are strong. `; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const results = await scanner.autoFix(violations); expect(results.fixed).toBeGreaterThan(0); const fixedContent = await fs.readFile(testFile, 'utf8'); expect(fixedContent).not.toContain('guarantee'); }); test('should skip violations without clear suggestions', async () => { // inst_016 violations (fabricated statistics) require manual review const testContent = '95% faster performance.'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); const inst016Violations = violations.filter(v => v.rule === 'inst_016'); const results = await scanner.autoFix(inst016Violations); // inst_016 requires source citation, can't auto-fix expect(results.skipped).toBeGreaterThan(0); }); }); describe('Formatting', () => { test('should format empty violations list', () => { const output = scanner.formatViolations([]); expect(output).toContain('No prohibited terms found'); }); test('should format violations summary', () => { const violations = [ { rule: 'inst_017', file: 'test.md', line: 1, match: 'guarantee', severity: 'HIGH' }, { rule: 'inst_017', file: 'test.md', line: 2, match: 'guaranteed', severity: 'HIGH' }, { rule: 'inst_018', file: 'test.html', line: 5, match: 'battle-tested', severity: 'MEDIUM' } ]; const output = scanner.formatViolations(violations, false); expect(output).toContain('Found 3 violation'); expect(output).toContain('inst_017: 2'); expect(output).toContain('inst_018: 1'); }); test('should format detailed violations', () => { const violations = [ { rule: 'inst_017', file: 'test.md', line: 1, match: 'guarantee', severity: 'HIGH', context: 'This guarantees safety', suggestion: 'enforcement' } ]; const output = scanner.formatViolations(violations, true); expect(output).toContain('test.md:1'); expect(output).toContain('Found: "guarantee"'); expect(output).toContain('Suggestion: enforcement'); }); }); describe('Utility Functions', () => { test('should escape regex special characters', () => { const escaped = scanner.escapeRegex('test.file[0]'); expect(escaped).toBe('test\\.file\\[0\\]'); }); test('should check allowed context for inst_017 references', () => { const line = 'inst_017 prohibits guaranteed language'; const result = scanner.isAllowedContext(line, 'guaranteed', 'test.md'); expect(result).toBe(true); }); test('should check allowed context for case studies', () => { const line = 'This guarantees results'; const result = scanner.isAllowedContext(line, 'guarantees', 'docs/case-studies/example.md'); expect(result).toBe(true); }); test('should reject prohibited terms in normal content', () => { const line = 'This guarantees results'; const result = scanner.isAllowedContext(line, 'guarantees', 'README.md'); expect(result).toBe(false); }); }); describe('File Inclusion/Exclusion', () => { test('should include markdown files', () => { expect(scanner.includePatterns).toContain('**/*.md'); }); test('should include HTML files', () => { expect(scanner.includePatterns).toContain('**/*.html'); }); test('should include JavaScript files', () => { expect(scanner.includePatterns).toContain('**/*.js'); }); test('should exclude node_modules', () => { expect(scanner.excludePatterns).toContain('**/node_modules/**'); }); test('should exclude .git directory', () => { expect(scanner.excludePatterns).toContain('**/.git/**'); }); test('should exclude .claude directory', () => { expect(scanner.excludePatterns).toContain('**/.claude/**'); }); }); describe('Edge Cases', () => { test('should handle empty files', async () => { await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'empty.md'); await fs.writeFile(testFile, ''); const violations = await scanner.scan(); // Should not error on empty files expect(Array.isArray(violations)).toBe(true); }); test('should handle files with only whitespace', async () => { await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'whitespace.md'); await fs.writeFile(testFile, '\n\n \n\t\n'); const violations = await scanner.scan(); expect(Array.isArray(violations)).toBe(true); }); test('should handle very long lines', async () => { const longLine = 'a'.repeat(10000) + ' guarantee ' + 'b'.repeat(10000); await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'long.md'); await fs.writeFile(testFile, longLine); const violations = await scanner.scan(); expect(violations.length).toBeGreaterThan(0); }); test('should handle violations at file end without newline', async () => { const testContent = 'This guarantees results'; await fs.mkdir(testFilesDir, { recursive: true }); const testFile = path.join(testFilesDir, 'test.md'); await fs.writeFile(testFile, testContent); const violations = await scanner.scan(); expect(violations.length).toBeGreaterThan(0); }); }); describe('Silent Mode', () => { test('should suppress console output in silent mode', async () => { const silentScanner = new ProhibitedTermsScanner({ silent: true, basePath: testFilesDir }); const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); await silentScanner.scan(); // In silent mode, should not call console.log for scanning message const scanningCalls = consoleSpy.mock.calls.filter(call => call.some(arg => typeof arg === 'string' && arg.includes('Scanning')) ); expect(scanningCalls.length).toBe(0); consoleSpy.mockRestore(); }); }); });