import { test, describe, beforeEach, afterEach } from 'node:test'; import { strict as assert } from 'node:assert'; import { createMockDOM, cleanupMockDOM, createMockFetch, createLogCapture } from '../utils/test-helpers.js'; import { sampleJSONContent, moduleNameMappingTests, networkTestResponses } from '../fixtures/content-samples.js'; import { readFileSync } from 'fs'; import path from 'path'; describe('ContentScanner - Tests Unitaires', () => { let ContentScanner; let logCapture; beforeEach(() => { createMockDOM(); logCapture = createLogCapture(); // Mock EnvConfig global.envConfig = { isRemoteContentEnabled: () => true, get: (key) => { const defaults = { 'REMOTE_TIMEOUT': 3000, 'TRY_REMOTE_FIRST': true, 'FALLBACK_TO_LOCAL': true }; return defaults[key]; } }; // Charger ContentScanner const scannerPath = path.resolve(process.cwd(), 'js/core/content-scanner.js'); const code = readFileSync(scannerPath, 'utf8'); const testCode = code .replace(/window\./g, 'global.') .replace(/typeof window !== 'undefined'/g, 'true'); eval(testCode); ContentScanner = global.ContentScanner; }); afterEach(() => { logCapture.restore(); cleanupMockDOM(); }); describe('Construction et initialisation', () => { test('devrait créer une instance ContentScanner', () => { const scanner = new ContentScanner(); assert.ok(scanner instanceof ContentScanner); assert.ok(scanner.discoveredContent instanceof Map); assert.equal(scanner.contentDirectory, 'js/content/'); }); test('devrait initialiser avec envConfig', () => { const scanner = new ContentScanner(); assert.ok(scanner.envConfig); assert.equal(scanner.envConfig.isRemoteContentEnabled(), true); }); }); describe('Conversion de noms de modules', () => { test('jsonFilenameToModuleName devrait convertir correctement', () => { const scanner = new ContentScanner(); moduleNameMappingTests.forEach(({ filename, expected }) => { const result = scanner.jsonFilenameToModuleName(`http://example.com/${filename}`); assert.equal(result, expected, `Failed for ${filename} -> ${expected}`); }); }); test('toPascalCase devrait convertir les chaînes avec tirets', () => { const scanner = new ContentScanner(); assert.equal(scanner.toPascalCase('hello-world'), 'HelloWorld'); assert.equal(scanner.toPascalCase('test-file-name'), 'TestFileName'); assert.equal(scanner.toPascalCase('simple'), 'Simple'); }); test('extractContentId devrait extraire l\'ID du fichier', () => { const scanner = new ContentScanner(); assert.equal(scanner.extractContentId('test-content.js'), 'test-content'); assert.equal(scanner.extractContentId('test-content.json'), 'test-content'); assert.equal(scanner.extractContentId('simple.js'), 'simple'); }); }); describe('Chargement de contenu JSON', () => { test('loadJsonContent devrait charger depuis le distant en priorité', async () => { const scanner = new ContentScanner(); global.fetch = createMockFetch(networkTestResponses); await scanner.loadJsonContent('sbs-level-7-8-new.json'); // Vérifier que le module est chargé assert.ok(global.ContentModules); assert.ok(global.ContentModules.SBSLevel78New); assert.equal(global.ContentModules.SBSLevel78New.name, sampleJSONContent.name); // Vérifier les logs const logs = logCapture.getLogs('INFO'); assert.ok(logs.some(log => log.message.includes('Chargement JSON: sbs-level-7-8-new.json'))); }); test('loadJsonContent devrait fallback vers local si distant échoue', async () => { const scanner = new ContentScanner(); // Mock fetch qui échoue pour le distant mais réussit pour le local global.fetch = async (url) => { if (url.includes('localhost:8083')) { throw new Error('Network error'); } if (url.includes('js/content/')) { return { ok: true, json: async () => sampleJSONContent }; } throw new Error('Unknown URL'); }; await scanner.loadJsonContent('test-content.json'); // Vérifier que le module est chargé depuis local assert.ok(global.ContentModules.TestContent); const logs = logCapture.getLogs('WARN'); assert.ok(logs.some(log => log.message.includes('Distant échoué'))); }); test('loadJsonContent devrait échouer si toutes les méthodes échouent', async () => { const scanner = new ContentScanner(); global.fetch = async () => { throw new Error('All methods failed'); }; try { await scanner.loadJsonContent('nonexistent.json'); assert.fail('Should have thrown an error'); } catch (error) { assert.ok(error.message.includes('Impossible de charger JSON')); } }); }); describe('Test de connectivité distante', () => { test('shouldTryRemote devrait retourner true si configuré', () => { const scanner = new ContentScanner(); // Mock window.location pour protocol http global.window.location.protocol = 'http:'; assert.equal(scanner.shouldTryRemote(), true); }); test('shouldTryRemote devrait retourner false si pas configuré', () => { const scanner = new ContentScanner(); global.envConfig.isRemoteContentEnabled = () => false; assert.equal(scanner.shouldTryRemote(), false); }); test('tryRemoteLoad devrait utiliser le proxy local', async () => { const scanner = new ContentScanner(); global.fetch = createMockFetch(networkTestResponses); const result = await scanner.tryRemoteLoad('sbs-level-7-8-new.json'); assert.equal(result.success, true); assert.equal(result.source, 'remote'); }); }); describe('Scan de fichiers de contenu', () => { test('scanContentFile devrait traiter un fichier JSON', async () => { const scanner = new ContentScanner(); global.fetch = createMockFetch(networkTestResponses); const result = await scanner.scanContentFile('sbs-level-7-8-new.json'); assert.ok(result); assert.equal(result.id, 'sbs-level-7-8-new'); assert.equal(result.filename, 'sbs-level-7-8-new.json'); assert.ok(result.name); }); test('scanContentFile devrait traiter un fichier JS', async () => { const scanner = new ContentScanner(); // Mock pour loadScript scanner.loadScript = async () => { global.ContentModules = global.ContentModules || {}; global.ContentModules.TestContent = { name: 'Test Content', vocabulary: { 'test': 'test' } }; }; const result = await scanner.scanContentFile('test-content.js'); assert.ok(result); assert.equal(result.id, 'test-content'); assert.equal(result.filename, 'test-content.js'); }); test('scanContentFile devrait échouer si module non trouvé', async () => { const scanner = new ContentScanner(); global.fetch = async () => ({ ok: true, json: async () => ({}) }); try { await scanner.scanContentFile('nonexistent.json'); assert.fail('Should have thrown an error'); } catch (error) { assert.ok(error.message.includes('Impossible de charger')); } }); }); describe('Extraction d\'informations de contenu', () => { test('extractContentInfo devrait extraire les métadonnées', () => { const scanner = new ContentScanner(); const module = { name: 'Test Module', description: 'Test description', difficulty: 'easy', version: '2.0' }; const info = scanner.extractContentInfo(module, 'test-id', 'test.js'); assert.equal(info.id, 'test-id'); assert.equal(info.filename, 'test.js'); assert.equal(info.name, 'Test Module'); assert.equal(info.description, 'Test description'); assert.equal(info.difficulty, 'easy'); assert.equal(info.enabled, true); assert.equal(info.metadata.version, '2.0'); }); test('extractContentInfo devrait utiliser des valeurs par défaut', () => { const scanner = new ContentScanner(); const module = {}; const info = scanner.extractContentInfo(module, 'test-id', 'test.js'); assert.equal(info.difficulty, 'medium'); assert.equal(info.description, 'Contenu automatiquement détecté'); assert.equal(info.metadata.version, '1.0'); assert.equal(info.metadata.format, 'legacy'); }); }); describe('Gestion des erreurs et logs', () => { test('updateConnectionStatus devrait émettre un événement', () => { const scanner = new ContentScanner(); let eventReceived = false; global.window.dispatchEvent = (event) => { if (event.type === 'contentConnectionStatus') { eventReceived = true; } }; scanner.updateConnectionStatus('online', 'Test connection'); assert.equal(eventReceived, true); }); test('devrait logger les erreurs appropriées', async () => { const scanner = new ContentScanner(); global.fetch = async () => { throw new Error('Test error'); }; try { await scanner.loadJsonContent('test.json'); } catch (error) { // Expected } const errorLogs = logCapture.getLogs('WARN'); assert.ok(errorLogs.length > 0); assert.ok(errorLogs.some(log => log.message.includes('Test error'))); }); }); describe('Discovery de fichiers', () => { test('tryCommonFiles devrait essayer les fichiers connus', async () => { const scanner = new ContentScanner(); global.fetch = createMockFetch({ 'http://localhost:8083/do-proxy/sbs-level-7-8-new.json': { ok: true }, 'http://localhost:8083/do-proxy/english-class-demo.json': { ok: true } }); const files = await scanner.tryCommonFiles(); assert.ok(Array.isArray(files)); assert.ok(files.includes('sbs-level-7-8-new.json')); assert.ok(files.includes('english-class-demo.json')); }); }); });