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
JavaScript
/**
* @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 };