seo-generator-server/tests/systematic/ModuleTestGenerator.js
Trouve Alexis 870cfb0340 [200~add step-by-step versioning system with Google Sheets integration
- 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
2025-09-06 16:38:20 +08:00

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 };