236 lines
7.4 KiB
JavaScript
236 lines
7.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Automated Test Conversion Script
|
|
* Converts standalone Node.js test scripts to Mocha format
|
|
*
|
|
* Usage:
|
|
* node tests/convert_to_mocha.js [--dry-run] [--pattern 'glob']
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const glob = require('glob');
|
|
|
|
// Parse arguments
|
|
const args = process.argv.slice(2);
|
|
let dryRun = false;
|
|
let pattern = 'tests/**/test_*.js';
|
|
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--dry-run') {
|
|
dryRun = true;
|
|
} else if (args[i] === '--pattern' && args[i + 1]) {
|
|
pattern = args[i + 1];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
console.log('║ Mocha Test Conversion Script ║');
|
|
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
|
console.log(`Pattern: ${pattern}`);
|
|
console.log(`Dry Run: ${dryRun}\n`);
|
|
|
|
/**
|
|
* Convert a standalone test file to Mocha format
|
|
*/
|
|
function convertToMocha(filePath, content) {
|
|
let converted = content;
|
|
|
|
// Step 1: Remove shebang and convert header
|
|
converted = converted.replace(/^#!\/usr\/bin\/env node\n/, '');
|
|
|
|
// Step 2: Remove environment loading boilerplate (replaced by tests/setup.js)
|
|
const envLoadingPattern = /\/\/ Parse.*?const envPath.*?require\('dotenv'\)\.config\(\{ path: envPath \}\);?\n+/s;
|
|
converted = converted.replace(envLoadingPattern, '');
|
|
|
|
// Step 3: Add Mocha/Chai imports if not present
|
|
if (!converted.includes('require(\'chai\')')) {
|
|
const firstRequire = converted.search(/^const |^require\(/m);
|
|
if (firstRequire !== -1) {
|
|
converted = converted.slice(0, firstRequire) +
|
|
`const { expect } = require('chai');\n` +
|
|
converted.slice(firstRequire);
|
|
} else {
|
|
converted = `const { expect } = require('chai');\n\n` + converted;
|
|
}
|
|
}
|
|
|
|
// Step 4: Extract test name from file path
|
|
const testName = path.basename(filePath, '.js')
|
|
.replace(/^test_/, '')
|
|
.replace(/_/g, ' ')
|
|
.replace(/\b\w/g, l => l.toUpperCase());
|
|
|
|
// Step 5: Detect main async function pattern
|
|
const mainFunctionPattern = /async function (main|test\w+)\(\) \{[\s\S]*?\}\s*(?:main\(\)|test\w+\(\)).*?\.catch/;
|
|
const hasMainFunction = mainFunctionPattern.test(converted);
|
|
|
|
if (hasMainFunction) {
|
|
// Extract main function body
|
|
const mainMatch = converted.match(/async function (main|test\w+)\(\) \{\s*([\s\S]*?)\s*\}\s*(?:main\(\)|test\w+\(\))/);
|
|
if (mainMatch) {
|
|
const functionBody = mainMatch[2];
|
|
|
|
// Wrap in describe block
|
|
converted = converted.replace(
|
|
mainFunctionPattern,
|
|
`describe('${testName}', function() {
|
|
this.timeout(60000);
|
|
|
|
it('should complete successfully', async function() {
|
|
${functionBody.split('\n').map(line => ' ' + line).join('\n')}
|
|
});
|
|
});`
|
|
);
|
|
}
|
|
} else {
|
|
// For simple scripts without main function
|
|
// Try to detect test sections and wrap them
|
|
const lines = converted.split('\n');
|
|
let inTestSection = false;
|
|
let testSections = [];
|
|
let currentSection = [];
|
|
|
|
for (const line of lines) {
|
|
if (line.includes('console.log') && (line.includes('Test ') || line.includes('===') || line.includes('───'))) {
|
|
if (currentSection.length > 0) {
|
|
testSections.push(currentSection.join('\n'));
|
|
currentSection = [];
|
|
}
|
|
inTestSection = true;
|
|
}
|
|
if (inTestSection) {
|
|
currentSection.push(line);
|
|
}
|
|
}
|
|
|
|
if (currentSection.length > 0) {
|
|
testSections.push(currentSection.join('\n'));
|
|
}
|
|
}
|
|
|
|
// Step 6: Convert console.log assertions to expect()
|
|
// This is a simple heuristic - manual review still needed
|
|
converted = converted.replace(
|
|
/if \((.*?) === (.*?)\) \{\s*console\.log\('✓.*?'\);?\s*\} else \{\s*console\.log\('✗.*?'\);?\s*(?:process\.exit|return)\(1\);?\s*\}/g,
|
|
'expect($1).to.equal($2);'
|
|
);
|
|
|
|
converted = converted.replace(
|
|
/if \((.*?) !== (.*?)\) \{\s*console\.log\('✓.*?'\);?\s*\} else \{\s*console\.log\('✗.*?'\);?\s*(?:process\.exit|return)\(1\);?\s*\}/g,
|
|
'expect($1).to.not.equal($2);'
|
|
);
|
|
|
|
// Step 7: Remove process.exit calls
|
|
converted = converted.replace(/process\.exit\(\d+\);?/g, '');
|
|
converted = converted.replace(/\.then\(exitCode => process\.exit\(exitCode\)\)/g, '');
|
|
converted = converted.replace(/\.catch\(error => \{\s*console\.error.*?process\.exit\(1\);?\s*\}\);?/gs, '');
|
|
|
|
// Step 8: Handle try-catch blocks that return exit codes
|
|
converted = converted.replace(
|
|
/try \{([\s\S]*?)\s+return 0;\s*\} catch \(error\) \{[\s\S]*?return 1;\s*\}/g,
|
|
'try {$1\n } catch (error) {\n throw error;\n }'
|
|
);
|
|
|
|
return converted;
|
|
}
|
|
|
|
/**
|
|
* Find all test files matching pattern
|
|
*/
|
|
function findTestFiles() {
|
|
const exclude = [
|
|
'**/node_modules/**',
|
|
'**/run_all_tests.js',
|
|
'**/organize_tests.js',
|
|
'**/fix_paths.js',
|
|
'**/setup.js',
|
|
'**/convert_to_mocha.js',
|
|
'**/*.spec.js',
|
|
'**/manual_*.js'
|
|
];
|
|
|
|
return glob.sync(pattern, {
|
|
ignore: exclude,
|
|
absolute: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Main execution
|
|
*/
|
|
async function main() {
|
|
const files = findTestFiles();
|
|
|
|
if (files.length === 0) {
|
|
console.log('No test files found matching pattern.');
|
|
return;
|
|
}
|
|
|
|
console.log(`Found ${files.length} test files to convert:\n`);
|
|
|
|
let converted = 0;
|
|
let skipped = 0;
|
|
let errors = 0;
|
|
|
|
for (const filePath of files) {
|
|
const relativePath = path.relative(process.cwd(), filePath);
|
|
|
|
try {
|
|
// Skip files that are already converted (contain describe/it)
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
|
|
if (content.includes('describe(') && content.includes('it(')) {
|
|
console.log(`⊘ ${relativePath} - Already Mocha format`);
|
|
skipped++;
|
|
continue;
|
|
}
|
|
|
|
const convertedContent = convertToMocha(filePath, content);
|
|
|
|
if (dryRun) {
|
|
console.log(`✓ ${relativePath} - Would convert`);
|
|
} else {
|
|
// Backup original
|
|
const backupPath = filePath + '.pre-mocha-backup';
|
|
fs.writeFileSync(backupPath, content, 'utf8');
|
|
|
|
// Write converted
|
|
fs.writeFileSync(filePath, convertedContent, 'utf8');
|
|
console.log(`✓ ${relativePath} - Converted (backup: ${path.basename(backupPath)})`);
|
|
}
|
|
|
|
converted++;
|
|
|
|
} catch (error) {
|
|
console.error(`✗ ${relativePath} - Error: ${error.message}`);
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
console.log('\n' + '═'.repeat(64));
|
|
console.log('Summary');
|
|
console.log('═'.repeat(64));
|
|
console.log(`Converted: ${converted}`);
|
|
console.log(`Skipped: ${skipped} (already Mocha format)`);
|
|
console.log(`Errors: ${errors}`);
|
|
console.log(`Total: ${files.length}`);
|
|
|
|
if (dryRun) {
|
|
console.log('\n⚠ Dry run mode - no files were modified');
|
|
console.log('Run without --dry-run to apply changes');
|
|
} else {
|
|
console.log('\n✓ Conversion complete');
|
|
console.log('⚠ IMPORTANT: Review and test converted files manually!');
|
|
console.log(' Automated conversion handles common patterns but may need adjustments.');
|
|
console.log(` Backups saved with .pre-mocha-backup extension`);
|
|
}
|
|
}
|
|
|
|
main().catch(error => {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
});
|