import { test, describe, beforeEach, afterEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { spawn } from 'child_process'; import { readFileSync } from 'fs'; import path from 'path'; // Test d'intégration pour le proxy DigitalOcean describe('Proxy DigitalOcean - Tests d\'Intégration', () => { let proxyProcess; const proxyPort = 8083; const proxyUrl = `http://localhost:${proxyPort}`; // Helper pour faire des requêtes HTTP async function makeRequest(path, options = {}) { const fetch = (await import('node-fetch')).default; const url = `${proxyUrl}${path}`; try { const response = await fetch(url, { 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 }; } } // Helper pour attendre que le serveur soit prêt async function waitForServer(maxAttempts = 10, delay = 1000) { for (let i = 0; i < maxAttempts; i++) { try { const response = await makeRequest('/do-proxy/_list'); if (response.status) { return true; } } catch (error) { // Ignorer les erreurs de connexion } await new Promise(resolve => setTimeout(resolve, delay)); } return false; } beforeEach(async () => { // Démarrer le serveur proxy si pas déjà actif try { const testResponse = await makeRequest('/do-proxy/_list'); if (testResponse.status) { console.log('🟢 Serveur proxy déjà actif'); return; } } catch (error) { // Server not running, start it } console.log('🚀 Démarrage du serveur proxy pour les tests...'); const serverPath = path.resolve(process.cwd(), 'export_logger/websocket-server.js'); proxyProcess = spawn('node', [serverPath], { cwd: path.dirname(serverPath), stdio: ['pipe', 'pipe', 'pipe'], detached: false }); // Attendre que le serveur soit prêt const isReady = await waitForServer(); if (!isReady) { throw new Error('Impossible de démarrer le serveur proxy pour les tests'); } console.log('✅ Serveur proxy prêt'); }); afterEach(() => { // Ne pas arrêter le serveur car il peut être utilisé par d'autres tests // ou l'application principale if (proxyProcess && !proxyProcess.killed) { // proxyProcess.kill(); } }); describe('Endpoints du proxy', () => { test('GET /do-proxy/sbs-level-7-8-new.json devrait retourner du JSON valide', async () => { const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); assert.equal(response.ok, true, `Request failed: ${response.error || response.data}`); assert.equal(response.status, 200); // Vérifier que c'est du JSON valide let jsonData; try { jsonData = JSON.parse(response.data); } catch (error) { assert.fail(`Response is not valid JSON: ${error.message}`); } // Vérifier la structure du contenu assert.ok(jsonData.name, 'JSON should have a name field'); assert.ok(jsonData.vocabulary, 'JSON should have a vocabulary field'); assert.equal(typeof jsonData.vocabulary, 'object'); }); test('GET /do-proxy/english-class-demo.json devrait retourner du contenu', async () => { const response = await makeRequest('/do-proxy/english-class-demo.json'); assert.equal(response.ok, true, `Request failed: ${response.error || response.data}`); assert.equal(response.status, 200); let jsonData; try { jsonData = JSON.parse(response.data); } catch (error) { assert.fail(`Response is not valid JSON: ${error.message}`); } assert.ok(jsonData.name || jsonData.vocabulary, 'JSON should have content'); }); test('HEAD /do-proxy/sbs-level-7-8-new.json devrait retourner headers sans body', async () => { const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { method: 'HEAD' }); assert.equal(response.ok, true); assert.equal(response.status, 200); // HEAD request should have empty or minimal body assert.ok(response.data.length <= 100, 'HEAD response should have minimal content'); }); test('GET /do-proxy/nonexistent.json devrait retourner 404', async () => { const response = await makeRequest('/do-proxy/nonexistent-file-12345.json'); assert.equal(response.ok, false); // Should be either 404 (not found) or 403 (forbidden) assert.ok([403, 404].includes(response.status)); }); }); describe('CORS et headers', () => { test('les réponses devrait inclure les headers CORS', async () => { const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); assert.equal(response.ok, true); assert.ok(response.headers['access-control-allow-origin']); assert.equal(response.headers['access-control-allow-origin'], '*'); }); test('OPTIONS request devrait être supportée', async () => { const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { method: 'OPTIONS' }); assert.equal(response.status, 200); assert.ok(response.headers['access-control-allow-origin']); assert.ok(response.headers['access-control-allow-methods']); }); }); describe('Listing des fichiers', () => { test('GET /do-proxy/_list devrait retourner une liste de fichiers ou une erreur 403', async () => { const response = await makeRequest('/do-proxy/_list'); // Le listing peut échouer avec 403 (permissions insuffisantes) ce qui est attendu if (response.status === 403) { let jsonData; try { jsonData = JSON.parse(response.data); } catch (error) { assert.fail('403 response should be valid JSON'); } assert.ok(jsonData.error, 'Should contain error message'); assert.ok(jsonData.knownFiles, 'Should contain known files list'); assert.ok(Array.isArray(jsonData.knownFiles)); } else if (response.status === 200) { let jsonData; try { jsonData = JSON.parse(response.data); } catch (error) { assert.fail('200 response should be valid JSON'); } assert.ok(jsonData.files, 'Should contain files array'); assert.ok(Array.isArray(jsonData.files)); } else { assert.fail(`Unexpected status: ${response.status}`); } }); }); describe('Performance et timeouts', () => { test('les requêtes devrait répondre dans un délai raisonnable', async () => { const startTime = Date.now(); const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); const endTime = Date.now(); assert.equal(response.ok, true); const responseTime = endTime - startTime; assert.ok(responseTime < 10000, `Response time too slow: ${responseTime}ms`); }); test('les requêtes multiples simultanées devrait fonctionner', async () => { const promises = [ makeRequest('/do-proxy/sbs-level-7-8-new.json'), makeRequest('/do-proxy/english-class-demo.json'), makeRequest('/do-proxy/sbs-level-7-8-new.json') // Duplicate to test caching ]; const responses = await Promise.all(promises); responses.forEach((response, index) => { assert.equal(response.ok, true, `Request ${index} failed: ${response.error}`); assert.equal(response.status, 200); }); }); }); describe('Authentification AWS', () => { test('les requêtes devrait inclure les headers d\'authentification AWS', async () => { // Ce test vérifie indirectement l'authentification en s'attendant à ce que // les requêtes réussissent, ce qui nécessite une authentification correcte const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); assert.equal(response.ok, true, 'Authentication should work for valid files'); assert.equal(response.status, 200); // Si l'authentification échoue, on obtiendrait typiquement 403 Forbidden // Le fait que nous obtenons 200 indique que l'authentification fonctionne }); }); describe('Gestion des erreurs', () => { test('les routes invalides devrait retourner 404', async () => { const response = await makeRequest('/invalid-route'); assert.equal(response.ok, false); assert.equal(response.status, 404); }); test('les méthodes non supportées devrait retourner une erreur', async () => { const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json', { method: 'PUT' }); // PUT n'est pas supporté, devrait retourner une erreur assert.equal(response.ok, false); }); }); describe('Intégration avec DigitalOcean Spaces', () => { test('devrait pouvoir récupérer des fichiers réels depuis DigitalOcean', async () => { const response = await makeRequest('/do-proxy/sbs-level-7-8-new.json'); if (response.ok) { const jsonData = JSON.parse(response.data); // Vérifier que le contenu a la structure attendue d'un fichier de contenu assert.ok(jsonData.name || jsonData.vocabulary, 'Should contain educational content'); if (jsonData.vocabulary) { assert.equal(typeof jsonData.vocabulary, 'object'); assert.ok(Object.keys(jsonData.vocabulary).length > 0, 'Vocabulary should not be empty'); } } else { // Si la connexion à DigitalOcean échoue, au moins vérifier que l'erreur est appropriée assert.ok([403, 404, 500].includes(response.status), `Unexpected error status: ${response.status}`); } }); }); });