/** * REPORTER AUTOMATIQUE POUR TESTS D'INTÉGRATION * Génère automatiquement un rapport HTML détaillé * S'interface avec le système de logging existant pour capturer les LLM calls */ import fs from 'fs'; import path from 'path'; class TestReporter { constructor() { this.results = []; this.startTime = Date.now(); this.currentTest = null; this.reportDir = path.join(process.cwd(), 'reports'); this.originalConsoleLog = console.log; // Créer le dossier reports s'il n'existe pas if (!fs.existsSync(this.reportDir)) { fs.mkdirSync(this.reportDir, { recursive: true }); } // Hook dans les logs pour capturer automatiquement les LLM calls this.hookLogging(); } hookLogging() { // Capture automatique des logs JSON pour extraire les stats LLM const originalStdout = process.stdout.write; process.stdout.write = (chunk, encoding, callback) => { const str = chunk.toString(); // Capturer les stats LLM depuis les logs JSON if (str.includes('"msg":"📊 Stats:') && this.currentTest) { try { const logLine = JSON.parse(str.trim()); const statsMatch = logLine.msg.match(/📊 Stats: ({.*})/); if (statsMatch) { const stats = JSON.parse(statsMatch[1]); this.currentTest.llmCalls.push({ provider: stats.provider, model: stats.model, duration: stats.duration, tokens: { promptTokens: stats.promptTokens, responseTokens: stats.responseTokens }, timestamp: stats.timestamp, input: 'Captured from logs', output: 'Captured from logs' }); } } catch (e) { // Ignore parsing errors } } return originalStdout.call(process.stdout, chunk, encoding, callback); }; } startTest(testName, config = {}) { this.currentTest = { name: testName, startTime: Date.now(), config, llmCalls: [], results: null, error: null, status: 'running' }; } recordLLMCall(provider, model, input, output, duration, tokens = {}) { if (!this.currentTest) return; this.currentTest.llmCalls.push({ provider, model, input: typeof input === 'string' ? input.substring(0, 500) + '...' : JSON.stringify(input).substring(0, 500) + '...', output: typeof output === 'string' ? output.substring(0, 1000) + '...' : JSON.stringify(output).substring(0, 1000) + '...', duration, tokens, timestamp: Date.now() }); } endTest(results = null, error = null) { if (!this.currentTest) return; this.currentTest.endTime = Date.now(); this.currentTest.duration = this.currentTest.endTime - this.currentTest.startTime; this.currentTest.results = results; this.currentTest.error = error; this.currentTest.status = error ? 'failed' : 'passed'; this.results.push({ ...this.currentTest }); this.currentTest = null; } generateReport() { const totalDuration = Date.now() - this.startTime; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const reportFile = path.join(this.reportDir, `ti-report-${timestamp}.html`); const stats = { total: this.results.length, passed: this.results.filter(r => r.status === 'passed').length, failed: this.results.filter(r => r.status === 'failed').length, totalDuration, avgDuration: this.results.length ? totalDuration / this.results.length : 0, totalLLMCalls: this.results.reduce((sum, r) => sum + r.llmCalls.length, 0) }; const html = this.generateHTML(stats); fs.writeFileSync(reportFile, html); // Générer aussi le JSON pour analyse programmatique const jsonFile = path.join(this.reportDir, `ti-report-${timestamp}.json`); fs.writeFileSync(jsonFile, JSON.stringify({ timestamp: new Date().toISOString(), stats, results: this.results }, null, 2)); console.log(`\n📊 RAPPORT GÉNÉRÉ AUTOMATIQUEMENT:`); console.log(` HTML: ${reportFile}`); console.log(` JSON: ${jsonFile}`); console.log(` 📈 ${stats.passed}/${stats.total} tests passés | ${stats.totalLLMCalls} appels LLM | ${totalDuration}ms total`); return reportFile; } generateHTML(stats) { return ` Rapport Tests d'Intégration - ${new Date().toLocaleString()}

📊 Rapport Tests d'Intégration Modulaire

Généré automatiquement le ${new Date().toLocaleString()}

${stats.total}
Tests Total
${stats.passed}
Tests Réussis
${stats.failed}
Tests Échoués
${stats.totalLLMCalls}
Appels LLM
${Math.round(stats.totalDuration / 1000)}s
Durée Totale
${Math.round(stats.avgDuration / 1000)}s
Durée Moyenne
${this.results.map((test, index) => `
${test.name} ${Math.round(test.duration / 1000)}s | ${test.llmCalls.length} LLM calls
${test.config ? `
Configuration:
${JSON.stringify(test.config, null, 2)}
` : ''} ${test.results ? `
Résultats:
${test.results.stats ? Object.entries(test.results.stats).map(([key, value]) => `
${key}: ${value}
` ).join('') : ''}
` : ''} ${test.error ? `
Erreur: ${test.error}
` : ''}

Appels LLM (${test.llmCalls.length})

${test.llmCalls.map((call, callIndex) => `
Provider: ${call.provider}
Model: ${call.model}
Durée: ${call.duration}ms
${call.tokens.promptTokens ? `
Tokens: ${call.tokens.promptTokens}→${call.tokens.responseTokens}
` : ''}
Input:
${call.input}
Output:
${call.output}
`).join('')}
`).join('')}
`; } } export { TestReporter };