diff --git a/tests/edge-cases/api-edge-cases.test.js b/tests/edge-cases/api-edge-cases.test.js new file mode 100644 index 0000000..4ebfa88 --- /dev/null +++ b/tests/edge-cases/api-edge-cases.test.js @@ -0,0 +1,477 @@ +/** + * TESTS EDGE CASES - API Controller + * Tests des cas limites, erreurs et comportements extrêmes + */ + +const { describe, it, before, after } = require('node:test'); +const assert = require('node:assert'); +const http = require('node:http'); +const { ManualServer } = require('../../lib/modes/ManualServer'); + +// Helper pour requêtes HTTP +function makeRequest(options, postData = null) { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const parsed = res.headers['content-type']?.includes('application/json') + ? JSON.parse(data) + : data; + resolve({ statusCode: res.statusCode, headers: res.headers, data: parsed }); + } catch (e) { + resolve({ statusCode: res.statusCode, headers: res.headers, data }); + } + }); + }); + + req.on('error', reject); + req.setTimeout(10000, () => { + req.destroy(); + reject(new Error('Request timeout')); + }); + + if (postData) { + req.write(typeof postData === 'object' ? JSON.stringify(postData) : postData); + } + req.end(); + }); +} + +describe('API Edge Cases - Tests des Cas Limites', () => { + let server; + let baseUrl; + const testPort = 3098; // Port différent pour éviter conflits + + before(async () => { + server = new ManualServer({ port: testPort, wsPort: 8098 }); + await server.start(); + baseUrl = `http://localhost:${testPort}`; + console.log(`🧪 Serveur edge cases démarré sur ${baseUrl}`); + }); + + after(async () => { + if (server) { + await server.stop(); + console.log('🛑 Serveur edge cases arrêté'); + } + }); + + describe('🔥 Paramètres Extrêmes', () => { + it('should handle very long project names', async () => { + const longName = 'A'.repeat(10000); // 10KB de nom + + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + name: longName, + description: 'Test nom très long' + }); + + // L'API doit gérer les noms longs gracieusement + assert.ok([201, 400].includes(response.statusCode)); + + if (response.statusCode === 201) { + assert.strictEqual(response.data.success, true); + assert.strictEqual(response.data.data.name, longName); + } else { + assert.strictEqual(response.data.success, false); + } + }); + + it('should handle pagination with extreme values', async () => { + // Test avec des valeurs de pagination extrêmes + const testCases = [ + { limit: -1, offset: 0 }, + { limit: 999999, offset: 0 }, + { limit: 50, offset: -1 }, + { limit: 'invalid', offset: 'invalid' }, + { limit: 0, offset: 999999 } + ]; + + for (const testCase of testCases) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: `/api/articles?limit=${testCase.limit}&offset=${testCase.offset}`, + method: 'GET' + }); + + // L'API doit soit réussir avec des valeurs normalisées, soit échouer gracieusement + assert.ok([200, 400, 500].includes(response.statusCode)); + + if (response.statusCode === 200) { + assert.ok(response.data.data.limit >= 0); + assert.ok(response.data.data.offset >= 0); + } + } + }); + + it('should handle special characters in all fields', async () => { + const specialChars = { + name: '🚀 Test "Special" & émojis 中文', + description: 'Description avec \n\t\r caractères spéciaux \\n "quotes" \'single\' & symbols €£¥', + config: { + special: '<>&"\'{}[]()=+-*/\\', + unicode: '🎯🔥💯✨🌟⭐', + mixed: 'Normal text avec 中文 и русский' + } + }; + + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, specialChars); + + assert.ok([201, 400].includes(response.statusCode)); + + if (response.statusCode === 201) { + assert.strictEqual(response.data.data.name, specialChars.name); + assert.strictEqual(response.data.data.description, specialChars.description); + } + }); + }); + + describe('🛡️ Sécurité et Injection', () => { + it('should reject SQL injection attempts', async () => { + const sqlInjections = [ + "'; DROP TABLE projects; --", + "1' OR '1'='1", + "admin'/*", + "1; SELECT * FROM users; --" + ]; + + for (const injection of sqlInjections) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + name: injection, + description: 'Test injection SQL' + }); + + // L'API doit traiter cela comme une chaîne normale, pas d'injection + assert.ok([201, 400].includes(response.statusCode)); + + if (response.statusCode === 201) { + assert.strictEqual(response.data.data.name, injection); // Stocké tel quel + } + } + }); + + it('should handle XSS attempts gracefully', async () => { + const xssPayloads = [ + '', + 'javascript:alert(1)', + '', + '' + ]; + + for (const payload of xssPayloads) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/templates', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + name: 'Test XSS', + content: payload, + description: 'Template avec payload XSS' + }); + + assert.ok([201, 400].includes(response.statusCode)); + + if (response.statusCode === 201) { + // Le payload doit être stocké tel quel (pas d'exécution côté serveur) + assert.strictEqual(response.data.data.content, payload); + } + } + }); + + it('should validate content-type header requirements', async () => { + // Test sans Content-Type + const response1 = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST' + }, '{"name":"Test"}'); + + // Test avec mauvais Content-Type + const response2 = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'text/plain' } + }, '{"name":"Test"}'); + + // Express devrait gérer ces cas + assert.ok([400, 415, 500].includes(response1.statusCode)); + assert.ok([400, 415, 500].includes(response2.statusCode)); + }); + }); + + describe('💣 Cas d\'Erreur Extrêmes', () => { + it('should handle malformed JSON gracefully', async () => { + const malformedJsons = [ + '{"name":}', + '{"name":"test",}', + '{name:"test"}', + '{"name":"test"', + 'null', + 'undefined', + '', + '[]', + 'true' + ]; + + for (const json of malformedJsons) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, json); + + assert.strictEqual(response.statusCode, 400); + assert.strictEqual(response.data.success, false); + } + }); + + it('should handle very large payloads', async () => { + // Payload de 1MB + const largePayload = { + name: 'Large Test', + description: 'X'.repeat(1024 * 1024), // 1MB de description + config: { + data: 'Y'.repeat(100000) // 100KB supplémentaires + } + }; + + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, largePayload); + + // L'API peut soit accepter, soit rejeter selon les limites configurées + assert.ok([201, 400, 413].includes(response.statusCode)); + }); + + it('should handle concurrent requests without corruption', async () => { + // 20 requêtes simultanées + const promises = Array(20).fill().map((_, i) => + makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + name: `Concurrent Project ${i}`, + description: `Test concurrence ${i}` + }) + ); + + const results = await Promise.allSettled(promises); + + // Compter les succès + const successes = results.filter(r => + r.status === 'fulfilled' && r.value.statusCode === 201 + ); + + // Au moins quelques requêtes doivent réussir + assert.ok(successes.length > 0); + + // Vérifier que les IDs sont uniques + const ids = successes.map(r => r.value.data.data.id); + const uniqueIds = new Set(ids); + assert.strictEqual(ids.length, uniqueIds.size, 'IDs doivent être uniques'); + }); + }); + + describe('🌐 Protocole HTTP Edge Cases', () => { + it('should handle unsupported HTTP methods', async () => { + const methods = ['PATCH', 'DELETE', 'HEAD', 'TRACE', 'CONNECT']; + + for (const method of methods) { + try { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method + }); + + // 405 Method Not Allowed ou 404 + assert.ok([404, 405].includes(response.statusCode)); + } catch (error) { + // Certaines méthodes peuvent être rejetées par Node.js + assert.ok(error.message.includes('Method') || error.message.includes('method')); + } + } + }); + + it('should handle invalid URLs and paths', async () => { + const invalidPaths = [ + '/api/projects/../../../etc/passwd', + '/api/projects/%2e%2e%2f%2e%2e%2f', + '/api/projects/\\..\\..\\windows\\system32', + '/api/projects?' + 'x'.repeat(10000), // Query string très longue + '/api/projects#fragment', + '/api/projects with spaces', + '/api/проекты', // URL avec caractères non-ASCII + ]; + + for (const path of invalidPaths) { + try { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path, + method: 'GET' + }); + + // Doit retourner 404 ou 400, pas d'erreur de sécurité + assert.ok([400, 404].includes(response.statusCode)); + } catch (error) { + // Erreurs de parsing d'URL attendues + assert.ok(error.message.includes('Invalid') || error.message.includes('URI')); + } + } + }); + + it('should handle connection timeouts gracefully', async () => { + // Test avec un article qui va prendre du temps (workflow LLM) + const startTime = Date.now(); + + try { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/articles', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + keyword: 'test timeout edge case', + project: 'timeout-test' + }); + + const duration = Date.now() - startTime; + + // Si ça réussit, vérifier que c'est raisonnable + if (response.statusCode === 201) { + assert.ok(duration < 120000, `Article generation took ${duration}ms`); + assert.strictEqual(response.data.success, true); + } else { + // Erreur attendue (timeout LLM, etc.) + assert.strictEqual(response.data.success, false); + } + + } catch (error) { + // Timeout de requête attendu + assert.ok(error.message.includes('timeout') || error.message.includes('Timeout')); + } + }); + }); + + describe('📊 Performance Edge Cases', () => { + it('should handle rapid successive requests', async () => { + const requests = []; + const startTime = Date.now(); + + // 50 requêtes health check rapides + for (let i = 0; i < 50; i++) { + requests.push( + makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/health', + method: 'GET' + }) + ); + } + + const results = await Promise.allSettled(requests); + const duration = Date.now() - startTime; + + const successes = results.filter(r => + r.status === 'fulfilled' && r.value.statusCode === 200 + ); + + // La plupart des requêtes doivent réussir + assert.ok(successes.length >= 45, `Only ${successes.length}/50 requests succeeded`); + + // Performance raisonnable + assert.ok(duration < 5000, `50 health checks took ${duration}ms`); + + console.log(`📈 Performance: ${successes.length}/50 requests in ${duration}ms`); + }); + + it('should maintain stability under stress', async () => { + // Mix de différents types de requêtes + const stressRequests = [ + // Health checks + ...Array(10).fill().map(() => ({ path: '/api/health', method: 'GET' })), + + // Metrics + ...Array(5).fill().map(() => ({ path: '/api/metrics', method: 'GET' })), + + // Project creation + ...Array(5).fill().map((_, i) => ({ + path: '/api/projects', + method: 'POST', + data: { name: `Stress Project ${i}`, description: 'Stress test' } + })), + + // Template creation + ...Array(3).fill().map((_, i) => ({ + path: '/api/templates', + method: 'POST', + data: { name: `Stress Template ${i}`, content: '' } + })) + ]; + + const promises = stressRequests.map(req => + makeRequest({ + hostname: 'localhost', + port: testPort, + path: req.path, + method: req.method, + headers: req.data ? { 'Content-Type': 'application/json' } : {} + }, req.data) + ); + + const results = await Promise.allSettled(promises); + + // Calculer taux de succès + const successes = results.filter(r => + r.status === 'fulfilled' && [200, 201].includes(r.value.statusCode) + ); + + const successRate = (successes.length / results.length) * 100; + console.log(`🎯 Stress test: ${successRate.toFixed(1)}% success rate`); + + // Au moins 80% de succès attendu + assert.ok(successRate >= 80, `Success rate too low: ${successRate}%`); + }); + }); +}); + +console.log('🧪 Tests Edge Cases API - Validation des cas limites et sécurité'); \ No newline at end of file diff --git a/tests/edge-cases/api-parameters.test.js b/tests/edge-cases/api-parameters.test.js new file mode 100644 index 0000000..31d8111 --- /dev/null +++ b/tests/edge-cases/api-parameters.test.js @@ -0,0 +1,450 @@ +/** + * TESTS PARAMÈTRES EDGE CASES - API Controller + * Tests des limites de paramètres et pagination + */ + +const { describe, it, before, after } = require('node:test'); +const assert = require('node:assert'); +const http = require('node:http'); +const { ManualServer } = require('../../lib/modes/ManualServer'); + +// Helper pour requêtes HTTP +function makeRequest(options, postData = null) { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const parsed = res.headers['content-type']?.includes('application/json') + ? JSON.parse(data) + : data; + resolve({ statusCode: res.statusCode, headers: res.headers, data: parsed }); + } catch (e) { + resolve({ statusCode: res.statusCode, headers: res.headers, data }); + } + }); + }); + + req.on('error', reject); + req.setTimeout(10000, () => { + req.destroy(); + reject(new Error('Request timeout')); + }); + + if (postData) { + req.write(typeof postData === 'object' ? JSON.stringify(postData) : postData); + } + req.end(); + }); +} + +describe('API Parameters Edge Cases', () => { + let server; + let baseUrl; + const testPort = 3096; + + before(async () => { + server = new ManualServer({ port: testPort, wsPort: 8096 }); + await server.start(); + baseUrl = `http://localhost:${testPort}`; + console.log(`📊 Serveur paramètres démarré sur ${baseUrl}`); + }); + + after(async () => { + if (server) { + await server.stop(); + console.log('🛑 Serveur paramètres arrêté'); + } + }); + + describe('📄 Pagination Edge Cases', () => { + it('should handle extreme pagination values', async () => { + const testCases = [ + { limit: -1, offset: 0, expected: 'normalize negative limit' }, + { limit: 999999, offset: 0, expected: 'cap very large limit' }, + { limit: 50, offset: -1, expected: 'normalize negative offset' }, + { limit: 0, offset: 0, expected: 'handle zero limit' }, + { limit: 50, offset: 999999, expected: 'handle large offset' } + ]; + + for (const testCase of testCases) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: `/api/articles?limit=${testCase.limit}&offset=${testCase.offset}`, + method: 'GET' + }); + + // L'API doit normaliser ou échouer gracieusement + assert.ok([200, 400, 500].includes(response.statusCode), + `Test case: ${testCase.expected}`); + + if (response.statusCode === 200) { + assert.ok(response.data.data.limit >= 0, 'Limit should be normalized to >= 0'); + assert.ok(response.data.data.offset >= 0, 'Offset should be normalized to >= 0'); + } + } + }); + + it('should handle invalid pagination types', async () => { + const invalidTypes = [ + { limit: 'abc', offset: 0 }, + { limit: 50, offset: 'def' }, + { limit: 'null', offset: 'undefined' }, + { limit: '[]', offset: '{}' }, + { limit: 'true', offset: 'false' } + ]; + + for (const testCase of invalidTypes) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: `/api/articles?limit=${testCase.limit}&offset=${testCase.offset}`, + method: 'GET' + }); + + // L'API peut convertir ou échouer + assert.ok([200, 400].includes(response.statusCode)); + + if (response.statusCode === 200) { + // Vérifier que les valeurs sont normalisées + assert.ok(typeof response.data.data.limit === 'number'); + assert.ok(typeof response.data.data.offset === 'number'); + } + } + }); + + it('should handle query parameters with special characters', async () => { + const specialParams = [ + 'project=%3Cscript%3E', // ', + '', + '">', + "';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//", + '">', + '' + ]; + + for (const payload of xssPayloads) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/templates', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + name: 'Test XSS', + content: payload, + description: 'Template avec payload XSS' + }); + + assert.ok([201, 400].includes(response.statusCode)); + + if (response.statusCode === 201) { + // Le payload doit être stocké tel quel, pas d'exécution + assert.strictEqual(response.data.data.content, payload); + } + } + }); + + it('should handle path traversal attempts', async () => { + const pathTraversals = [ + '../../../etc/passwd', + '..\\..\\..\\windows\\system32\\config', + '%2e%2e%2f%2e%2e%2f%2e%2e%2f', + '....//....//....//etc/passwd', + '..%2F..%2F..%2Fetc%2Fpasswd' + ]; + + for (const path of pathTraversals) { + try { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: `/api/articles/${path}`, + method: 'GET' + }); + + // Doit retourner 404 ou 500, pas accéder aux fichiers système + assert.ok([404, 500].includes(response.statusCode)); + + } catch (error) { + // Erreurs d'URL malformée acceptables + assert.ok(error.message.includes('Invalid') || error.message.includes('URI')); + } + } + }); + }); + + describe('🔍 Validation des Données', () => { + it('should validate required fields strictly', async () => { + const invalidPayloads = [ + null, + undefined, + {}, + { name: '' }, + { name: null }, + { name: undefined }, + { description: 'Sans nom' }, + { name: ' ' }, // Espaces seulement + ]; + + for (const payload of invalidPayloads) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, payload); + + assert.strictEqual(response.statusCode, 400); + assert.strictEqual(response.data.success, false); + assert.ok(response.data.error); + } + }); + + it('should handle malformed JSON gracefully', async () => { + const malformedJsons = [ + '{"name":}', + '{"name":"test",}', + '{name:"test"}', // Clés sans guillemets + '{"name":"test"', // JSON incomplet + 'null', + 'undefined', + '', + '[]', // Array au lieu d'objet + 'true', // Boolean au lieu d'objet + '{"name":"test","config":{invalid}}' // Objet imbriqué malformé + ]; + + for (const json of malformedJsons) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, json); + + assert.strictEqual(response.statusCode, 400); + assert.strictEqual(response.data.success, false); + } + }); + + it('should handle extreme data types', async () => { + const extremePayloads = [ + { name: 123 }, // Number au lieu de string + { name: true }, // Boolean au lieu de string + { name: [] }, // Array au lieu de string + { name: {} }, // Object au lieu de string + { name: 'Test', description: 123 }, // Number en description + { name: 'Test', config: 'not an object' } // String au lieu d'object + ]; + + for (const payload of extremePayloads) { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, payload); + + // L'API peut soit accepter (conversion automatique) soit rejeter + assert.ok([201, 400].includes(response.statusCode)); + + if (response.statusCode === 201) { + // Vérifier que les types sont convertis proprement + assert.strictEqual(typeof response.data.data.name, 'string'); + } + } + }); + }); + + describe('🌐 Protocole HTTP', () => { + it('should handle missing Content-Type header', async () => { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST' + // Pas de Content-Type + }, '{"name":"Test"}'); + + // Express devrait gérer gracieusement + assert.ok([400, 415, 500].includes(response.statusCode)); + }); + + it('should handle wrong Content-Type header', async () => { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'text/plain' } + }, '{"name":"Test"}'); + + assert.ok([400, 415, 500].includes(response.statusCode)); + }); + + it('should handle unsupported HTTP methods', async () => { + const methods = ['PATCH', 'DELETE', 'HEAD', 'TRACE']; + + for (const method of methods) { + try { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method + }); + + // 405 Method Not Allowed ou 404 + assert.ok([404, 405].includes(response.statusCode)); + } catch (error) { + // Certaines méthodes peuvent être rejetées par Node.js + assert.ok(error.message.includes('Method') || error.message.includes('method')); + } + } + }); + + it('should handle invalid URLs', async () => { + const invalidPaths = [ + '/api/projects with spaces', + '/api/проекты', // Caractères non-ASCII + '/api/projects?' + 'x'.repeat(1000), // Query string très longue + '/api/projects#fragment' + ]; + + for (const path of invalidPaths) { + try { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path, + method: 'GET' + }); + + assert.ok([400, 404].includes(response.statusCode)); + } catch (error) { + // Erreurs de parsing d'URL attendues + assert.ok(error.message.includes('Invalid') || error.message.includes('URI')); + } + } + }); + }); + + describe('📊 Limites et Performance', () => { + it('should handle very large payloads', async () => { + // Payload de 100KB + const largePayload = { + name: 'Large Test', + description: 'X'.repeat(100 * 1024), // 100KB + config: { + data: 'Y'.repeat(10000) + } + }; + + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, largePayload); + + // Peut accepter ou rejeter selon les limites + assert.ok([201, 400, 413].includes(response.statusCode)); + }); + + it('should handle concurrent requests safely', async () => { + // 10 requêtes simultanées + const promises = Array(10).fill().map((_, i) => + makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/projects', + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, { + name: `Concurrent Project ${i}`, + description: `Test concurrence ${i}` + }) + ); + + const results = await Promise.allSettled(promises); + + // Vérifier succès + const successes = results.filter(r => + r.status === 'fulfilled' && r.value.statusCode === 201 + ); + + assert.ok(successes.length > 0, 'Au moins une requête doit réussir'); + + // Vérifier unicité des IDs + const ids = successes.map(r => r.value.data.data.id); + const uniqueIds = new Set(ids); + assert.strictEqual(ids.length, uniqueIds.size, 'IDs doivent être uniques'); + }); + + it('should maintain stability under rapid requests', async () => { + const requests = []; + + // 20 requêtes health check rapides + for (let i = 0; i < 20; i++) { + requests.push( + makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/health', + method: 'GET' + }) + ); + } + + const results = await Promise.allSettled(requests); + + const successes = results.filter(r => + r.status === 'fulfilled' && r.value.statusCode === 200 + ); + + // Au moins 90% doivent réussir + const successRate = (successes.length / results.length) * 100; + assert.ok(successRate >= 90, `Success rate too low: ${successRate}%`); + }); + }); + + describe('🔒 Headers et CORS', () => { + it('should handle CORS preflight requests', async () => { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/health', + method: 'OPTIONS', + headers: { + 'Origin': 'http://localhost:3000', + 'Access-Control-Request-Method': 'POST' + } + }); + + // CORS devrait être géré + assert.ok([200, 204].includes(response.statusCode)); + }); + + it('should set appropriate security headers', async () => { + const response = await makeRequest({ + hostname: 'localhost', + port: testPort, + path: '/api/health', + method: 'GET' + }); + + assert.strictEqual(response.statusCode, 200); + + // Vérifier headers de sécurité de base + assert.ok(response.headers['content-type']); + + // Les headers de sécurité peuvent être ajoutés par Express/middleware + console.log('📋 Headers reçus:', Object.keys(response.headers)); + }); + }); +}); + +console.log('🔒 Tests Sécurité API - Validation injection, XSS et limites'); \ No newline at end of file