- Add intermediate saves (v1.0-v1.4) to Generated_Articles_Versioned - Fix compiled_text pipeline (generatedTexts object structure) - Add /api/workflow-modulaire endpoint with version tracking - Create test-modulaire.html interface with real-time logs - Support parent-child linking via Parent_Article_ID
992 lines
33 KiB
JavaScript
992 lines
33 KiB
JavaScript
// ========================================
|
|
// GÉNÉRATEUR AUTOMATIQUE DE TESTS
|
|
// Scan des modules lib/ et génération de tests systématiques
|
|
// ========================================
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const { logSh } = require('../../lib/ErrorReporting');
|
|
|
|
/**
|
|
* Générateur automatique de tests pour tous les modules lib/
|
|
* Analyse AST et génère des tests adaptés au type de fonction
|
|
*/
|
|
class ModuleTestGenerator {
|
|
|
|
static LIB_PATH = path.join(__dirname, '../../lib');
|
|
static GENERATED_TESTS_PATH = path.join(__dirname, './generated');
|
|
|
|
// Types de fonctions identifiables
|
|
static FUNCTION_TYPES = {
|
|
ASYNC: 'async',
|
|
SYNC: 'sync',
|
|
CONTENT_GENERATOR: 'content',
|
|
VALIDATOR: 'validator',
|
|
UTILITY: 'utility',
|
|
CLASS_METHOD: 'method',
|
|
EXPORT_FUNCTION: 'export'
|
|
};
|
|
|
|
/**
|
|
* 🚀 GÉNÉRATION COMPLÈTE TESTS - ORCHESTRATEUR PRINCIPAL
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Scan récursif dossier lib/ pour détecter tous modules .js
|
|
* ✅ Génération automatique tests adaptés par type fonction
|
|
* ✅ Création master test runner pour exécution batch
|
|
* ✅ Statistiques complètes : générés, échecs, couverture
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - scanLibModules() : scan récursif avec filter .js non-test
|
|
* - Pour chaque module : analyzeModule() + generateTestCases() + writeTestFile()
|
|
* - generateMasterTestRunner() pour orchestration globale
|
|
* - Logging détaillé avec ErrorReporting.logSh()
|
|
*/
|
|
static async generateAllTests() {
|
|
try {
|
|
logSh('🔧 Démarrage génération automatique des tests', 'INFO');
|
|
|
|
// Création du dossier de tests générés
|
|
await this.ensureGeneratedDirectory();
|
|
|
|
// Scan de tous les modules lib/
|
|
const modules = await this.scanLibModules();
|
|
logSh(`📦 ${modules.length} modules détectés`, 'DEBUG');
|
|
|
|
const results = {
|
|
generated: 0,
|
|
skipped: 0,
|
|
errors: 0,
|
|
modules: []
|
|
};
|
|
|
|
// Génération pour chaque module
|
|
for (const modulePath of modules) {
|
|
try {
|
|
const moduleResult = await this.generateTestsForModule(modulePath);
|
|
results.modules.push(moduleResult);
|
|
results.generated++;
|
|
|
|
logSh(`✅ Tests générés: ${path.basename(modulePath)}`, 'DEBUG');
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur génération ${path.basename(modulePath)}: ${error.message}`, 'ERROR');
|
|
results.errors++;
|
|
}
|
|
}
|
|
|
|
// Génération du test runner global
|
|
await this.generateMasterTestRunner(results.modules);
|
|
|
|
logSh(`🎯 Génération terminée: ${results.generated} générés, ${results.errors} erreurs`, 'INFO');
|
|
return results;
|
|
|
|
} catch (error) {
|
|
logSh(`💥 Erreur génération globale: ${error.message}`, 'ERROR');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🎯 GÉNÉRATION TESTS MODULE SPÉCIFIQUE - PIPELINE INDIVIDUEL
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Analyse statique complète : fonctions, classes, exports, dépendances
|
|
* ✅ Génération cas tests adaptés au type (async, validator, generator, etc.)
|
|
* ✅ Écriture fichier test avec imports et structure Node.js
|
|
* ✅ Calcul métrique couverture (% fonctions testées)
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - analyzeModule() pour extraction AST complète
|
|
* - generateTestCases() avec templates spécialisés
|
|
* - writeTestFile() avec structure Node.js Test Runner
|
|
* - calculateCoverage() pour métriques qualité
|
|
*/
|
|
static async generateTestsForModule(modulePath) {
|
|
const analysis = await this.analyzeModule(modulePath);
|
|
const tests = await this.generateTestCases(analysis);
|
|
const testFilePath = await this.writeTestFile(analysis, tests);
|
|
|
|
return {
|
|
modulePath,
|
|
testFilePath,
|
|
analysis,
|
|
testCount: tests.length,
|
|
coverage: this.calculateCoverage(analysis, tests)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 🔍 ANALYSE MODULE - EXTRACTEUR AST COMPLET
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Lecture fichier + parsing relatif paths pour imports
|
|
* ✅ Extraction 4 types : fonctions, classes, exports, dépendances
|
|
* ✅ Analyse signature fonctions (async, params, type)
|
|
* ✅ Construction structure analysis unifiée
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - fs.readFile() + path.relative() pour chemins
|
|
* - extractFunctions() avec regex patterns multiples
|
|
* - extractClasses() + extractExports() + extractDependencies()
|
|
* - Return analysis object complet pour génération tests
|
|
*/
|
|
static async analyzeModule(modulePath) {
|
|
const content = await fs.readFile(modulePath, 'utf8');
|
|
const relativePath = path.relative(this.LIB_PATH, modulePath);
|
|
const moduleName = path.basename(modulePath, '.js');
|
|
|
|
const analysis = {
|
|
modulePath,
|
|
relativePath,
|
|
moduleName,
|
|
content,
|
|
functions: [],
|
|
classes: [],
|
|
exports: [],
|
|
dependencies: []
|
|
};
|
|
|
|
// Extraction des fonctions
|
|
analysis.functions = this.extractFunctions(content);
|
|
|
|
// Extraction des classes
|
|
analysis.classes = this.extractClasses(content);
|
|
|
|
// Extraction des exports
|
|
analysis.exports = this.extractExports(content);
|
|
|
|
// Extraction des dépendances
|
|
analysis.dependencies = this.extractDependencies(content);
|
|
|
|
return analysis;
|
|
}
|
|
|
|
/**
|
|
* 🔧 EXTRACTION FONCTIONS - PARSER REGEX AVANCÉ
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ 4 patterns regex : async function, function normale, arrow functions, méthodes classe
|
|
* ✅ Extraction nom + paramètres + détection async
|
|
* ✅ Classification automatique type fonction (generator, validator, utility, etc.)
|
|
* ✅ Déduplication et filtrage (skip constructor, méthodes privées)
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - 4 regex patterns avec exec() loop pour toutes occurrences
|
|
* - determineFunctionType() selon nom et signature
|
|
* - Params parsing : split(',') + trim + filter vides
|
|
* - Dedup avec find() pour éviter doublons par nom
|
|
*/
|
|
static extractFunctions(content) {
|
|
const functions = [];
|
|
|
|
// Patterns pour différents types de fonctions
|
|
const patterns = [
|
|
// Fonctions async
|
|
/async\s+function\s+(\w+)\s*\(([^)]*)\)/g,
|
|
// Fonctions normales
|
|
/(?:^|\n)\s*function\s+(\w+)\s*\(([^)]*)\)/g,
|
|
// Fonctions fléchées avec nom
|
|
/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g,
|
|
// Méthodes de classe
|
|
/(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/g
|
|
];
|
|
|
|
patterns.forEach((pattern, index) => {
|
|
let match;
|
|
while ((match = pattern.exec(content)) !== null) {
|
|
const functionName = match[1];
|
|
const params = match[2] || '';
|
|
|
|
// Skip constructors et méthodes privées
|
|
if (functionName === 'constructor' || functionName.startsWith('_')) continue;
|
|
|
|
const func = {
|
|
name: functionName,
|
|
params: params.split(',').map(p => p.trim()).filter(p => p),
|
|
isAsync: match[0].includes('async'),
|
|
type: this.determineFunctionType(functionName, match[0], content),
|
|
fullMatch: match[0],
|
|
index: match.index
|
|
};
|
|
|
|
// Éviter les doublons
|
|
if (!functions.find(f => f.name === functionName)) {
|
|
functions.push(func);
|
|
}
|
|
}
|
|
});
|
|
|
|
return functions;
|
|
}
|
|
|
|
/**
|
|
* 🏢 EXTRACTION CLASSES - DÉTECTEUR ORIENTÉ OBJET
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Regex pattern class avec support héritage (extends)
|
|
* ✅ Extraction méthodes classe via extractClassMethods()
|
|
* ✅ Construction structure class complète avec metadata
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Pattern : /class\s+(\w+)(?:\s+extends\s+(\w+))?\s*\{/g
|
|
* - Pour chaque match : extraction nom + parent class
|
|
* - extractClassMethods() pour lister méthodes publiques
|
|
* - Return array classes avec nom, extends, méthodes
|
|
*/
|
|
static extractClasses(content) {
|
|
const classes = [];
|
|
const classPattern = /class\s+(\w+)(?:\s+extends\s+(\w+))?\s*\{/g;
|
|
|
|
let match;
|
|
while ((match = classPattern.exec(content)) !== null) {
|
|
classes.push({
|
|
name: match[1],
|
|
extends: match[2],
|
|
methods: this.extractClassMethods(content, match.index)
|
|
});
|
|
}
|
|
|
|
return classes;
|
|
}
|
|
|
|
/**
|
|
* 📦 EXTRACTION EXPORTS - DÉTECTEUR MODULE INTERFACE
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ 2 patterns : module.exports = {...} et exports.functionName
|
|
* ✅ Parsing object exports avec support syntax variations
|
|
* ✅ Déduplication avec Set pour éviter doublons
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Pattern 1 : /module\.exports\s*=\s*\{([^}]+)\}/s pour block exports
|
|
* - Pattern 2 : /exports\.(\w+)/g pour named exports individuels
|
|
* - Parsing object syntax avec split(':') pour key:value
|
|
* - [...new Set(exports)] pour déduplication finale
|
|
*/
|
|
static extractExports(content) {
|
|
const exports = [];
|
|
|
|
// module.exports = { ... }
|
|
const moduleExportsMatch = content.match(/module\.exports\s*=\s*\{([^}]+)\}/s);
|
|
if (moduleExportsMatch) {
|
|
const exportsContent = moduleExportsMatch[1];
|
|
const exportNames = exportsContent.match(/(\w+)(?:\s*:\s*\w+)?/g) || [];
|
|
exports.push(...exportNames.map(name => name.split(':')[0].trim()));
|
|
}
|
|
|
|
// exports.functionName
|
|
const namedExportsPattern = /exports\.(\w+)/g;
|
|
let match;
|
|
while ((match = namedExportsPattern.exec(content)) !== null) {
|
|
exports.push(match[1]);
|
|
}
|
|
|
|
return [...new Set(exports)]; // Déduplication
|
|
}
|
|
|
|
/**
|
|
* 🔗 EXTRACTION DÉPENDANCES - DÉTECTEUR IMPORTS
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Détection statements require() avec support quotes multiples
|
|
* ✅ Extraction noms modules pour génération imports tests
|
|
* ✅ Support syntax variations : require('module'), require("module")
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - Pattern : /require\(['\"\`]([^'\"\`]+)['\"\`]\)/g
|
|
* - Exec loop pour toutes occurrences
|
|
* - Capture groupe 1 pour nom module
|
|
* - Return array pour imports générés
|
|
*/
|
|
static extractDependencies(content) {
|
|
const dependencies = [];
|
|
|
|
// require statements
|
|
const requirePattern = /require\(['"`]([^'"`]+)['"`]\)/g;
|
|
let match;
|
|
while ((match = requirePattern.exec(content)) !== null) {
|
|
dependencies.push(match[1]);
|
|
}
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
/**
|
|
* 🏷️ DÉTERMINATION TYPE FONCTION - CLASSIFICATEUR INTELLIGENT
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Classification 6 types selon nom et signature
|
|
* ✅ CONTENT_GENERATOR : generate|create|build dans nom
|
|
* ✅ VALIDATOR : validate|check|verify dans nom
|
|
* ✅ ASYNC : signature.includes('async')
|
|
* ✅ UTILITY : format|parse|calculate dans nom
|
|
* ✅ Fallback SYNC pour autres
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - Séquence if conditions avec includes() sur nom fonction
|
|
* - Priority order : generator > validator > async > utility > sync
|
|
* - Return FUNCTION_TYPES enum pour template selection
|
|
*/
|
|
static determineFunctionType(name, signature, content) {
|
|
// Fonction de génération de contenu
|
|
if (name.includes('generate') || name.includes('create') || name.includes('build')) {
|
|
return this.FUNCTION_TYPES.CONTENT_GENERATOR;
|
|
}
|
|
|
|
// Fonction de validation
|
|
if (name.includes('validate') || name.includes('check') || name.includes('verify')) {
|
|
return this.FUNCTION_TYPES.VALIDATOR;
|
|
}
|
|
|
|
// Fonction async
|
|
if (signature.includes('async')) {
|
|
return this.FUNCTION_TYPES.ASYNC;
|
|
}
|
|
|
|
// Fonction utilitaire
|
|
if (name.includes('format') || name.includes('parse') || name.includes('calculate')) {
|
|
return this.FUNCTION_TYPES.UTILITY;
|
|
}
|
|
|
|
return this.FUNCTION_TYPES.SYNC;
|
|
}
|
|
|
|
/**
|
|
* 🧪 GÉNÉRATION CAS TESTS - FACTORY TEMPLATE TESTS
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Génération tests fonctions avec templates spécialisés
|
|
* ✅ Génération tests exports (vérification disponibilité)
|
|
* ✅ Test intégration général module health check
|
|
* ✅ Selection template selon function type
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Loop analysis.functions : generateFunctionTest() pour chaque
|
|
* - Loop analysis.exports : generateExportTest() pour disponibilité
|
|
* - generateIntegrationTest() pour health check global
|
|
* - Return array complet tests pour writeTestFile()
|
|
*/
|
|
static async generateTestCases(analysis) {
|
|
const tests = [];
|
|
|
|
// Tests pour les fonctions
|
|
for (const func of analysis.functions) {
|
|
const testCase = await this.generateFunctionTest(func, analysis);
|
|
if (testCase) tests.push(testCase);
|
|
}
|
|
|
|
// Tests pour les exports
|
|
for (const exportName of analysis.exports) {
|
|
const exportTest = this.generateExportTest(exportName, analysis);
|
|
if (exportTest) tests.push(exportTest);
|
|
}
|
|
|
|
// Test d'intégration général
|
|
tests.push(this.generateIntegrationTest(analysis));
|
|
|
|
return tests;
|
|
}
|
|
|
|
/**
|
|
* 🎯 GÉNÉRATION TEST FONCTION - TEMPLATE SELECTOR
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Switch selon function type pour template approprié
|
|
* ✅ Templates spécialisés : ContentGenerator, Validator, Async, Basic
|
|
* ✅ Génération inputs mockés selon paramètres fonction
|
|
* ✅ Structure test object avec metadata
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Switch func.type pour sélection template method
|
|
* - getContentGeneratorTestTemplate() avec validation IA
|
|
* - getValidatorTestTemplate() avec valid/invalid inputs
|
|
* - getAsyncTestTemplate() avec timeout et duration
|
|
* - getBasicTestTemplate() pour fonctions standard
|
|
*/
|
|
static async generateFunctionTest(func, analysis) {
|
|
let testTemplate;
|
|
|
|
switch (func.type) {
|
|
case this.FUNCTION_TYPES.CONTENT_GENERATOR:
|
|
testTemplate = this.getContentGeneratorTestTemplate(func, analysis);
|
|
break;
|
|
case this.FUNCTION_TYPES.VALIDATOR:
|
|
testTemplate = this.getValidatorTestTemplate(func, analysis);
|
|
break;
|
|
case this.FUNCTION_TYPES.ASYNC:
|
|
testTemplate = this.getAsyncTestTemplate(func, analysis);
|
|
break;
|
|
default:
|
|
testTemplate = this.getBasicTestTemplate(func, analysis);
|
|
}
|
|
|
|
return {
|
|
name: `${func.name}_test`,
|
|
type: func.type,
|
|
template: testTemplate,
|
|
function: func
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 📝 TEMPLATE CONTENT GENERATOR - TEST GÉNÉRATION CONTENU
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Exécution fonction génération avec mock input
|
|
* ✅ Validations base : result exists, type string/object
|
|
* ✅ Validation qualité IA si contenu texte >50 chars
|
|
* ✅ Seuil qualité : AIContentValidator score >= 40
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - Génération mockInput adapté aux paramètres
|
|
* - Assert basiques : result truthy + type check
|
|
* - If string + length>50 : AIContentValidator.quickValidate()
|
|
* - Assert validation.overall >= 40 pour qualité acceptable
|
|
*/
|
|
static getContentGeneratorTestTemplate(func, analysis) {
|
|
return `
|
|
test('${func.name} - Content Generation', async () => {
|
|
const mockInput = ${this.generateMockInput(func)};
|
|
|
|
try {
|
|
const result = await ${analysis.moduleName}.${func.name}(mockInput);
|
|
|
|
// Validations de base
|
|
assert.ok(result, 'Should return a result');
|
|
assert.ok(typeof result === 'string' || typeof result === 'object', 'Should return content');
|
|
|
|
// Validation IA si contenu texte
|
|
if (typeof result === 'string' && result.length > 50) {
|
|
const validation = await AIContentValidator.quickValidate(result, {
|
|
context: 'Generated content test'
|
|
});
|
|
assert.ok(validation.overall >= 40, 'Content quality should be acceptable');
|
|
}
|
|
|
|
console.log('✅ ${func.name}: Content generated successfully');
|
|
|
|
} catch (error) {
|
|
console.error('❌ ${func.name}: Generation failed:', error.message);
|
|
throw error;
|
|
}
|
|
});`;
|
|
}
|
|
|
|
/**
|
|
* ✅ TEMPLATE VALIDATOR - TEST FONCTION VALIDATION
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Test input valide : doit retourner résultat (pas undefined)
|
|
* ✅ Test input invalide : doit retourner false ou throw error
|
|
* ✅ Gestion gracieuse erreurs attendues validation
|
|
* ✅ Vérification comportement correct selon type retour
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - generateValidInput() + generateInvalidInput() pour cas tests
|
|
* - Assert validResult !== undefined pour success case
|
|
* - Try/catch sur invalid : si boolean=false OU exception OK
|
|
* - Console.log expected errors pour debugging
|
|
*/
|
|
static getValidatorTestTemplate(func, analysis) {
|
|
return `
|
|
test('${func.name} - Validation', async () => {
|
|
const validInput = ${this.generateValidInput(func)};
|
|
const invalidInput = ${this.generateInvalidInput(func)};
|
|
|
|
try {
|
|
// Test avec input valide
|
|
const validResult = await ${analysis.moduleName}.${func.name}(validInput);
|
|
assert.ok(validResult !== undefined, 'Should return result for valid input');
|
|
|
|
// Test avec input invalide
|
|
try {
|
|
const invalidResult = await ${analysis.moduleName}.${func.name}(invalidInput);
|
|
// Si pas d'erreur, vérifier que la validation échoue
|
|
if (typeof invalidResult === 'boolean') {
|
|
assert.strictEqual(invalidResult, false, 'Should return false for invalid input');
|
|
}
|
|
} catch (error) {
|
|
// C'est attendu pour une validation qui throw
|
|
console.log('Expected validation error:', error.message);
|
|
}
|
|
|
|
console.log('✅ ${func.name}: Validation working correctly');
|
|
|
|
} catch (error) {
|
|
console.error('❌ ${func.name}: Validation test failed:', error.message);
|
|
throw error;
|
|
}
|
|
});`;
|
|
}
|
|
|
|
/**
|
|
* ⏱️ TEMPLATE ASYNC - TEST FONCTION ASYNCHRONE
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Exécution async avec mesure durée (performance)
|
|
* ✅ Timeout 30 secondes max (assert duration < 30000ms)
|
|
* ✅ Vérification résultat non undefined
|
|
* ✅ Logging durée exécution pour monitoring
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Date.now() avant/après pour mesure durée
|
|
* - await func() avec mockInput généré
|
|
* - Assert result + assert duration < 30000
|
|
* - Console.log avec template literal pour durée
|
|
*/
|
|
static getAsyncTestTemplate(func, analysis) {
|
|
return `
|
|
test('${func.name} - Async Operation', async () => {
|
|
const input = ${this.generateMockInput(func)};
|
|
|
|
try {
|
|
const startTime = Date.now();
|
|
const result = await ${analysis.moduleName}.${func.name}(input);
|
|
const duration = Date.now() - startTime;
|
|
|
|
// Validations de base
|
|
assert.ok(result !== undefined, 'Should return a result');
|
|
assert.ok(duration < 30000, 'Should complete within 30 seconds');
|
|
|
|
console.log(\`✅ ${func.name}: Completed in \${duration}ms\`);
|
|
|
|
} catch (error) {
|
|
console.error('❌ ${func.name}: Async operation failed:', error.message);
|
|
throw error;
|
|
}
|
|
});`;
|
|
}
|
|
|
|
/**
|
|
* 🔧 TEMPLATE BASIC - TEST FONCTION STANDARD
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Exécution synchrone fonction avec mock input
|
|
* ✅ Validations minimales : result !== undefined
|
|
* ✅ Type checking basique pour éviter crashes
|
|
* ✅ Gestion erreurs avec logging pour debugging
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - Génération mockInput via generateMockInput()
|
|
* - Appel direct fonction (pas await)
|
|
* - Double assert : !== undefined + typeof check
|
|
* - Try/catch avec console.error pour troubleshooting
|
|
*/
|
|
static getBasicTestTemplate(func, analysis) {
|
|
return `
|
|
test('${func.name} - Basic Function', () => {
|
|
const input = ${this.generateMockInput(func)};
|
|
|
|
try {
|
|
const result = ${analysis.moduleName}.${func.name}(input);
|
|
|
|
// Validations de base
|
|
assert.ok(result !== undefined, 'Should return a result');
|
|
assert.ok(typeof result !== 'undefined', 'Result should be defined');
|
|
|
|
console.log('✅ ${func.name}: Function executed successfully');
|
|
|
|
} catch (error) {
|
|
console.error('❌ ${func.name}: Function failed:', error.message);
|
|
throw error;
|
|
}
|
|
});`;
|
|
}
|
|
|
|
/**
|
|
* 🎭 GÉNÉRATION INPUTS MOCKÉS - FACTORY DONNÉES TEST
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Génération données test adaptées selon noms paramètres
|
|
* ✅ Mapping intelligent : 'content' → texte, 'data' → CSV object, etc.
|
|
* ✅ Support paramètres multiples avec array notation
|
|
* ✅ Fallback 'test_value' pour paramètres non reconnus
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - If params.length=0 : return 'undefined'
|
|
* - Map params : content→text, data→CSV, personality→profile, options→config
|
|
* - Single param : return direct, multiple : return [array]
|
|
* - Smart defaults pour testing réaliste
|
|
*/
|
|
static generateMockInput(func) {
|
|
if (func.params.length === 0) return 'undefined';
|
|
|
|
const mockInputs = func.params.map(param => {
|
|
if (param.includes('content')) return '"Test content for validation"';
|
|
if (param.includes('data') || param.includes('csv')) return '{ mc0: "test keyword", t0: "Test title" }';
|
|
if (param.includes('personality')) return '{ nom: "Marc", style: "technique" }';
|
|
if (param.includes('options')) return '{ test: true }';
|
|
if (param.includes('text')) return '"Sample text for processing"';
|
|
return '"test_value"';
|
|
});
|
|
|
|
return mockInputs.length === 1 ? mockInputs[0] : `[${mockInputs.join(', ')}]`;
|
|
}
|
|
|
|
static generateValidInput(func) {
|
|
return this.generateMockInput(func);
|
|
}
|
|
|
|
static generateInvalidInput(func) {
|
|
if (func.params.length === 0) return 'undefined';
|
|
return 'null';
|
|
}
|
|
|
|
/**
|
|
* 📝 ÉCRITURE FICHIER TEST - GÉNÉRATEUR FICHIER
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Construction nom fichier : moduleName + '.generated.test.js'
|
|
* ✅ Génération contenu complet avec buildTestFileContent()
|
|
* ✅ Écriture fichier dans dossier GENERATED_TESTS_PATH
|
|
* ✅ Return path pour référence dans results
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - path.join(GENERATED_TESTS_PATH, fileName) pour chemin complet
|
|
* - buildTestFileContent() avec imports + tests + structure
|
|
* - fs.writeFile() avec encoding utf8
|
|
* - Return testFilePath pour tracking
|
|
*/
|
|
static async writeTestFile(analysis, tests) {
|
|
const testFileName = `${analysis.moduleName}.generated.test.js`;
|
|
const testFilePath = path.join(this.GENERATED_TESTS_PATH, testFileName);
|
|
|
|
const testContent = this.buildTestFileContent(analysis, tests);
|
|
|
|
await fs.writeFile(testFilePath, testContent, 'utf8');
|
|
|
|
return testFilePath;
|
|
}
|
|
|
|
/**
|
|
* 🏧 CONSTRUCTION CONTENU FICHIER - BUILDER TEMPLATE COMPLET
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Header avec metadata : module path, date génération, etc.
|
|
* ✅ Imports automatiques : assert, node:test, module cible + dépendances
|
|
* ✅ Structure describe() avec setup + tests générés + integration
|
|
* ✅ Test module loading + health check avec exports listing
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - generateImports() pour imports nécessaires (IA validator si content tests)
|
|
* - Template string avec placeholders : moduleName, date, tests
|
|
* - Concat tests.map(test => test.template) pour tous cas
|
|
* - Integration test avec Object.keys() pour exports check
|
|
*/
|
|
static buildTestFileContent(analysis, tests) {
|
|
const imports = this.generateImports(analysis);
|
|
const testCases = tests.map(test => test.template).join('\n');
|
|
|
|
return `// ========================================
|
|
// TESTS GÉNÉRÉS AUTOMATIQUEMENT - ${analysis.moduleName}
|
|
// Module: ${analysis.relativePath}
|
|
// Générés le: ${new Date().toISOString()}
|
|
// ========================================
|
|
|
|
const assert = require('assert');
|
|
const { test, describe } = require('node:test');
|
|
${imports}
|
|
|
|
describe('${analysis.moduleName} - Tests automatiques', () => {
|
|
|
|
// Setup avant les tests
|
|
let testContext = {};
|
|
|
|
test('Module loading', () => {
|
|
assert.ok(${analysis.moduleName}, 'Module should be loaded');
|
|
console.log('📦 Module ${analysis.moduleName} loaded successfully');
|
|
});
|
|
|
|
${testCases}
|
|
|
|
// Test d'intégration général
|
|
test('Integration - Module health check', async () => {
|
|
try {
|
|
// Vérification exports
|
|
const exports = Object.keys(${analysis.moduleName});
|
|
assert.ok(exports.length > 0, 'Module should export functions');
|
|
|
|
console.log(\`✅ ${analysis.moduleName}: \${exports.length} exports available\`);
|
|
console.log('📋 Exports:', exports.join(', '));
|
|
|
|
} catch (error) {
|
|
console.error('❌ Integration test failed:', error.message);
|
|
throw error;
|
|
}
|
|
});
|
|
});`;
|
|
}
|
|
|
|
/**
|
|
* 📦 GÉNÉRATION IMPORTS NÉCESSAIRES - DÉTECTEUR DÉPENDANCES
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Import module cible avec chemin relatif corrigé
|
|
* ✅ Détection tests contenu pour import AIContentValidator
|
|
* ✅ Construction array imports pour injection template
|
|
* ✅ Chemins relatifs adaptés structure dossiers
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - Import base : require('../../relativePath') pour module
|
|
* - hasContentTests = analysis.functions avec CONTENT_GENERATOR type
|
|
* - If hasContentTests : add AIContentValidator import
|
|
* - Return imports.join('\n') pour template injection
|
|
*/
|
|
static generateImports(analysis) {
|
|
const imports = [`const ${analysis.moduleName} = require('../../${analysis.relativePath}');`];
|
|
|
|
// Import du validateur IA si tests de contenu
|
|
const hasContentTests = analysis.functions.some(f =>
|
|
f.type === this.FUNCTION_TYPES.CONTENT_GENERATOR
|
|
);
|
|
|
|
if (hasContentTests) {
|
|
imports.push("const { AIContentValidator } = require('../validators/AIContentValidator');");
|
|
}
|
|
|
|
return imports.join('\n');
|
|
}
|
|
|
|
/**
|
|
* 📊 CALCUL COUVERTURE - MÉTRIQUE QUALITÉ TESTS
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Comptage total fonctions + exports disponibles
|
|
* ✅ Comptage tests générés (exclusion type integration)
|
|
* ✅ Calcul pourcentage couverture avec protection division zéro
|
|
* ✅ Return métriques détaillées pour reporting
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - totalFunctions = analysis.functions.length + analysis.exports.length
|
|
* - testedFunctions = tests.filter(t => t.type !== 'integration').length
|
|
* - percentage = Math.round((tested/total)*100) avec protection >0
|
|
* - Return {total, tested, percentage} pour dashboard
|
|
*/
|
|
static calculateCoverage(analysis, tests) {
|
|
const totalFunctions = analysis.functions.length + analysis.exports.length;
|
|
const testedFunctions = tests.filter(t => t.type !== 'integration').length;
|
|
|
|
return {
|
|
total: totalFunctions,
|
|
tested: testedFunctions,
|
|
percentage: totalFunctions > 0 ? Math.round((testedFunctions / totalFunctions) * 100) : 0
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 📁 UTILITAIRE CRÉATION DOSSIER - SETUP ENVIRONNEMENT
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Vérification existence dossier GENERATED_TESTS_PATH
|
|
* ✅ Création automatique si inexistant avec recursive:true
|
|
* ✅ Gestion gracieuse erreurs accès fichiers
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - fs.access() pour test existence
|
|
* - Si erreur : fs.mkdir() avec {recursive:true}
|
|
* - Try/catch pattern pour gestion erreurs
|
|
*/
|
|
static async ensureGeneratedDirectory() {
|
|
try {
|
|
await fs.access(this.GENERATED_TESTS_PATH);
|
|
} catch (error) {
|
|
await fs.mkdir(this.GENERATED_TESTS_PATH, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🔍 SCAN MODULES LIB - EXPLORATEUR RÉCURSIF
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Scan récursif dossier lib/ avec exploration sous-dossiers
|
|
* ✅ Filtrage .js files excluant fichiers test existants
|
|
* ✅ Construction liste complète chemins absolus modules
|
|
* ✅ Support structure dossiers imbriqués (adversarial/, generation/, etc.)
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - scanDirectory() récursif avec fs.readdir({withFileTypes:true})
|
|
* - Pour chaque item : if directory → recurse, if .js file → add
|
|
* - Filter : item.name.endsWith('.js') && !item.name.includes('test')
|
|
* - Return array paths absolus pour generateAllTests()
|
|
*/
|
|
static async scanLibModules() {
|
|
const modules = [];
|
|
|
|
const scanDirectory = async (dirPath) => {
|
|
const items = await fs.readdir(dirPath, { withFileTypes: true });
|
|
|
|
for (const item of items) {
|
|
const fullPath = path.join(dirPath, item.name);
|
|
|
|
if (item.isDirectory()) {
|
|
await scanDirectory(fullPath);
|
|
} else if (item.isFile() && item.name.endsWith('.js') && !item.name.includes('test')) {
|
|
modules.push(fullPath);
|
|
}
|
|
}
|
|
};
|
|
|
|
await scanDirectory(this.LIB_PATH);
|
|
return modules;
|
|
}
|
|
|
|
/**
|
|
* 🎯 EXTRACTION MÉTHODES CLASSE - PARSER ORIENTÉ OBJET
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Extraction méthodes depuis position début classe
|
|
* ✅ Support async methods avec pattern flexible
|
|
* ✅ Exclusion constructor (pas testé automatiquement)
|
|
* ✅ Return array noms méthodes pour tests génération
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - substring(classStartIndex) pour focus contenu classe
|
|
* - Pattern : /(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/g
|
|
* - Filter !== 'constructor' pour méthodes publiques
|
|
* - Return methods array pour class analysis
|
|
*/
|
|
static extractClassMethods(content, classStartIndex) {
|
|
// Extraction simplifiée des méthodes de classe
|
|
const methods = [];
|
|
const classContent = content.substring(classStartIndex);
|
|
const methodPattern = /(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/g;
|
|
|
|
let match;
|
|
while ((match = methodPattern.exec(classContent)) !== null) {
|
|
if (match[1] !== 'constructor') {
|
|
methods.push(match[1]);
|
|
}
|
|
}
|
|
|
|
return methods;
|
|
}
|
|
|
|
/**
|
|
* 📤 GÉNÉRATION TEST EXPORT - VÉRIFICATEUR INTERFACE
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Vérification disponibilité export (!== undefined)
|
|
* ✅ Test simple existence pour interface module
|
|
* ✅ Logging success pour debugging
|
|
* ✅ Structure test object standardisée
|
|
*
|
|
* ALGORITHME EXÉCUTÉ :
|
|
* - Assert moduleName.exportName !== undefined
|
|
* - Template minimal avec console.log success
|
|
* - Return test object avec name, type:'export', template, exportName
|
|
*/
|
|
static generateExportTest(exportName, analysis) {
|
|
return {
|
|
name: `export_${exportName}_test`,
|
|
type: 'export',
|
|
template: `
|
|
test('Export - ${exportName}', () => {
|
|
assert.ok(${analysis.moduleName}.${exportName} !== undefined, 'Export ${exportName} should be available');
|
|
console.log('✅ Export ${exportName}: Available');
|
|
});`,
|
|
exportName
|
|
};
|
|
}
|
|
|
|
static generateIntegrationTest(analysis) {
|
|
return {
|
|
name: 'integration_test',
|
|
type: 'integration',
|
|
template: '', // Géré dans buildTestFileContent
|
|
analysis
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 🎨 GÉNÉRATION MASTER TEST RUNNER - ORCHESTRATEUR GLOBAL
|
|
*
|
|
* CE QUI EST TESTÉ :
|
|
* ✅ Création runner exécutant tous tests générés en séquence
|
|
* ✅ Gestion spawn Node.js avec --test flag pour chaque fichier
|
|
* ✅ Comptage passed/failed avec exit code approprié
|
|
* ✅ Logging détaillé pour monitoring execution
|
|
*
|
|
* ALGORITHMES EXÉCUTÉS :
|
|
* - Génération TEST_FILES array depuis moduleResults paths
|
|
* - runSingleTest() avec spawn('node', ['--test', testPath])
|
|
* - Promise wrapper pour child process avec close/error events
|
|
* - Stats tracking + process.exit(1) si failures
|
|
*/
|
|
static async generateMasterTestRunner(moduleResults) {
|
|
const runnerPath = path.join(this.GENERATED_TESTS_PATH, 'test-runner-generated.js');
|
|
|
|
const runnerContent = `// ========================================
|
|
// RUNNER TESTS GÉNÉRÉS AUTOMATIQUEMENT
|
|
// Exécute tous les tests générés pour les modules lib/
|
|
// ========================================
|
|
|
|
const { spawn } = require('child_process');
|
|
const path = require('path');
|
|
|
|
const TEST_FILES = [
|
|
${moduleResults.map(r => ` '${path.basename(r.testFilePath)}'`).join(',\n')}
|
|
];
|
|
|
|
async function runGeneratedTests() {
|
|
console.log('🧪 === TESTS GÉNÉRÉS AUTOMATIQUEMENT ===');
|
|
console.log(\`📦 \${TEST_FILES.length} fichiers de test détectés\`);
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
for (const testFile of TEST_FILES) {
|
|
console.log(\`\n🔍 Test: \${testFile}\`);
|
|
|
|
try {
|
|
await runSingleTest(testFile);
|
|
passed++;
|
|
console.log(\`✅ \${testFile}: PASSED\`);
|
|
} catch (error) {
|
|
failed++;
|
|
console.log(\`❌ \${testFile}: FAILED - \${error.message}\`);
|
|
}
|
|
}
|
|
|
|
console.log(\`\n📊 RÉSULTATS: \${passed} passed, \${failed} failed\`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function runSingleTest(testFile) {
|
|
return new Promise((resolve, reject) => {
|
|
const testPath = path.join(__dirname, testFile);
|
|
const child = spawn('node', ['--test', testPath], {
|
|
stdio: 'inherit'
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(\`Test failed with code \${code}\`));
|
|
}
|
|
});
|
|
|
|
child.on('error', reject);
|
|
});
|
|
}
|
|
|
|
// Exécution si appelé directement
|
|
if (require.main === module) {
|
|
runGeneratedTests().catch(error => {
|
|
console.error('💥 Erreur runner:', error.message);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = { runGeneratedTests };`;
|
|
|
|
await fs.writeFile(runnerPath, runnerContent, 'utf8');
|
|
logSh(`📝 Test runner généré: ${runnerPath}`, 'DEBUG');
|
|
}
|
|
}
|
|
|
|
module.exports = { ModuleTestGenerator }; |