seo-generator-server/tests/dashboard/TestDashboardServer.js

530 lines
14 KiB
JavaScript

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