/** * Self-Configurator Tests * ADR-025: Enhanced Init with Self-Configuration */ import { describe, it, expect } from 'vitest'; import { SelfConfigurator, createSelfConfigurator, recommendConfig, } from '../../../src/init/self-configurator.js'; import type { ProjectAnalysis } from '../../../src/init/types.js'; // Helper to create a base project analysis // Note: We set totalCount > 0 to avoid triggering the "no-tests" rule which overwrites domains function createBaseAnalysis(overrides: Partial = {}): ProjectAnalysis { return { projectName: 'test-project', projectRoot: '/test/project', projectType: 'single', packageManager: 'npm', hasTypeScript: false, hasCIConfig: false, ciProvider: undefined, frameworks: [], languages: [], existingTests: { totalCount: 10, // Non-zero to avoid triggering no-tests rule byType: { unit: 8, integration: 2, e2e: 0, other: 0 }, byFramework: { jest: 10 }, directories: ['tests'], }, codeComplexity: { totalFiles: 50, totalLines: 5000, averageFileSize: 100, recommendation: 'moderate', }, coverage: { hasReport: false, lines: 0, branches: 0, functions: 0, statements: 0, }, analysisTimestamp: new Date(), analysisDurationMs: 100, ...overrides, }; } describe('SelfConfigurator', () => { describe('createSelfConfigurator', () => { it('should create a self-configurator', () => { const configurator = createSelfConfigurator(); expect(configurator).toBeInstanceOf(SelfConfigurator); }); it('should create a minimal configurator', () => { const configurator = createSelfConfigurator({ minimal: true }); expect(configurator).toBeInstanceOf(SelfConfigurator); }); }); describe('recommend', () => { it('should generate base configuration', () => { const analysis = createBaseAnalysis(); const configurator = createSelfConfigurator(); const config = configurator.recommend(analysis); expect(config.version).toBe('3.0.0'); expect(config.project.name).toBe('test-project'); expect(config.project.root).toBe('/test/project'); expect(config.project.type).toBe('single'); }); it('should enable learning by default', () => { const analysis = createBaseAnalysis(); const config = recommendConfig(analysis); expect(config.learning.enabled).toBe(true); expect(config.learning.hnswConfig).toBeDefined(); }); it('should configure HNSW for large codebases', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 1500, totalLines: 150000, averageFileSize: 100, recommendation: 'complex', }, }); const config = recommendConfig(analysis); expect(config.learning.hnswConfig.M).toBe(32); expect(config.learning.hnswConfig.efConstruction).toBe(400); expect(config.learning.hnswConfig.efSearch).toBe(200); }); it('should configure HNSW for small codebases', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 30, totalLines: 3000, averageFileSize: 100, recommendation: 'simple', }, }); const config = recommendConfig(analysis); expect(config.learning.hnswConfig.M).toBe(8); expect(config.learning.hnswConfig.efConstruction).toBe(100); }); it('should use transformer embeddings for TypeScript', () => { const analysis = createBaseAnalysis({ hasTypeScript: true, languages: [{ name: 'typescript', percentage: 80, fileCount: 40 }], }); const config = recommendConfig(analysis); expect(config.learning.embeddingModel).toBe('transformer'); }); it('should use rules routing for simple projects', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 20, totalLines: 2000, averageFileSize: 100, recommendation: 'simple', }, }); const config = recommendConfig(analysis); expect(config.routing.mode).toBe('rules'); }); it('should use ML routing for complex projects', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 500, totalLines: 100000, averageFileSize: 200, recommendation: 'complex', }, }); const config = recommendConfig(analysis); expect(config.routing.mode).toBe('ml'); }); it('should use hybrid routing for moderate projects', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 100, totalLines: 10000, averageFileSize: 100, recommendation: 'moderate', }, }); const config = recommendConfig(analysis); expect(config.routing.mode).toBe('hybrid'); }); }); describe('Configuration Rules', () => { it('should apply TypeScript + Vitest rule', () => { const analysis = createBaseAnalysis({ hasTypeScript: true, frameworks: [{ name: 'vitest', version: '1.0.0', confidence: 1.0 }], languages: [{ name: 'typescript', percentage: 90, fileCount: 45 }], }); const config = recommendConfig(analysis); expect(config.learning.embeddingModel).toBe('transformer'); expect(config.routing.confidenceThreshold).toBe(0.75); }); it('should apply large codebase rule', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 800, totalLines: 80000, averageFileSize: 100, recommendation: 'complex', }, }); const config = recommendConfig(analysis); expect(config.agents.maxConcurrent).toBe(15); }); it('should apply small project rule', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 25, totalLines: 2500, averageFileSize: 100, recommendation: 'simple', }, }); const config = recommendConfig(analysis); expect(config.agents.maxConcurrent).toBe(5); expect(config.workers.maxConcurrent).toBe(2); }); it('should apply low coverage rule', () => { const analysis = createBaseAnalysis({ coverage: { hasReport: true, lines: 30, branches: 20, functions: 40, statements: 30, }, }); const config = recommendConfig(analysis); expect(config.workers.enabled).toContain('coverage-gap-scanner'); }); it('should apply monorepo rule', () => { const analysis = createBaseAnalysis({ projectType: 'monorepo', }); const config = recommendConfig(analysis); expect(config.agents.maxConcurrent).toBe(15); expect(config.workers.maxConcurrent).toBe(6); expect(config.domains.enabled).toContain('code-intelligence'); }); it('should apply E2E framework rule', () => { const analysis = createBaseAnalysis({ frameworks: [{ name: 'playwright', version: '1.40.0', confidence: 1.0 }], }); const config = recommendConfig(analysis); expect(config.domains.enabled).toContain('visual-accessibility'); expect(config.domains.enabled).toContain('chaos-resilience'); }); it('should apply CI integration rule', () => { const analysis = createBaseAnalysis({ hasCIConfig: true, ciProvider: 'github-actions', }); const config = recommendConfig(analysis); expect(config.hooks.ciIntegration).toBe(true); expect(config.hooks.claudeCode).toBe(true); expect(config.domains.enabled).toContain('quality-assessment'); }); it('should apply no-tests rule', () => { const analysis = createBaseAnalysis({ existingTests: { totalCount: 0, // Explicitly zero to trigger no-tests rule byType: { unit: 0, integration: 0, e2e: 0, other: 0 }, byFramework: {}, directories: [], }, }); const config = recommendConfig(analysis); expect(config.learning.qualityThreshold).toBe(0.5); expect(config.learning.promotionThreshold).toBe(2); expect(config.domains.enabled).toContain('test-generation'); // No-tests rule focuses on test generation domains only expect(config.domains.enabled).toContain('coverage-analysis'); expect(config.domains.enabled).toContain('learning-optimization'); }); it('should apply many-tests rule', () => { const analysis = createBaseAnalysis({ existingTests: { totalCount: 600, byType: { unit: 400, integration: 150, e2e: 50, other: 0 }, byFramework: { jest: 600 }, directories: ['tests'], }, }); const config = recommendConfig(analysis); expect(config.workers.enabled).toContain('flaky-test-detector'); expect(config.domains.enabled).toContain('test-execution'); }); it('should apply Python project rule', () => { const analysis = createBaseAnalysis({ languages: [{ name: 'python', percentage: 80, fileCount: 40 }], }); const config = recommendConfig(analysis); expect(config.routing.mode).toBe('hybrid'); expect(config.domains.enabled).toContain('security-compliance'); }); it('should apply Java project rule', () => { const analysis = createBaseAnalysis({ languages: [{ name: 'java', percentage: 70, fileCount: 35 }], }); const config = recommendConfig(analysis); expect(config.agents.defaultTimeout).toBe(120000); }); }); describe('getApplicableRules', () => { it('should return list of applicable rules', () => { const analysis = createBaseAnalysis({ hasTypeScript: true, frameworks: [{ name: 'vitest', version: '1.0.0', confidence: 1.0 }], hasCIConfig: true, ciProvider: 'github-actions', }); const configurator = createSelfConfigurator(); const rules = configurator.getApplicableRules(analysis); expect(rules).toContain('typescript-vitest'); expect(rules).toContain('has-ci'); expect(rules).toContain('github-actions'); }); it('should return empty array for minimal analysis', () => { const analysis = createBaseAnalysis(); const configurator = createSelfConfigurator({ minimal: true }); const rules = configurator.getApplicableRules(analysis); // Minimal configurator only includes typescript-vitest, large-codebase, small-project // Base analysis doesn't match any of these expect(Array.isArray(rules)).toBe(true); }); }); describe('Domain Recommendations', () => { it('should always include core domains', () => { const analysis = createBaseAnalysis(); const config = recommendConfig(analysis); expect(config.domains.enabled).toContain('test-generation'); expect(config.domains.enabled).toContain('test-execution'); expect(config.domains.enabled).toContain('coverage-analysis'); expect(config.domains.enabled).toContain('learning-optimization'); }); it('should add security for larger projects', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 200, totalLines: 20000, averageFileSize: 100, recommendation: 'moderate', }, }); const config = recommendConfig(analysis); expect(config.domains.enabled).toContain('security-compliance'); }); it('should add code intelligence for TypeScript projects', () => { const analysis = createBaseAnalysis({ hasTypeScript: true, }); const config = recommendConfig(analysis); expect(config.domains.enabled).toContain('code-intelligence'); }); it('should deduplicate domain list', () => { const analysis = createBaseAnalysis({ hasTypeScript: true, hasCIConfig: true, codeComplexity: { totalFiles: 200, totalLines: 20000, averageFileSize: 100, recommendation: 'complex', }, }); const config = recommendConfig(analysis); // Check no duplicates const uniqueDomains = [...new Set(config.domains.enabled)]; expect(config.domains.enabled.length).toBe(uniqueDomains.length); }); }); describe('Worker Recommendations', () => { it('should always include core workers', () => { const analysis = createBaseAnalysis(); const config = recommendConfig(analysis); expect(config.workers.enabled).toContain('pattern-consolidator'); expect(config.workers.enabled).toContain('routing-accuracy-monitor'); }); it('should add coverage scanner for missing coverage', () => { const analysis = createBaseAnalysis({ coverage: { hasReport: false, lines: 0, branches: 0, functions: 0, statements: 0, }, }); const config = recommendConfig(analysis); expect(config.workers.enabled).toContain('coverage-gap-scanner'); }); it('should add flaky detector for many tests', () => { const analysis = createBaseAnalysis({ existingTests: { totalCount: 200, byType: { unit: 150, integration: 40, e2e: 10, other: 0 }, byFramework: { jest: 200 }, directories: ['tests'], }, }); const config = recommendConfig(analysis); expect(config.workers.enabled).toContain('flaky-test-detector'); }); }); describe('Agent Recommendations', () => { it('should recommend 15 agents for monorepos', () => { const analysis = createBaseAnalysis({ projectType: 'monorepo' }); const config = recommendConfig(analysis); expect(config.agents.maxConcurrent).toBe(15); }); it('should recommend 12 agents for large codebases', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 600, totalLines: 60000, averageFileSize: 100, recommendation: 'complex', }, }); // Note: large-codebase rule (>500 files) sets it to 15 const config = recommendConfig(analysis); expect(config.agents.maxConcurrent).toBe(15); }); it('should recommend 5 agents for small projects', () => { const analysis = createBaseAnalysis({ codeComplexity: { totalFiles: 30, totalLines: 3000, averageFileSize: 100, recommendation: 'simple', }, }); const config = recommendConfig(analysis); expect(config.agents.maxConcurrent).toBe(5); }); }); });