UNPKG

agentsqripts

Version:

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

483 lines (398 loc) 14.7 kB
/** * @file Unit tests for static bug analysis core functionality * @description Tests bug pattern detection, logic error identification, and fix recommendations */ const { analyzeFileStaticBugs, analyzeProjectStaticBugs, detectBugPatterns, generateBugRecommendations } = require('./analyzeStaticBugs'); const qtests = require('qtests'); const fs = require('fs'); const path = require('path'); /** * Test runner for static bug analysis */ async function runTests() { console.log('=== Testing Static Bug Analysis Core Functionality ==='); const results = { total: 0, passed: 0 }; // Test null reference detection results.total++; try { const tempFile = path.join(__dirname, 'temp-null-ref-test.js'); const nullRefCode = ` function processUser(user) { // Potential null reference - no null check return user.name.toUpperCase(); } function getEmail(person) { // Chain of property access without null checks return person.contact.email.toLowerCase(); } function formatData(data) { // Accessing property on potentially undefined object const items = data.items; return items.map(item => item.name); } // Safe null checking (should not trigger) function safeProcessUser(user) { if (user && user.name) { return user.name.toUpperCase(); } return ''; } `; fs.writeFileSync(tempFile, nullRefCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(typeof analysis === 'object', 'analyzeFileStaticBugs should return object'); qtests.assert(Array.isArray(analysis.bugs), 'Analysis should include bugs array'); qtests.assert(analysis.bugs.length > 0, 'Should detect null reference bugs'); qtests.assert(analysis.bugs.some(bug => bug.type.includes('null_reference') || bug.category === 'Null Reference' ), 'Should identify null reference patterns'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly detects null reference bugs'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs null reference test failed: ${error.message}`); } // Test undefined variable detection results.total++; try { const tempFile = path.join(__dirname, 'temp-undefined-test.js'); const undefinedCode = ` function calculate() { // Using undefined variable return total * 1.1; } function processArray() { // Typo in variable name const items = [1, 2, 3]; return item.map(x => x * 2); // 'item' instead of 'items' } function handleData() { let result; if (Math.random() > 0.5) { result = 'success'; } // result might be undefined here return result.toUpperCase(); } // Proper variable declaration (should not trigger) function properCalculate() { const total = 100; return total * 1.1; } `; fs.writeFileSync(tempFile, undefinedCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(analysis.bugs.some(bug => bug.type.includes('undefined_variable') || bug.category === 'Undefined Variable' ), 'Should identify undefined variable bugs'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly detects undefined variable bugs'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs undefined variable test failed: ${error.message}`); } // Test logical error detection results.total++; try { const tempFile = path.join(__dirname, 'temp-logic-test.js'); const logicCode = ` function isEven(num) { // Logic error - should use modulo return num / 2 === Math.floor(num / 2); } function findMax(arr) { let max = 0; // Logic error - should initialize with first element or -Infinity for (let i = 0; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } function validateAge(age) { // Logic error - age validation if (age > 0 && age < 150) { return true; } return false; // Missing handling for edge case: age = 0 } function processItems(items) { // Off-by-one error for (let i = 1; i <= items.length; i++) { console.log(items[i]); // Will be undefined on last iteration } } `; fs.writeFileSync(tempFile, logicCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(analysis.bugs.some(bug => bug.type.includes('logic_error') || bug.type.includes('off_by_one') || bug.category === 'Logic Error' ), 'Should identify logical error bugs'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly detects logical error bugs'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs logic error test failed: ${error.message}`); } // Test type mismatch detection results.total++; try { const tempFile = path.join(__dirname, 'temp-type-test.js'); const typeCode = ` function addNumbers(a, b) { // Type confusion - string concatenation instead of addition return a + b; // Could be '12' instead of 3 if a='1', b='2' } function processData(data) { // Assuming array but could be object return data.map(item => item.name); // Will fail if data is not array } function compareValues(x, y) { // Type coercion issues if (x == y) { // Should use strict equality return true; } return false; } function formatNumber(num) { // Type assumption without validation return num.toFixed(2); // Will fail if num is not a number } // Type safe implementation (should not trigger) function safeAdd(a, b) { if (typeof a === 'number' && typeof b === 'number') { return a + b; } throw new Error('Both arguments must be numbers'); } `; fs.writeFileSync(tempFile, typeCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(analysis.bugs.some(bug => bug.type.includes('type_mismatch') || bug.type.includes('type_coercion') || bug.category === 'Type Error' ), 'Should identify type mismatch bugs'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly detects type mismatch bugs'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs type mismatch test failed: ${error.message}`); } // Test error handling bugs results.total++; try { const tempFile = path.join(__dirname, 'temp-error-handling-test.js'); const errorCode = ` async function fetchData(url) { // Missing error handling const response = await fetch(url); const data = await response.json(); return data; } function parseJSON(jsonString) { // No try-catch for JSON.parse return JSON.parse(jsonString); } function divide(a, b) { // No check for division by zero return a / b; } function processFile(filename) { // File operations without error handling const content = fs.readFileSync(filename); return content.toString(); } // Proper error handling (should not trigger) async function safeFetchData(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error('Network response was not ok'); } const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); return null; } } `; fs.writeFileSync(tempFile, errorCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(analysis.bugs.some(bug => bug.type.includes('missing_error_handling') || bug.type.includes('unhandled_exception') || bug.category === 'Error Handling' ), 'Should identify error handling bugs'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly detects error handling bugs'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs error handling test failed: ${error.message}`); } // Test async/await pattern bugs results.total++; try { const tempFile = path.join(__dirname, 'temp-async-test.js'); const asyncCode = ` function fetchUserData(userId) { // Missing await keyword return fetch('/api/users/' + userId); } async function processUsers() { const users = [1, 2, 3]; const results = []; // Sequential processing in loop (performance issue) for (const userId of users) { const user = await fetchUser(userId); results.push(user); } return results; } async function handleRequests() { // Forgotten await const promise1 = fetchData('/api/data1'); const promise2 = fetchData('/api/data2'); // Using promises incorrectly return promise1 + promise2; } // Proper async implementation (should not trigger) async function properFetchUserData(userId) { try { const response = await fetch('/api/users/' + userId); return await response.json(); } catch (error) { console.error('Error fetching user:', error); return null; } } `; fs.writeFileSync(tempFile, asyncCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(analysis.bugs.some(bug => bug.type.includes('missing_await') || bug.type.includes('async_pattern') || bug.category === 'Async/Await' ), 'Should identify async/await pattern bugs'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly detects async/await bugs'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs async/await test failed: ${error.message}`); } // Test bug recommendations generation results.total++; try { const testBugs = [ { type: 'null_reference', severity: 'HIGH', line: 15, variable: 'user', pattern: 'property access without null check' }, { type: 'undefined_variable', severity: 'HIGH', line: 25, variable: 'total', pattern: 'variable used before declaration' }, { type: 'type_mismatch', severity: 'MEDIUM', line: 35, expected: 'number', actual: 'string', pattern: 'type coercion in comparison' } ]; const recommendations = generateBugRecommendations(testBugs); qtests.assert(Array.isArray(recommendations), 'generateBugRecommendations should return array'); qtests.assert(recommendations.length > 0, 'Should generate recommendations for bugs'); qtests.assert(recommendations.every(rec => typeof rec.priority === 'string'), 'Each recommendation should have priority'); qtests.assert(recommendations.every(rec => typeof rec.fix === 'string'), 'Each recommendation should have fix suggestion'); qtests.assert(recommendations.every(rec => typeof rec.explanation === 'string'), 'Each recommendation should have explanation'); console.log('✓ generateBugRecommendations correctly generates actionable recommendations'); results.passed++; } catch (error) { console.log(`✗ generateBugRecommendations test failed: ${error.message}`); } // Test clean, bug-free code results.total++; try { const tempFile = path.join(__dirname, 'temp-clean-test.js'); const cleanCode = ` // Well-written, bug-free code function safeProcessUser(user) { if (!user || typeof user !== 'object') { throw new Error('Invalid user object'); } if (!user.name || typeof user.name !== 'string') { throw new Error('User name is required and must be a string'); } return user.name.toUpperCase(); } function safeAdd(a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw new Error('Both arguments must be numbers'); } return a + b; } async function safeFetchData(url) { try { if (typeof url !== 'string' || !url.trim()) { throw new Error('Valid URL is required'); } const response = await fetch(url); if (!response.ok) { throw new Error(\`HTTP error! status: \${response.status}\`); } const data = await response.json(); return data; } catch (error) { console.error('Error fetching data:', error); throw error; } } function findMax(arr) { if (!Array.isArray(arr) || arr.length === 0) { throw new Error('Array must be non-empty'); } let max = arr[0]; for (let i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } `; fs.writeFileSync(tempFile, cleanCode); const analysis = await analyzeFileStaticBugs(tempFile); qtests.assert(analysis.bugs.length === 0 || analysis.bugs.length < 2, 'Clean code should have minimal static bugs'); fs.unlinkSync(tempFile); console.log('✓ analyzeFileStaticBugs correctly identifies clean, bug-free code'); results.passed++; } catch (error) { console.log(`✗ analyzeFileStaticBugs clean code test failed: ${error.message}`); } console.log(`=== Static Bug Analysis 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 };