// ======================================== // SERVEUR DASHBOARD TESTS - WebSocket + API // Interface temps réel pour monitoring des tests systématiques // ======================================== const WebSocket = require('ws'); const express = require('express'); const http = require('http'); const path = require('path'); const fs = require('fs').promises; const { SystematicTestRunner } = require('../runners/SystematicTestRunner'); const { ModuleTestGenerator } = require('../systematic/ModuleTestGenerator'); const { logSh } = require('../../lib/ErrorReporting'); /** * Serveur dashboard avec WebSocket pour tests en temps réel */ class TestDashboardServer { constructor(options = {}) { this.port = options.port || 8082; this.httpPort = options.httpPort || 8083; this.server = null; this.wss = null; this.httpServer = null; this.app = null; this.clients = new Set(); this.currentTestRun = null; this.systemStats = { modules: 0, tests: 0, coverage: 0, aiScore: 0, status: 'idle' }; this.testHistory = []; } /** * Démarrage du serveur dashboard */ async start() { try { logSh(`🚀 Démarrage dashboard tests (WebSocket: ${this.port}, HTTP: ${this.httpPort})`, 'INFO'); // Serveur HTTP pour l'interface await this.setupHTTPServer(); // Serveur WebSocket pour communication temps réel await this.setupWebSocketServer(); logSh('✅ Dashboard tests opérationnel', 'INFO'); logSh(`🌐 Interface: http://localhost:${this.httpPort}`, 'INFO'); logSh(`📡 WebSocket: ws://localhost:${this.port}`, 'INFO'); } catch (error) { logSh(`❌ Erreur démarrage dashboard: ${error.message}`, 'ERROR'); throw error; } } /** * Configuration du serveur HTTP */ async setupHTTPServer() { this.app = express(); // Middleware this.app.use(express.json()); this.app.use(express.static(path.join(__dirname, '.'))); // Routes API this.setupAPIRoutes(); // Route principale - dashboard this.app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'TestDashboard.html')); }); // Démarrage serveur HTTP this.httpServer = this.app.listen(this.httpPort, () => { logSh(`🌐 Serveur HTTP dashboard démarré sur port ${this.httpPort}`, 'DEBUG'); }); } /** * Configuration des routes API */ setupAPIRoutes() { // Status système this.app.get('/api/status', (req, res) => { res.json({ status: this.systemStats.status, stats: this.systemStats, clients: this.clients.size, currentRun: this.currentTestRun ? this.currentTestRun.id : null, timestamp: new Date().toISOString() }); }); // Historique des tests this.app.get('/api/history', (req, res) => { const limit = parseInt(req.query.limit) || 10; res.json({ history: this.testHistory.slice(-limit), total: this.testHistory.length }); }); // Démarrage de tests via API this.app.post('/api/run-tests', async (req, res) => { const { mode = 'quick' } = req.body; try { await this.runTests(mode); res.json({ success: true, message: `Tests ${mode} démarrés` }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); // Génération de tests this.app.post('/api/generate-tests', async (req, res) => { try { await this.generateTests(); res.json({ success: true, message: 'Génération de tests démarrée' }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); // Arrêt de tests this.app.post('/api/stop-tests', (req, res) => { this.stopCurrentTests(); res.json({ success: true, message: 'Arrêt des tests demandé' }); }); } /** * Configuration du serveur WebSocket */ async setupWebSocketServer() { this.server = http.createServer(); this.wss = new WebSocket.Server({ server: this.server }); this.wss.on('connection', (ws, req) => { this.handleClientConnection(ws, req); }); this.server.listen(this.port, () => { logSh(`📡 Serveur WebSocket dashboard démarré sur port ${this.port}`, 'DEBUG'); }); } /** * Gestion des connexions WebSocket */ handleClientConnection(ws, req) { const clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; logSh(`🔌 Client connecté: ${clientId}`, 'DEBUG'); this.clients.add(ws); // Envoi de l'état initial this.sendToClient(ws, { type: 'connection_established', clientId, timestamp: new Date().toISOString() }); this.sendToClient(ws, { type: 'system_stats', stats: this.systemStats }); // Gestion des messages clients ws.on('message', (message) => { try { const data = JSON.parse(message); this.handleClientMessage(ws, data); } catch (error) { logSh(`⚠️ Message client invalide: ${error.message}`, 'WARNING'); } }); // Déconnexion client ws.on('close', () => { this.clients.delete(ws); logSh(`🔌 Client déconnecté: ${clientId}`, 'DEBUG'); }); ws.on('error', (error) => { logSh(`❌ Erreur WebSocket client: ${error.message}`, 'ERROR'); this.clients.delete(ws); }); } /** * Gestion des messages des clients */ async handleClientMessage(ws, data) { try { switch (data.action) { case 'run_tests': await this.runTests(data.mode || 'quick'); break; case 'generate_tests': await this.generateTests(); break; case 'stop_tests': this.stopCurrentTests(); break; case 'get_status': this.sendToClient(ws, { type: 'system_stats', stats: this.systemStats }); break; default: logSh(`⚠️ Action inconnue: ${data.action}`, 'WARNING'); } } catch (error) { logSh(`❌ Erreur traitement message client: ${error.message}`, 'ERROR'); this.sendToClient(ws, { type: 'error', message: error.message }); } } /** * Exécution des tests avec monitoring */ async runTests(mode) { if (this.currentTestRun && this.currentTestRun.active) { throw new Error('Des tests sont déjà en cours'); } const runId = `run_${Date.now()}`; this.currentTestRun = { id: runId, mode, startTime: new Date(), active: true, progress: 0 }; this.systemStats.status = 'running'; // Notification clients this.broadcast({ type: 'test_started', runId, mode, timestamp: new Date().toISOString() }); logSh(`🧪 Démarrage tests ${mode} (${runId})`, 'INFO'); try { // Exécution avec callbacks de progression const results = await this.runTestsWithProgress(mode); // Finalisation this.currentTestRun.active = false; this.currentTestRun.endTime = new Date(); this.currentTestRun.results = results; this.systemStats.status = 'idle'; this.updateSystemStats(results); // Sauvegarde historique this.testHistory.push({ ...this.currentTestRun, summary: { modules: results.overview.totalModules, passed: results.overview.successful, failed: results.overview.failed, duration: results.overview.duration } }); // Notification clients this.broadcast({ type: 'test_completed', runId, results: results.overview, timestamp: new Date().toISOString() }); logSh(`✅ Tests terminés (${runId}): ${results.overview.successful}/${results.overview.totalModules}`, 'INFO'); } catch (error) { this.currentTestRun.active = false; this.currentTestRun.error = error.message; this.systemStats.status = 'error'; logSh(`❌ Erreur tests (${runId}): ${error.message}`, 'ERROR'); this.broadcast({ type: 'test_error', runId, error: error.message, timestamp: new Date().toISOString() }); throw error; } } /** * Exécution avec callbacks de progression */ async runTestsWithProgress(mode) { const progressCallback = (progress, message, moduleData) => { this.currentTestRun.progress = progress; this.broadcast({ type: 'test_progress', runId: this.currentTestRun.id, progress, message, module: moduleData ? moduleData.name : null, status: moduleData ? moduleData.status : null, timestamp: new Date().toISOString() }); }; const validationCallback = (module, validation) => { this.broadcast({ type: 'validation_result', runId: this.currentTestRun.id, module, validation, timestamp: new Date().toISOString() }); }; // Hooks personnalisés pour SystematicTestRunner const options = { progressCallback, validationCallback, mode }; if (mode === 'quick') { return await SystematicTestRunner.runQuick(options); } else { return await SystematicTestRunner.runDetailed(options); } } /** * Génération de tests */ async generateTests() { logSh('🔧 Génération des tests démarrée', 'INFO'); this.broadcast({ type: 'log', level: 'INFO', message: 'Génération automatique des tests démarrée', timestamp: new Date().toISOString() }); try { const results = await ModuleTestGenerator.generateAllTests(); this.systemStats.tests = results.modules.reduce((sum, m) => sum + m.testCount, 0); this.systemStats.modules = results.generated; this.broadcast({ type: 'generation_completed', results: { generated: results.generated, errors: results.errors, totalTests: this.systemStats.tests }, timestamp: new Date().toISOString() }); logSh(`✅ Génération terminée: ${results.generated} modules, ${this.systemStats.tests} tests`, 'INFO'); } catch (error) { logSh(`❌ Erreur génération: ${error.message}`, 'ERROR'); this.broadcast({ type: 'generation_error', error: error.message, timestamp: new Date().toISOString() }); } } /** * Arrêt des tests en cours */ stopCurrentTests() { if (this.currentTestRun && this.currentTestRun.active) { this.currentTestRun.active = false; this.currentTestRun.stopped = true; this.systemStats.status = 'stopping'; logSh(`🛑 Arrêt des tests demandé (${this.currentTestRun.id})`, 'INFO'); this.broadcast({ type: 'test_stopped', runId: this.currentTestRun.id, timestamp: new Date().toISOString() }); } } /** * Mise à jour des statistiques système */ updateSystemStats(results) { if (results && results.overview) { this.systemStats.modules = results.overview.totalModules || 0; this.systemStats.coverage = results.overview.coverage || 0; // Calcul score IA moyen if (results.phases && results.phases.aiValidation && results.phases.aiValidation.validations) { const validations = Object.values(results.phases.aiValidation.validations); const scores = validations.filter(v => v.overall).map(v => v.overall); this.systemStats.aiScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0; } } this.broadcast({ type: 'system_stats', stats: this.systemStats }); } /** * Communication WebSocket */ sendToClient(ws, data) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); } } broadcast(data) { this.clients.forEach(client => { this.sendToClient(client, data); }); } /** * Arrêt du serveur */ async stop() { logSh('🛑 Arrêt du serveur dashboard', 'INFO'); // Arrêt des tests en cours this.stopCurrentTests(); // Fermeture connexions WebSocket this.clients.forEach(client => { client.close(); }); // Arrêt serveurs if (this.wss) { this.wss.close(); } if (this.server) { this.server.close(); } if (this.httpServer) { this.httpServer.close(); } logSh('✅ Dashboard arrêté', 'INFO'); } /** * État du serveur */ getStatus() { return { running: !!this.server && !!this.httpServer, clients: this.clients.size, ports: { websocket: this.port, http: this.httpPort }, currentRun: this.currentTestRun, stats: this.systemStats, uptime: process.uptime() }; } } // Exécution directe pour tests if (require.main === module) { const dashboard = new TestDashboardServer(); dashboard.start() .then(() => { console.log('🎯 Dashboard de tests démarré avec succès!'); console.log(`🌐 Interface: http://localhost:${dashboard.httpPort}`); // Gestion arrêt propre process.on('SIGINT', async () => { console.log('\n🛑 Arrêt du dashboard...'); await dashboard.stop(); process.exit(0); }); }) .catch(error => { console.error('💥 Erreur démarrage dashboard:', error.message); process.exit(1); }); } module.exports = { TestDashboardServer };