agmission/Development/server/tests/run_all_tests.js

201 lines
6.3 KiB
JavaScript
Executable File

#!/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);
});