Class_generator/tests/integration/proxy-digitalocean.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

282 lines
11 KiB
JavaScript

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}`);
}
});
});
});