UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

368 lines (306 loc) 13.2 kB
/** * @file Unit tests for WET code analyzer core functionality * @description Tests duplicate code detection, similarity calculation, and refactoring recommendations */ const { analyzeWetCode, calculateDRYScore, generateRefactoringRecommendations } = require('./wetCodeAnalyzer'); const qtests = require('qtests'); const fs = require('fs'); const path = require('path'); /** * Test runner for WET code analyzer */ async function runTests() { console.log('=== Testing WET Code Analyzer Core Functionality ==='); const results = { total: 0, passed: 0 }; // Test duplicate code detection results.total++; try { const tempFile = path.join(__dirname, 'temp-duplicate-test.js'); const duplicateCode = ` function validateEmail(email) { if (!email) return false; if (!email.includes('@')) return false; if (!email.includes('.')) return false; return true; } function checkEmailFormat(emailAddress) { if (!emailAddress) return false; if (!emailAddress.includes('@')) return false; if (!emailAddress.includes('.')) return false; return true; } function isValidEmail(mail) { if (!mail) return false; if (!mail.includes('@')) return false; if (!mail.includes('.')) return false; return true; } `; fs.writeFileSync(tempFile, duplicateCode); const analysis = await analyzeWetCode(tempFile); qtests.assert(typeof analysis === 'object', 'analyzeWetCode should return object'); qtests.assert(Array.isArray(analysis.duplicateGroups), 'Analysis should include duplicate groups'); qtests.assert(analysis.duplicateGroups.length > 0, 'Should detect duplicate code patterns'); qtests.assert(typeof analysis.dryScore === 'number', 'Analysis should include DRY score'); qtests.assert(analysis.dryScore < 100, 'DRY score should reflect code duplication'); fs.unlinkSync(tempFile); console.log('✓ analyzeWetCode correctly detects duplicate code patterns'); results.passed++; } catch (error) { console.log(`✗ analyzeWetCode duplicate detection test failed: ${error.message}`); } // Test similar but not identical code detection results.total++; try { const tempFile = path.join(__dirname, 'temp-similar-test.js'); const similarCode = ` function processUserData(user) { if (!user.name) throw new Error('Name required'); if (!user.email) throw new Error('Email required'); user.name = user.name.trim(); user.email = user.email.toLowerCase(); return user; } function processProductData(product) { if (!product.title) throw new Error('Title required'); if (!product.price) throw new Error('Price required'); product.title = product.title.trim(); product.price = parseFloat(product.price); return product; } function processOrderData(order) { if (!order.items) throw new Error('Items required'); if (!order.total) throw new Error('Total required'); order.items = order.items.filter(item => item.quantity > 0); order.total = parseFloat(order.total); return order; } `; fs.writeFileSync(tempFile, similarCode); const analysis = await analyzeWetCode(tempFile); qtests.assert(analysis.duplicateGroups.length > 0, 'Should detect similar code patterns'); qtests.assert(analysis.summary.totalSimilarBlocks > 0, 'Should count similar blocks'); fs.unlinkSync(tempFile); console.log('✓ analyzeWetCode correctly detects similar code patterns'); results.passed++; } catch (error) { console.log(`✗ analyzeWetCode similar code detection test failed: ${error.message}`); } // Test DRY score calculation results.total++; try { const duplicateBlocks = [ { lines: ['line1', 'line2', 'line3'], similarityScore: 100 }, { lines: ['line1', 'line2', 'line3'], similarityScore: 100 }, { lines: ['line1', 'line2', 'line3'], similarityScore: 100 } ]; const totalBlocks = 10; const dryScore = calculateDRYScore(duplicateBlocks, totalBlocks); qtests.assert(typeof dryScore === 'number', 'calculateDRYScore should return number'); qtests.assert(dryScore >= 0 && dryScore <= 100, 'DRY score should be between 0 and 100'); qtests.assert(dryScore < 100, 'DRY score should be less than 100 when duplicates exist'); // Test with no duplicates const perfectScore = calculateDRYScore([], 10); qtests.assert(perfectScore === 100, 'Perfect DRY score should be 100 with no duplicates'); console.log('✓ calculateDRYScore correctly calculates DRY scores'); results.passed++; } catch (error) { console.log(`✗ calculateDRYScore test failed: ${error.message}`); } // Test refactoring recommendations generation results.total++; try { const duplicateGroups = [ { blocks: [ { file: 'file1.js', startLine: 10, endLine: 15, functionName: 'validateEmail' }, { file: 'file2.js', startLine: 5, endLine: 10, functionName: 'checkEmail' } ], similarityScore: 95, linesOfCode: 6, potentialSavings: 12 }, { blocks: [ { file: 'file1.js', startLine: 20, endLine: 30, functionName: 'processData' }, { file: 'file2.js', startLine: 15, endLine: 25, functionName: 'handleData' }, { file: 'file3.js', startLine: 8, endLine: 18, functionName: 'transformData' } ], similarityScore: 88, linesOfCode: 11, potentialSavings: 33 } ]; const recommendations = generateRefactoringRecommendations(duplicateGroups); qtests.assert(Array.isArray(recommendations), 'generateRefactoringRecommendations should return array'); qtests.assert(recommendations.length > 0, 'Should generate recommendations for duplicate groups'); qtests.assert(recommendations.every(rec => typeof rec.priority === 'string'), 'Each recommendation should have priority'); qtests.assert(recommendations.every(rec => typeof rec.estimatedSavings === 'number'), 'Each recommendation should have estimated savings'); qtests.assert(recommendations.every(rec => typeof rec.refactoringStrategy === 'string'), 'Each recommendation should have refactoring strategy'); console.log('✓ generateRefactoringRecommendations correctly generates actionable recommendations'); results.passed++; } catch (error) { console.log(`✗ generateRefactoringRecommendations test failed: ${error.message}`); } // Test complex nested duplication results.total++; try { const tempFile = path.join(__dirname, 'temp-nested-test.js'); const nestedCode = ` class UserService { validateUser(user) { if (!user) throw new Error('User is required'); if (!user.id) throw new Error('User ID is required'); if (!user.name) throw new Error('User name is required'); if (!user.email) throw new Error('User email is required'); return true; } processUser(user) { if (!user) throw new Error('User is required'); if (!user.id) throw new Error('User ID is required'); if (!user.name) throw new Error('User name is required'); if (!user.email) throw new Error('User email is required'); // Additional processing user.processedAt = new Date(); return user; } } class ProductService { validateProduct(product) { if (!product) throw new Error('Product is required'); if (!product.id) throw new Error('Product ID is required'); if (!product.name) throw new Error('Product name is required'); if (!product.price) throw new Error('Product price is required'); return true; } processProduct(product) { if (!product) throw new Error('Product is required'); if (!product.id) throw new Error('Product ID is required'); if (!product.name) throw new Error('Product name is required'); if (!product.price) throw new Error('Product price is required'); // Additional processing product.processedAt = new Date(); return product; } } `; fs.writeFileSync(tempFile, nestedCode); const analysis = await analyzeWetCode(tempFile); qtests.assert(analysis.duplicateGroups.length > 0, 'Should detect nested duplication patterns'); qtests.assert(analysis.summary.duplicateBlockCount > 2, 'Should identify multiple duplicate blocks'); fs.unlinkSync(tempFile); console.log('✓ analyzeWetCode correctly detects complex nested duplication'); results.passed++; } catch (error) { console.log(`✗ analyzeWetCode nested duplication test failed: ${error.message}`); } // Test clean code with minimal duplication results.total++; try { const tempFile = path.join(__dirname, 'temp-clean-test.js'); const cleanCode = ` // Clean, DRY code with good abstraction const validators = { required: (value, fieldName) => { if (!value) throw new Error(\`\${fieldName} is required\`); }, email: (value) => { if (!value.includes('@')) throw new Error('Invalid email format'); } }; function validateUser(user) { validators.required(user, 'User'); validators.required(user.name, 'User name'); validators.email(user.email); return true; } function validateProduct(product) { validators.required(product, 'Product'); validators.required(product.name, 'Product name'); validators.required(product.price, 'Product price'); return true; } // Different logic, no duplication function calculateTax(amount, rate) { return amount * rate; } function formatCurrency(amount) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount); } `; fs.writeFileSync(tempFile, cleanCode); const analysis = await analyzeWetCode(tempFile); qtests.assert(analysis.dryScore > 80, 'Clean code should have high DRY score'); qtests.assert(analysis.duplicateGroups.length === 0 || analysis.duplicateGroups.length < 2, 'Clean code should have minimal duplication'); fs.unlinkSync(tempFile); console.log('✓ analyzeWetCode correctly identifies clean, DRY code'); results.passed++; } catch (error) { console.log(`✗ analyzeWetCode clean code test failed: ${error.message}`); } // Test error handling results.total++; try { const analysis = await analyzeWetCode('/non/existent/file.js'); qtests.assert(typeof analysis === 'object', 'Should handle non-existent files gracefully'); qtests.assert(analysis.duplicateGroups.length === 0, 'Should return empty results for non-existent files'); qtests.assert(analysis.dryScore === 100, 'Should return perfect DRY score for non-existent files'); console.log('✓ analyzeWetCode handles non-existent files gracefully'); results.passed++; } catch (error) { console.log(`✗ analyzeWetCode error handling test failed: ${error.message}`); } // Test string concatenation detection results.total++; try { const tempFile = path.join(__dirname, 'temp-strings-test.js'); const stringCode = ` function buildUserMessage(user) { let message = ''; message += 'Hello '; message += user.name; message += ', welcome to '; message += user.site; message += '!'; return message; } function buildProductMessage(product) { let message = ''; message += 'Product '; message += product.name; message += ' costs '; message += product.price; message += ' dollars'; return message; } function buildOrderMessage(order) { let message = ''; message += 'Order '; message += order.id; message += ' contains '; message += order.items.length; message += ' items'; return message; } `; fs.writeFileSync(tempFile, stringCode); const analysis = await analyzeWetCode(tempFile); qtests.assert(analysis.duplicateGroups.length > 0, 'Should detect string concatenation patterns'); fs.unlinkSync(tempFile); console.log('✓ analyzeWetCode correctly detects string concatenation duplication'); results.passed++; } catch (error) { console.log(`✗ analyzeWetCode string concatenation test failed: ${error.message}`); } console.log(`=== WET Code Analyzer Test Results ===`); console.log(`Tests passed: ${results.passed}/${results.total}`); console.log(`Success rate: ${((results.passed / results.total) * 100).toFixed(1)}%`); return results; } module.exports = { runTests };