UNPKG

agentic-qe

Version:

Agentic Quality Engineering Fleet System - AI-driven quality management platform

424 lines • 18.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GenerateCommand = void 0; const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); class GenerateCommand { static async execute(target, options) { console.log(chalk_1.default.blue.bold('\n🧠 Generating Test Suites with AI Analysis\n')); try { const spinner = (0, ora_1.default)('Initializing test generation...').start(); // Validate inputs await this.validateInputs(target, options); spinner.text = 'Analyzing source code...'; // Analyze source code const analysis = await this.analyzeSourceCode(options.path); spinner.text = 'Generating test strategies...'; // Generate test specifications const testSpecs = await this.generateTestSpecifications(target, options, analysis); spinner.text = 'Creating test files...'; // Generate actual test files const generatedTests = await this.generateTestFiles(testSpecs, options); spinner.text = 'Setting up test infrastructure...'; // Setup test infrastructure await this.setupTestInfrastructure(options); spinner.succeed(chalk_1.default.green('Test generation completed successfully!')); // Display summary this.displayGenerationSummary(generatedTests, options); // Store progress in coordination memory await this.storeGenerationProgress(generatedTests); } catch (error) { console.error(chalk_1.default.red('āŒ Test generation failed:'), error.message); if (options.verbose) { console.error(chalk_1.default.gray(error.stack)); } process.exit(1); } } static async validateInputs(target, options) { const validTargets = ['tests', 'integration', 'performance', 'security']; if (!validTargets.includes(target)) { throw new Error(`Invalid target '${target}'. Must be one of: ${validTargets.join(', ')}`); } const validTypes = ['unit', 'integration', 'e2e', 'performance', 'security']; if (!validTypes.includes(options.type)) { throw new Error(`Invalid type '${options.type}'. Must be one of: ${validTypes.join(', ')}`); } const coverageTarget = parseInt(options.coverageTarget); if (coverageTarget < 0 || coverageTarget > 100) { throw new Error('Coverage target must be between 0 and 100'); } if (!await fs.pathExists(options.path)) { throw new Error(`Source path '${options.path}' does not exist`); } if (options.fromSwagger && !await fs.pathExists(options.fromSwagger)) { throw new Error(`Swagger file '${options.fromSwagger}' does not exist`); } } static async analyzeSourceCode(sourcePath) { const analysis = { files: [], complexity: 0, testable_functions: [], dependencies: [], coverage_gaps: [], risk_areas: [] }; // Recursively analyze source files const files = await this.getSourceFiles(sourcePath); for (const file of files) { const fileAnalysis = await this.analyzeFile(file); analysis.files.push(fileAnalysis); analysis.complexity += fileAnalysis.complexity; analysis.testable_functions.push(...fileAnalysis.functions); } return analysis; } static async getSourceFiles(sourcePath) { const files = []; const extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cs', '.go']; const traverse = async (dir) => { const items = await fs.readdir(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = await fs.stat(fullPath); if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') { await traverse(fullPath); } else if (stat.isFile() && extensions.some(ext => item.endsWith(ext))) { files.push(fullPath); } } }; await traverse(sourcePath); return files; } static async analyzeFile(filePath) { const content = await fs.readFile(filePath, 'utf-8'); const ext = path.extname(filePath); // Basic analysis - would be enhanced with actual AST parsing const lines = content.split('\n'); const functions = this.extractFunctions(content, ext); const complexity = this.calculateComplexity(content); return { path: filePath, extension: ext, lines: lines.length, functions, complexity, imports: this.extractImports(content, ext), exports: this.extractExports(content, ext) }; } static extractFunctions(content, extension) { const functions = []; // Simple regex patterns for different languages const patterns = { '.js': [/function\s+(\w+)\s*\(/g, /(\w+)\s*[=:]\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>)/g], '.ts': [/function\s+(\w+)\s*\(/g, /(\w+)\s*[=:]\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>)/g], '.py': [/def\s+(\w+)\s*\(/g], '.java': [/(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)*(\w+)\s*\(/g], }; const funcPatterns = patterns[extension] || patterns['.js']; funcPatterns.forEach(pattern => { let match; while ((match = pattern.exec(content)) !== null) { functions.push({ name: match[1], line: content.substring(0, match.index).split('\n').length, type: 'function' }); } }); return functions; } static calculateComplexity(content) { // Basic cyclomatic complexity calculation const complexityKeywords = [ 'if', 'else', 'while', 'for', 'switch', 'case', 'catch', 'try', '&&', '||', '?', ':', 'break', 'continue' ]; let complexity = 1; // Base complexity complexityKeywords.forEach(keyword => { const regex = new RegExp(`\\b${keyword}\\b`, 'g'); const matches = content.match(regex); if (matches) { complexity += matches.length; } }); return complexity; } static extractImports(content, extension) { const imports = []; if (extension === '.js' || extension === '.ts') { const importRegex = /import\s+.*?\s+from\s+['"`]([^'"`]+)['"`]/g; let match; while ((match = importRegex.exec(content)) !== null) { imports.push(match[1]); } } return imports; } static extractExports(content, extension) { const exports = []; if (extension === '.js' || extension === '.ts') { const exportRegex = /export\s+(?:default\s+)?(?:class|function|const|let|var)\s+(\w+)/g; let match; while ((match = exportRegex.exec(content)) !== null) { exports.push(match[1]); } } return exports; } static async generateTestSpecifications(target, options, analysis) { const testSpecs = { target, type: options.type, framework: options.framework, coverageTarget: parseInt(options.coverageTarget), files: [], strategies: [], metadata: { generated: new Date().toISOString(), sourceFiles: analysis.files.length, functions: analysis.testable_functions.length, complexity: analysis.complexity } }; // Generate test specifications for each file for (const file of analysis.files) { const fileTestSpec = this.generateFileTestSpec(file, options); testSpecs.files.push(fileTestSpec); } // Generate testing strategies testSpecs.strategies = this.generateTestingStrategies(options, analysis); return testSpecs; } static generateFileTestSpec(fileAnalysis, options) { const testFile = this.getTestFilePath(fileAnalysis.path, options); return { sourceFile: fileAnalysis.path, testFile, functions: fileAnalysis.functions.map((func) => ({ name: func.name, tests: this.generateFunctionTests(func, options) })), coverage: { target: parseInt(options.coverageTarget), strategy: 'comprehensive' } }; } static getTestFilePath(sourcePath, options) { const relativePath = path.relative(options.path, sourcePath); const parsedPath = path.parse(relativePath); const testFileName = `${parsedPath.name}.test${parsedPath.ext}`; return path.join(options.output, parsedPath.dir, testFileName); } static generateFunctionTests(func, options) { const tests = []; // Basic test cases tests.push({ name: `should ${func.name} with valid input`, type: 'happy-path', description: `Test ${func.name} function with valid parameters` }); tests.push({ name: `should handle edge cases for ${func.name}`, type: 'edge-case', description: `Test ${func.name} function with edge case scenarios` }); tests.push({ name: `should handle errors in ${func.name}`, type: 'error-handling', description: `Test ${func.name} function error handling` }); // Property-based testing if (options.propertyBased) { tests.push({ name: `property-based tests for ${func.name}`, type: 'property-based', description: `Generated property-based tests for ${func.name}` }); } return tests; } static generateTestingStrategies(options, analysis) { const strategies = []; // Coverage strategy strategies.push({ type: 'coverage', target: parseInt(options.coverageTarget), method: 'comprehensive', focus: ['statements', 'branches', 'functions'] }); // Complexity-based strategy if (analysis.complexity > 10) { strategies.push({ type: 'complexity-focused', target: 'high-complexity-functions', method: 'intensive-testing' }); } // Framework-specific strategy strategies.push({ type: 'framework-optimized', framework: options.framework, patterns: this.getFrameworkPatterns(options.framework) }); return strategies; } static getFrameworkPatterns(framework) { const patterns = { 'jest': ['describe-it', 'beforeEach-afterEach', 'mock-spy'], 'mocha': ['describe-it', 'before-after', 'chai-assertions'], 'pytest': ['fixtures', 'parametrize', 'markers'], 'junit': ['test-annotations', 'setup-teardown', 'assertions'] }; return patterns[framework] || patterns['jest']; } static async generateTestFiles(testSpecs, options) { const generatedTests = []; // Ensure output directory exists await fs.ensureDir(options.output); for (const fileSpec of testSpecs.files) { const testContent = await this.generateTestFileContent(fileSpec, options); // Ensure test file directory exists await fs.ensureDir(path.dirname(fileSpec.testFile)); // Write test file await fs.writeFile(fileSpec.testFile, testContent); generatedTests.push({ sourceFile: fileSpec.sourceFile, testFile: fileSpec.testFile, testsCount: fileSpec.functions.reduce((sum, func) => sum + func.tests.length, 0) }); } return generatedTests; } static async generateTestFileContent(fileSpec, options) { const framework = options.framework; const sourceFile = fileSpec.sourceFile; const relativePath = path.relative(path.dirname(fileSpec.testFile), sourceFile); let content = ''; // Framework-specific imports and setup if (framework === 'jest') { content += `import { ${fileSpec.functions.map((f) => f.name).join(', ')} } from '${relativePath}';\n\n`; content += `describe('${path.basename(sourceFile)}', () => {\n`; for (const func of fileSpec.functions) { content += ` describe('${func.name}', () => {\n`; for (const test of func.tests) { content += ` it('${test.name}', () => {\n`; content += ` // ${test.description}\n`; content += ` // TODO: Implement test logic\n`; content += ` expect(${func.name}).toBeDefined();\n`; content += ` });\n\n`; } content += ` });\n\n`; } content += `});\n`; } return content; } static async setupTestInfrastructure(options) { // Create test configuration files const configDir = '.agentic-qe/config'; await fs.ensureDir(configDir); // Jest configuration if (options.framework === 'jest') { const jestConfig = { testEnvironment: 'node', collectCoverage: true, coverageDirectory: 'coverage', coverageReporters: ['text', 'lcov', 'html'], testMatch: ['**/*.test.js', '**/*.test.ts'], coverageThreshold: { global: { branches: parseInt(options.coverageTarget), functions: parseInt(options.coverageTarget), lines: parseInt(options.coverageTarget), statements: parseInt(options.coverageTarget) } } }; await fs.writeJson(`${configDir}/jest.config.json`, jestConfig, { spaces: 2 }); } // Create test scripts const scriptsDir = '.agentic-qe/scripts'; await fs.ensureDir(scriptsDir); const testScript = `#!/bin/bash # Auto-generated test execution script echo "Running ${options.framework} tests..." ${options.framework} --config .agentic-qe/config/${options.framework}.config.json `; await fs.writeFile(`${scriptsDir}/run-tests.sh`, testScript); await fs.chmod(`${scriptsDir}/run-tests.sh`, '755'); } static displayGenerationSummary(generatedTests, options) { console.log(chalk_1.default.yellow('\nšŸ“Š Test Generation Summary:')); console.log(chalk_1.default.gray(` Framework: ${options.framework}`)); console.log(chalk_1.default.gray(` Coverage Target: ${options.coverageTarget}%`)); console.log(chalk_1.default.gray(` Files Generated: ${generatedTests.length}`)); const totalTests = generatedTests.reduce((sum, file) => sum + file.testsCount, 0); console.log(chalk_1.default.gray(` Total Tests: ${totalTests}`)); if (options.propertyBased) { console.log(chalk_1.default.gray(` Property-based Testing: Enabled`)); } if (options.mutationTesting) { console.log(chalk_1.default.gray(` Mutation Testing: Enabled`)); } console.log(chalk_1.default.yellow('\nšŸ“ Generated Files:')); generatedTests.forEach(test => { console.log(chalk_1.default.gray(` ${test.testFile} (${test.testsCount} tests)`)); }); console.log(chalk_1.default.yellow('\nšŸ’” Next Steps:')); console.log(chalk_1.default.gray(' 1. Review generated tests and add implementation details')); console.log(chalk_1.default.gray(' 2. Run tests: agentic-qe run tests --parallel')); console.log(chalk_1.default.gray(' 3. Analyze coverage: agentic-qe analyze coverage --gaps')); } static async storeGenerationProgress(generatedTests) { const progress = { timestamp: new Date().toISOString(), files: generatedTests.length, tests: generatedTests.reduce((sum, file) => sum + file.testsCount, 0), status: 'completed' }; // Store in coordination memory for other agents const coordinationScript = ` npx claude-flow@alpha memory store --key "agentic-qe/generation/progress" --value '${JSON.stringify(progress)}' npx claude-flow@alpha hooks notify --message "Test generation completed: ${progress.tests} tests in ${progress.files} files" `; await fs.writeFile('.agentic-qe/scripts/store-generation-progress.sh', coordinationScript); await fs.chmod('.agentic-qe/scripts/store-generation-progress.sh', '755'); } } exports.GenerateCommand = GenerateCommand; //# sourceMappingURL=generate.js.map