Class_generator/tests/integration/stress-tests.test.js
StillHammer cb614a439d Add comprehensive test suite with unit tests and integration tests
- Complete test infrastructure with runners, helpers, and fixtures
- Unit tests for core modules: EnvConfig, ContentScanner, GameLoader
- Integration tests for proxy, content loading, and navigation
- Edge case tests covering data corruption, network failures, security
- Stress tests with 100+ concurrent requests and performance monitoring
- Test fixtures with malicious content samples and edge case data
- Comprehensive README with usage instructions and troubleshooting

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 11:37:08 +08:00

443 lines
18 KiB
JavaScript

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&ampersands.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');
}
});
});
});