import { test, describe, beforeEach, afterEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { createMockDOM, cleanupMockDOM, createLogCapture, delay } from '../utils/test-helpers.js'; // Tests de stress et edge cases d'intégration describe('Tests de Stress et Edge Cases d\'Intégration', () => { let logCapture; // Helper pour faire des requêtes HTTP réelles async function makeRequest(path, options = {}) { const fetch = (await import('node-fetch')).default; const url = `http://localhost:8083${path}`; try { const response = await fetch(url, { timeout: options.timeout || 5000, ...options }); return { ok: response.ok, status: response.status, data: response.ok ? await response.text() : await response.text(), headers: Object.fromEntries(response.headers.entries()) }; } catch (error) { return { ok: false, error: error.message, timeout: error.code === 'TIMEOUT' }; } } beforeEach(() => { createMockDOM(); logCapture = createLogCapture(); }); afterEach(() => { logCapture.restore(); cleanupMockDOM(); }); describe('Tests de Charge du Proxy', () => { test('devrait gérer 100 requêtes simultanées', async () => { const concurrentRequests = 100; const promises = []; console.log(`🔥 Lancement de ${concurrentRequests} requêtes simultanées...`); for (let i = 0; i < concurrentRequests; i++) { promises.push(makeRequest('/do-proxy/sbs-level-7-8-new.json', { timeout: 10000 // Timeout plus long pour la charge })); } const startTime = Date.now(); const results = await Promise.allSettled(promises); const endTime = Date.now(); const successful = results.filter(r => r.status === 'fulfilled' && r.value.ok ).length; const failed = results.filter(r => r.status === 'rejected' || !r.value.ok ).length; const timeouts = results.filter(r => r.status === 'fulfilled' && r.value.timeout ).length; console.log(`✅ Résultats: ${successful} succès, ${failed} échecs, ${timeouts} timeouts`); console.log(`⏱️ Temps total: ${endTime - startTime}ms`); console.log(`📊 Moyenne: ${(endTime - startTime) / concurrentRequests}ms par requête`); // Au moins 80% de succès attendu assert.ok(successful >= concurrentRequests * 0.8, `Taux de succès trop faible: ${successful}/${concurrentRequests}`); // Temps total raisonnable (moins de 30 secondes) assert.ok(endTime - startTime < 30000, `Temps total trop long: ${endTime - startTime}ms`); }); test('devrait gérer les requêtes avec différentes tailles de payload', async () => { const requests = [ '/do-proxy/sbs-level-7-8-new.json', // ~9KB '/do-proxy/english-class-demo.json', // ~12KB '/do-proxy/nonexistent-small.json', // 404 '/do-proxy/nonexistent-large.json' // 404 ]; const results = await Promise.all( requests.map(path => makeRequest(path)) ); // Vérifier que les différentes tailles sont gérées const validResponses = results.filter(r => r.ok); assert.ok(validResponses.length >= 2, 'Should handle multiple payload sizes'); // Vérifier que les 404 sont correctement gérées const notFoundResponses = results.filter(r => !r.ok && r.status === 404); assert.ok(notFoundResponses.length >= 0, 'Should handle 404s gracefully'); }); test('devrait maintenir les performances sous charge continue', async () => { const duration = 10000; // 10 secondes const requestInterval = 100; // Une requête toutes les 100ms console.log('🔄 Test de charge continue pendant 10 secondes...'); const startTime = Date.now(); const results = []; let requestCount = 0; while (Date.now() - startTime < duration) { const requestStart = Date.now(); try { const result = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { timeout: 2000 }); const requestTime = Date.now() - requestStart; results.push({ success: result.ok, time: requestTime, requestNumber: requestCount }); } catch (error) { results.push({ success: false, time: Date.now() - requestStart, error: error.message, requestNumber: requestCount }); } requestCount++; await delay(requestInterval); } const totalTime = Date.now() - startTime; const successfulRequests = results.filter(r => r.success).length; const avgResponseTime = results .filter(r => r.success) .reduce((sum, r) => sum + r.time, 0) / successfulRequests; console.log(`📊 Résultats charge continue:`); console.log(` • Durée totale: ${totalTime}ms`); console.log(` • Requêtes totales: ${requestCount}`); console.log(` • Requêtes réussies: ${successfulRequests}`); console.log(` • Taux de succès: ${(successfulRequests/requestCount*100).toFixed(1)}%`); console.log(` • Temps de réponse moyen: ${avgResponseTime.toFixed(1)}ms`); // Assertions de performance assert.ok(successfulRequests / requestCount >= 0.9, 'Au moins 90% de succès'); assert.ok(avgResponseTime < 1000, 'Temps de réponse moyen < 1s'); }); }); describe('Tests de Robustesse Réseau', () => { test('devrait gérer les interruptions de connexion', async () => { // Simuler des requêtes rapides qui pourraient être interrompues const rapidRequests = Array(20).fill().map((_, i) => makeRequest('/do-proxy/sbs-level-7-8-new.json', { timeout: 100 + i * 10 // Timeouts variables }) ); const results = await Promise.allSettled(rapidRequests); // Compter les différents types de résultats const successful = results.filter(r => r.status === 'fulfilled' && r.value.ok ).length; const timeouts = results.filter(r => r.status === 'fulfilled' && r.value.timeout ).length; console.log(`🌐 Requêtes rapides: ${successful} succès, ${timeouts} timeouts`); // Même avec des timeouts courts, certaines requêtes devraient passer assert.ok(successful > 0, 'Au moins quelques requêtes devraient réussir'); }); test('devrait récupérer après des erreurs temporaires', async () => { // Tester la récupération en faisant plusieurs tentatives let consecutiveSuccesses = 0; const maxAttempts = 10; for (let i = 0; i < maxAttempts; i++) { const result = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); if (result.ok) { consecutiveSuccesses++; } else { consecutiveSuccesses = 0; console.log(`⚠️ Échec temporaire (tentative ${i + 1}): ${result.error || result.status}`); } // Si on a 3 succès consécutifs, le système s'est récupéré if (consecutiveSuccesses >= 3) { console.log(`✅ Récupération réussie après ${i + 1} tentatives`); break; } await delay(500); // Attendre entre les tentatives } assert.ok(consecutiveSuccesses >= 3, 'Le système devrait pouvoir se récupérer'); }); }); describe('Tests de Limites Mémoire', () => { test('devrait gérer des réponses très volumineuses', async () => { // Tester avec un fichier qui pourrait être volumineux const result = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); if (result.ok) { const dataSize = result.data.length; console.log(`📏 Taille des données reçues: ${(dataSize/1024).toFixed(1)}KB`); // Vérifier que même de gros fichiers sont gérés assert.ok(dataSize > 0, 'Should receive data'); assert.ok(dataSize < 10 * 1024 * 1024, 'Should not exceed 10MB'); // Limite raisonnable // Vérifier que c'est du JSON valide try { JSON.parse(result.data); } catch (error) { assert.fail('Les données devraient être du JSON valide'); } } }); test('devrait nettoyer la mémoire entre les requêtes', async () => { // Faire plusieurs requêtes séquentielles pour tester le nettoyage const requests = 20; let maxMemoryUsage = 0; for (let i = 0; i < requests; i++) { const result = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); if (result.ok) { const currentUsage = process.memoryUsage().heapUsed; maxMemoryUsage = Math.max(maxMemoryUsage, currentUsage); } // Petite pause pour permettre le garbage collection if (i % 5 === 0) { await delay(10); } } console.log(`🧠 Utilisation mémoire maximale: ${(maxMemoryUsage/1024/1024).toFixed(1)}MB`); // La mémoire ne devrait pas exploser (limite arbitraire de 500MB) assert.ok(maxMemoryUsage < 500 * 1024 * 1024, 'Memory usage should stay reasonable'); }); }); describe('Tests de Sécurité Edge Cases', () => { test('devrait rejeter les tentatives d\'injection de path', async () => { const maliciousPaths = [ '/do-proxy/../../../etc/passwd', '/do-proxy/..\\\\..\\\\..\\\\windows\\\\system32\\\\config\\\\sam', '/do-proxy/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd', '/do-proxy/....//....//....//etc//passwd', '/do-proxy/\\x2e\\x2e\\x2f\\x2e\\x2e\\x2f\\x2e\\x2e\\x2fetc\\x2fpasswd' ]; for (const path of maliciousPaths) { const result = await makeRequest(path); // Ces requêtes devraient échouer (404 ou 403) assert.ok(!result.ok || result.status === 404 || result.status === 403, `Path injection should be blocked: ${path}`); // Ne devrait pas retourner de contenu système if (result.data) { assert.ok(!result.data.includes('root:'), 'Should not expose system files'); assert.ok(!result.data.includes('Administrator'), 'Should not expose system files'); } } }); test('devrait limiter la taille des requêtes', async () => { // Tenter d'envoyer une requête avec des headers très longs const longHeader = 'x'.repeat(10000); try { const result = await makeRequest('/do-proxy/test.json', { headers: { 'X-Very-Long-Header': longHeader } }); // La requête peut échouer (ce qui est bien) ou réussir en ignorant l'header if (!result.ok) { assert.ok([400, 413, 431].includes(result.status), 'Should reject oversized headers appropriately'); } } catch (error) { // C'est acceptable que ça lève une exception assert.ok(error.message.includes('header') || error.message.includes('too large') || error.message.includes('ECONNRESET')); } }); test('devrait gérer les caractères spéciaux dans les URLs', async () => { const specialCharPaths = [ '/do-proxy/file with spaces.json', '/do-proxy/file%20with%20encoded%20spaces.json', '/do-proxy/file+with+plus.json', '/do-proxy/файл-на-русском.json', '/do-proxy/文件中文.json', '/do-proxy/file&with&ersands.json', '/do-proxy/file?with?questions.json' ]; for (const path of specialCharPaths) { const result = await makeRequest(path); // Ces requêtes peuvent échouer (404) mais ne devraient pas crasher le serveur assert.ok(typeof result.status === 'number', `Should handle special characters gracefully: ${path}`); // Le serveur devrait toujours répondre assert.ok(result.status >= 200 && result.status < 600, 'Should return valid HTTP status'); } }); }); describe('Tests de Compatibilité', () => { test('devrait fonctionner avec différents User-Agents', async () => { const userAgents = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/91.0.4472.124', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Firefox/89.0', 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) Mobile/15E148', 'curl/7.68.0', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)', 'PostmanRuntime/7.28.0' ]; for (const userAgent of userAgents) { const result = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { headers: { 'User-Agent': userAgent } }); // Toutes les requêtes valides devraient fonctionner indépendamment du User-Agent if (result.ok) { assert.ok(result.data.length > 0, `Should work with User-Agent: ${userAgent.substring(0, 50)}...`); } } }); test('devrait gérer différents types de Accept headers', async () => { const acceptHeaders = [ 'application/json', 'application/json, text/plain, */*', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', '*/*', 'application/json;charset=utf-8', 'text/plain' ]; for (const accept of acceptHeaders) { const result = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { headers: { 'Accept': accept } }); // Les requêtes JSON devraient fonctionner avec tous les Accept headers raisonnables if (result.ok) { try { JSON.parse(result.data); } catch (error) { assert.fail(`Should return valid JSON regardless of Accept header: ${accept}`); } } } }); }); describe('Tests de Récupération d\'Erreurs', () => { test('devrait récupérer après des erreurs en série', async () => { // Faire plusieurs requêtes vers des ressources inexistantes const badRequests = Array(5).fill().map((_, i) => makeRequest(`/do-proxy/nonexistent-${i}.json`) ); const badResults = await Promise.all(badRequests); // Toutes devraient échouer assert.ok(badResults.every(r => !r.ok), 'Bad requests should fail'); // Puis tester qu'une bonne requête fonctionne encore await delay(100); // Petite pause const goodResult = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); if (goodResult.ok) { assert.ok(goodResult.data.length > 0, 'Le serveur devrait récupérer après des erreurs'); } else { console.log('⚠️ Le serveur peut être temporairement indisponible'); } }); test('devrait maintenir les connexions après des timeouts', async () => { // Faire des requêtes avec des timeouts très courts const shortTimeoutRequests = Array(3).fill().map(() => makeRequest('/do-proxy/sbs-level-7-8-new.json', { timeout: 1 // 1ms - presque garanti de timeout }) ); await Promise.allSettled(shortTimeoutRequests); // Puis tester avec un timeout normal const normalResult = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { timeout: 5000 }); if (normalResult.ok) { assert.ok(normalResult.data.length > 0, 'Les connexions devraient être maintenues après des timeouts'); } }); }); });