seogeneratorserver/tests/runners/SystematicTestRunner.js

594 lines
20 KiB
JavaScript

// ========================================
// RUNNER SYSTÉMATIQUE - ORCHESTRATION COMPLÈTE
// Exécution coordonnée de tous les tests avec validation IA
// ========================================
const { ModuleTestGenerator } = require('../systematic/ModuleTestGenerator');
const { AIContentValidator } = require('../validators/AIContentValidator');
const { QualityMetrics } = require('../validators/QualityMetrics');
const { AntiDetectionValidator } = require('../validators/AntiDetectionValidator');
const { PersonalityValidator } = require('../validators/PersonalityValidator');
const { spawn } = require('child_process');
const fs = require('fs').promises;
const path = require('path');
const { logSh } = require('../../lib/ErrorReporting');
/**
* Runner systématique pour validation complète de tous les modules
* Combine tests automatisés + validation IA + métriques de qualité
*/
class SystematicTestRunner {
static VALIDATION_MATRIX = {
'content-generation': {
validators: ['quality', 'anti-detection', 'personality'],
threshold: 80,
samples: 10,
timeout: 300000 // 5 minutes
},
'pattern-breaking': {
validators: ['anti-detection', 'technical'],
threshold: 85,
samples: 15,
timeout: 120000 // 2 minutes
},
'modes-management': {
validators: ['technical', 'integration'],
threshold: 95,
samples: 5,
timeout: 60000 // 1 minute
},
'llm-manager': {
validators: ['technical', 'integration'],
threshold: 90,
samples: 8,
timeout: 180000 // 3 minutes
},
'utilities': {
validators: ['technical'],
threshold: 75,
samples: 5,
timeout: 30000 // 30 secondes
}
};
/**
* Exécution complète de la suite de tests systématique
*/
static async runFullSuite(options = {}) {
const startTime = Date.now();
try {
logSh('🚀 === SUITE DE TESTS SYSTÉMATIQUE - DÉMARRAGE ===', 'INFO');
const results = {
overview: {
startTime,
endTime: null,
duration: 0,
totalModules: 0,
successful: 0,
failed: 0,
coverage: 0
},
phases: {
generation: null,
execution: null,
aiValidation: null,
reporting: null
},
modules: [],
aiValidations: {},
qualityMetrics: {},
failures: []
};
// PHASE 1: Génération des tests
logSh('📝 Phase 1: Génération automatique des tests', 'INFO');
results.phases.generation = await this.runGenerationPhase(options);
// PHASE 2: Exécution des tests
logSh('🧪 Phase 2: Exécution des tests générés', 'INFO');
results.phases.execution = await this.runExecutionPhase(options);
// PHASE 3: Validation IA
logSh('🤖 Phase 3: Validation IA du contenu généré', 'INFO');
results.phases.aiValidation = await this.runAIValidationPhase(options);
// PHASE 4: Rapport final
logSh('📊 Phase 4: Génération des rapports', 'INFO');
results.phases.reporting = await this.runReportingPhase(results, options);
// Consolidation des résultats
results.overview.endTime = Date.now();
results.overview.duration = results.overview.endTime - startTime;
results.overview.totalModules = results.phases.generation.modules.length;
results.overview.successful = results.phases.execution.passed;
results.overview.failed = results.phases.execution.failed;
results.overview.coverage = this.calculateOverallCoverage(results);
logSh(`✅ Suite systématique terminée en ${Math.round(results.overview.duration / 1000)}s`, 'INFO');
logSh(`📈 Couverture globale: ${results.overview.coverage}%`, 'INFO');
return results;
} catch (error) {
logSh(`💥 Erreur suite systématique: ${error.message}`, 'ERROR');
throw error;
}
}
/**
* Phase 1: Génération automatique des tests
*/
static async runGenerationPhase(options) {
try {
const generationResult = await ModuleTestGenerator.generateAllTests();
logSh(`📦 ${generationResult.generated} modules traités`, 'INFO');
logSh(`⚠️ ${generationResult.errors} erreurs de génération`, generationResult.errors > 0 ? 'WARNING' : 'DEBUG');
return generationResult;
} catch (error) {
logSh(`❌ Erreur phase génération: ${error.message}`, 'ERROR');
throw error;
}
}
/**
* Phase 2: Exécution des tests générés
*/
static async runExecutionPhase(options) {
try {
const generatedTestsPath = path.join(__dirname, '../systematic/generated');
const testFiles = await this.getGeneratedTestFiles(generatedTestsPath);
const executionResults = {
passed: 0,
failed: 0,
details: [],
duration: 0
};
const startTime = Date.now();
for (const testFile of testFiles) {
const testResult = await this.runSingleTestFile(testFile);
executionResults.details.push(testResult);
if (testResult.success) {
executionResults.passed++;
} else {
executionResults.failed++;
}
}
executionResults.duration = Date.now() - startTime;
logSh(`🎯 Tests exécutés: ${executionResults.passed + executionResults.failed}`, 'INFO');
logSh(`✅ Succès: ${executionResults.passed} | ❌ Échecs: ${executionResults.failed}`, 'INFO');
return executionResults;
} catch (error) {
logSh(`❌ Erreur phase exécution: ${error.message}`, 'ERROR');
throw error;
}
}
/**
* Phase 3: Validation IA des contenus générés
*/
static async runAIValidationPhase(options) {
try {
const validationResults = {
totalValidations: 0,
successfulValidations: 0,
averageQuality: 0,
averageAntiDetection: 0,
validations: {}
};
// Détection des modules de génération de contenu
const contentModules = await this.identifyContentGenerationModules();
for (const moduleName of contentModules) {
logSh(`🤖 Validation IA: ${moduleName}`, 'DEBUG');
try {
const moduleValidation = await this.validateModuleContent(moduleName);
validationResults.validations[moduleName] = moduleValidation;
validationResults.totalValidations++;
if (moduleValidation.overall >= 60) {
validationResults.successfulValidations++;
}
} catch (error) {
logSh(`⚠️ Erreur validation ${moduleName}: ${error.message}`, 'WARNING');
validationResults.validations[moduleName] = {
error: error.message,
overall: 0
};
}
}
// Calcul des moyennes
const validationValues = Object.values(validationResults.validations)
.filter(v => !v.error && v.overall);
if (validationValues.length > 0) {
validationResults.averageQuality = Math.round(
validationValues.reduce((sum, v) => sum + v.quality, 0) / validationValues.length
);
validationResults.averageAntiDetection = Math.round(
validationValues.reduce((sum, v) => sum + v.antiDetection, 0) / validationValues.length
);
}
logSh(`🎯 Validations IA: ${validationResults.successfulValidations}/${validationResults.totalValidations}`, 'INFO');
return validationResults;
} catch (error) {
logSh(`❌ Erreur phase validation IA: ${error.message}`, 'ERROR');
throw error;
}
}
/**
* Phase 4: Génération des rapports
*/
static async runReportingPhase(results, options) {
try {
const reportingResults = {
htmlReport: null,
jsonReport: null,
csvReport: null
};
// Génération rapport HTML
if (!options.skipHtmlReport) {
reportingResults.htmlReport = await this.generateHtmlReport(results);
}
// Génération rapport JSON
reportingResults.jsonReport = await this.generateJsonReport(results);
// Génération rapport CSV (métriques)
if (options.generateCsv) {
reportingResults.csvReport = await this.generateCsvReport(results);
}
logSh('📄 Rapports générés', 'INFO');
return reportingResults;
} catch (error) {
logSh(`❌ Erreur phase reporting: ${error.message}`, 'ERROR');
throw error;
}
}
/**
* Utilitaires d'exécution
*/
static async getGeneratedTestFiles(directory) {
try {
const files = await fs.readdir(directory);
return files
.filter(file => file.endsWith('.test.js'))
.map(file => path.join(directory, file));
} catch (error) {
logSh(`⚠️ Dossier tests générés introuvable: ${directory}`, 'WARNING');
return [];
}
}
static async runSingleTestFile(testFilePath) {
const moduleName = path.basename(testFilePath, '.generated.test.js');
return new Promise((resolve) => {
const startTime = Date.now();
const child = spawn('node', ['--test', testFilePath], {
stdio: 'pipe',
timeout: 120000 // 2 minutes max par fichier
});
let output = '';
let errorOutput = '';
child.stdout.on('data', (data) => {
output += data.toString();
});
child.stderr.on('data', (data) => {
errorOutput += data.toString();
});
child.on('close', (code) => {
const duration = Date.now() - startTime;
resolve({
module: moduleName,
success: code === 0,
exitCode: code,
duration,
output,
errorOutput,
timestamp: new Date().toISOString()
});
});
child.on('error', (error) => {
resolve({
module: moduleName,
success: false,
error: error.message,
duration: Date.now() - startTime,
timestamp: new Date().toISOString()
});
});
});
}
/**
* Validation IA des modules de contenu
*/
static async identifyContentGenerationModules() {
const contentModules = [
'ContentGeneration',
'InitialGeneration',
'TechnicalEnhancement',
'TransitionEnhancement',
'StyleEnhancement',
'SelectiveEnhancement',
'PatternBreakingCore',
'Main'
];
return contentModules;
}
static async validateModuleContent(moduleName) {
try {
// Simulation d'exécution du module pour obtenir du contenu
const sampleContent = await this.generateSampleContent(moduleName);
if (!sampleContent || sampleContent.length < 50) {
return {
overall: 0,
error: 'Contenu insuffisant pour validation'
};
}
// Validation multi-critères
const validation = await AIContentValidator.fullValidate(sampleContent, {
source: `module_test_${moduleName}`,
expectedLength: sampleContent.length
});
// Métriques qualité
const qualityMetrics = QualityMetrics.calculateMetrics(sampleContent);
// Validation anti-détection
const antiDetection = AntiDetectionValidator.validateAntiDetection(sampleContent);
return {
overall: validation.overall,
quality: validation.scores.quality || qualityMetrics.overall,
antiDetection: antiDetection.overallScore,
technical: validation.scores.technical || 75,
confidence: validation.confidence,
contentLength: sampleContent.length,
validationTimestamp: new Date().toISOString()
};
} catch (error) {
return {
overall: 0,
error: error.message
};
}
}
static async generateSampleContent(moduleName) {
// Simulation simplifiée de génération de contenu
const sampleContents = {
'ContentGeneration': 'La plaque personnalisée représente une solution élégante pour créer un élément décoratif unique. Cette approche technique permet d\'optimiser l\'esthétique tout en respectant les contraintes fonctionnelles. Le processus de personnalisation offre une flexibilité remarquable.',
'InitialGeneration': 'Créer une plaque gravée demande une approche méthodique. La sélection des matériaux influence directement la qualité du résultat final. Cette étape initiale détermine les possibilités créatives subsequentes.',
'TechnicalEnhancement': 'L\'optimisation technique des processus de gravure nécessite une précision millimétrique. Les paramètres de puissance laser sont calibrés selon le matériau choisi. Cette configuration garantit une qualité professionnelle constante.',
'Main': 'Le workflow complet intègre plusieurs étapes de validation. Chaque phase du processus contribue à la qualité globale du livrable. La coordination des différents modules assure la cohérence du résultat.'
};
return sampleContents[moduleName] || 'Contenu de test générique pour validation des métriques de qualité et de détection IA.';
}
/**
* Génération des rapports
*/
static async generateJsonReport(results) {
const reportPath = path.join(__dirname, '../reports', `systematic-test-report-${Date.now()}.json`);
// Ensure reports directory exists
await fs.mkdir(path.dirname(reportPath), { recursive: true });
const reportData = {
timestamp: new Date().toISOString(),
summary: results.overview,
phases: results.phases,
validations: results.phases.aiValidation?.validations || {},
metadata: {
nodeVersion: process.version,
platform: process.platform,
generator: 'SystematicTestRunner'
}
};
await fs.writeFile(reportPath, JSON.stringify(reportData, null, 2), 'utf8');
logSh(`📄 Rapport JSON: ${reportPath}`, 'DEBUG');
return reportPath;
}
static async generateHtmlReport(results) {
const reportPath = path.join(__dirname, '../reports', `systematic-test-report-${Date.now()}.html`);
const htmlContent = this.buildHtmlReport(results);
await fs.writeFile(reportPath, htmlContent, 'utf8');
logSh(`📄 Rapport HTML: ${reportPath}`, 'DEBUG');
return reportPath;
}
static buildHtmlReport(results) {
return `<!DOCTYPE html>
<html>
<head>
<title>Rapport Tests Systématiques</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { text-align: center; border-bottom: 2px solid #007acc; padding-bottom: 20px; margin-bottom: 30px; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }
.stat-card { background: linear-gradient(135deg, #007acc, #0056b3); color: white; padding: 15px; border-radius: 6px; text-align: center; }
.stat-value { font-size: 24px; font-weight: bold; }
.stat-label { font-size: 14px; opacity: 0.9; }
.phase { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 6px; }
.phase-header { font-size: 18px; font-weight: bold; margin-bottom: 10px; color: #007acc; }
.validation-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; }
.validation-card { border: 1px solid #ddd; padding: 15px; border-radius: 6px; background: #f9f9f9; }
.score { font-weight: bold; padding: 2px 8px; border-radius: 4px; color: white; }
.score.excellent { background: #28a745; }
.score.good { background: #17a2b8; }
.score.moderate { background: #ffc107; color: #000; }
.score.poor { background: #dc3545; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🧪 Rapport Tests Systématiques</h1>
<p>Généré le ${new Date().toLocaleString('fr-FR')}</p>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value">${results.overview.totalModules || 0}</div>
<div class="stat-label">Modules testés</div>
</div>
<div class="stat-card">
<div class="stat-value">${results.overview.coverage || 0}%</div>
<div class="stat-label">Couverture globale</div>
</div>
<div class="stat-card">
<div class="stat-value">${Math.round((results.overview.duration || 0) / 1000)}s</div>
<div class="stat-label">Durée totale</div>
</div>
<div class="stat-card">
<div class="stat-value">${results.overview.successful || 0}</div>
<div class="stat-label">Tests réussis</div>
</div>
</div>
<div class="phase">
<div class="phase-header">📝 Phase 1 - Génération</div>
<p>Modules traités: ${results.phases.generation?.generated || 0}</p>
<p>Erreurs: ${results.phases.generation?.errors || 0}</p>
</div>
<div class="phase">
<div class="phase-header">🧪 Phase 2 - Exécution</div>
<p>Tests passés: ${results.phases.execution?.passed || 0}</p>
<p>Tests échoués: ${results.phases.execution?.failed || 0}</p>
</div>
<div class="phase">
<div class="phase-header">🤖 Phase 3 - Validation IA</div>
<div class="validation-grid">
${Object.entries(results.phases.aiValidation?.validations || {}).map(([module, validation]) => `
<div class="validation-card">
<h4>${module}</h4>
<p>Score global: <span class="score ${this.getScoreClass(validation.overall || 0)}">${validation.overall || 0}/100</span></p>
${validation.quality ? `<p>Qualité: ${validation.quality}/100</p>` : ''}
${validation.antiDetection ? `<p>Anti-détection: ${validation.antiDetection}/100</p>` : ''}
${validation.error ? `<p style="color: red;">Erreur: ${validation.error}</p>` : ''}
</div>
`).join('')}
</div>
</div>
</div>
</body>
</html>`;
}
static getScoreClass(score) {
if (score >= 85) return 'excellent';
if (score >= 70) return 'good';
if (score >= 50) return 'moderate';
return 'poor';
}
/**
* Calculs des métriques
*/
static calculateOverallCoverage(results) {
const generationResults = results.phases.generation;
if (!generationResults || !generationResults.modules) return 0;
const totalFunctions = generationResults.modules.reduce((sum, module) =>
sum + (module.analysis?.functions?.length || 0), 0
);
const testedFunctions = generationResults.modules.reduce((sum, module) =>
sum + (module.testCount || 0), 0
);
return totalFunctions > 0 ? Math.round((testedFunctions / totalFunctions) * 100) : 0;
}
/**
* API publique simplifiée
*/
static async runQuick(options = {}) {
return this.runFullSuite({
...options,
skipHtmlReport: true,
generateCsv: false
});
}
static async runDetailed(options = {}) {
return this.runFullSuite({
...options,
generateCsv: true
});
}
}
// Exécution directe
if (require.main === module) {
const args = process.argv.slice(2);
const mode = args.includes('--quick') ? 'quick' : 'detailed';
SystematicTestRunner[mode === 'quick' ? 'runQuick' : 'runDetailed']()
.then(results => {
console.log('\n🎯 RÉSULTATS FINAUX:');
console.log(`📦 Modules: ${results.overview.totalModules}`);
console.log(`✅ Succès: ${results.overview.successful}`);
console.log(`❌ Échecs: ${results.overview.failed}`);
console.log(`📊 Couverture: ${results.overview.coverage}%`);
process.exit(results.overview.failed > 0 ? 1 : 0);
})
.catch(error => {
console.error('💥 Erreur runner systématique:', error.message);
process.exit(1);
});
}
module.exports = { SystematicTestRunner };