#!/usr/bin/env node /** * Test runner for standalone integration tests * Spawns separate Node.js processes for each test file to handle process.exit() calls * * Usage: * node tests/run_all_tests.js # Run all tests * node tests/run_all_tests.js --pattern 'promo/test_*.js' # Run specific pattern * node tests/run_all_tests.js --verbose # Show detailed output * node tests/run_all_tests.js --bail # Stop on first failure */ const path = require('path'); const glob = require('glob'); const { spawn } = require('child_process'); // Parse arguments const args = process.argv.slice(2); let envFile = './environment.env'; let pattern = '**/test_*.js'; // Default: all tests in subdirectories let verbose = false; let bail = false; for (let i = 0; i < args.length; i++) { if (args[i] === '--env' && args[i + 1]) { envFile = args[i + 1]; i++; } else if (args[i] === '--pattern' && args[i + 1]) { pattern = args[i + 1]; i++; } else if (args[i] === '--verbose' || args[i] === '-v') { verbose = true; } else if (args[i] === '--bail' || args[i] === '--stop-on-failure') { bail = true; } } // Load environment BEFORE finding files (in case env affects file discovery) const envPath = path.resolve(process.cwd(), envFile); require('dotenv').config({ path: envPath }); console.log('═══════════════════════════════════════════════════════'); console.log('🧪 AgMission Test Runner'); console.log('═══════════════════════════════════════════════════════'); console.log(`📁 Environment: ${envFile}`); console.log(`🔍 Pattern: ${pattern}`); console.log(`📢 Verbose: ${verbose}`); console.log(`🛑 Stop on failure: ${bail}`); console.log('═══════════════════════════════════════════════════════\n'); // Find all test files const testPattern = `tests/${pattern}`; const files = glob.sync(testPattern, { ignore: [ '**/node_modules/**', '**/organize_tests.js', '**/fix_paths.js', '**/run_all_tests.js', '**/setup.js', '**/*.spec.js', // Skip Mocha tests '**/manual_*.js' // Skip manual scripts ] }); if (files.length === 0) { console.log(`❌ No test files found matching: ${testPattern}`); process.exit(1); } console.log(`📋 Found ${files.length} test files:\n`); files.forEach((f, i) => { console.log(` ${i + 1}. ${f}`); }); console.log(); // Run each test file in separate process let passed = 0; let failed = 0; const failures = []; const startTime = Date.now(); /** * Run a single test file in a separate process */ function runTest(file) { return new Promise((resolve) => { const fileName = path.basename(file); const testStartTime = Date.now(); console.log('─'.repeat(60)); console.log(`🧪 Running: ${file}`); console.log('─'.repeat(60)); const child = spawn('node', [file], { cwd: process.cwd(), stdio: verbose ? 'inherit' : 'pipe', env: process.env }); let output = ''; let errorOutput = ''; if (!verbose) { child.stdout?.on('data', (data) => { output += data.toString(); }); child.stderr?.on('data', (data) => { errorOutput += data.toString(); }); } child.on('close', (code) => { const duration = Date.now() - testStartTime; if (code === 0) { console.log(`✅ PASSED: ${fileName} (${duration}ms)\n`); resolve({ passed: true, file: fileName, duration }); } else { console.log(`❌ FAILED: ${fileName} (${duration}ms)`); console.log(` Exit code: ${code}`); // Show output only on failure (unless verbose) if (!verbose && (output || errorOutput)) { console.log(` Output preview:`); const preview = (errorOutput || output).split('\n').slice(-5).join('\n'); console.log(preview.split('\n').map(l => ' ' + l).join('\n')); } console.log(); resolve({ passed: false, file: fileName, duration, error: `Exit code: ${code}`, output: errorOutput || output }); } }); child.on('error', (error) => { const duration = Date.now() - testStartTime; console.log(`❌ FAILED: ${fileName} (${duration}ms)`); console.error(` Error: ${error.message}\n`); resolve({ passed: false, file: fileName, duration, error: error.message }); }); }); } async function runTests() { for (let i = 0; i < files.length; i++) { const result = await runTest(files[i]); if (result.passed) { passed++; } else { failed++; failures.push(result); // Stop on first failure if --bail flag is set if (bail) { console.log('🛑 Stopping due to test failure (--bail flag)\n'); break; } } } // Summary const totalDuration = Date.now() - startTime; const totalTests = passed + failed; console.log('═══════════════════════════════════════════════════════'); console.log('📊 TEST SUMMARY'); console.log('═══════════════════════════════════════════════════════'); console.log(`✅ Passed: ${passed}/${totalTests}`); console.log(`❌ Failed: ${failed}/${totalTests}`); console.log(`⏱️ Total Duration: ${(totalDuration / 1000).toFixed(2)}s`); if (failures.length > 0) { console.log('\n❌ FAILED TESTS:'); failures.forEach((f, i) => { console.log(` ${i + 1}. ${f.file} - ${f.error}`); }); } console.log('═══════════════════════════════════════════════════════\n'); process.exit(failed > 0 ? 1 : 0); } runTests().catch(err => { console.error('Fatal error running tests:', err); process.exit(1); });