Major Changes: - Moved legacy system to Legacy/ folder for archival - Built new modular architecture with strict separation of concerns - Created core system: Module, EventBus, ModuleLoader, Router - Added Application bootstrap with auto-start functionality - Implemented development server with ES6 modules support - Created comprehensive documentation and project context - Converted SBS-7-8 content to JSON format - Copied all legacy games and content to new structure New Architecture Features: - Sealed modules with WeakMap private data - Strict dependency injection system - Event-driven communication only - Inviolable responsibility patterns - Auto-initialization without commands - Component-based UI foundation ready Technical Stack: - Vanilla JS/HTML/CSS only - ES6 modules with proper imports/exports - HTTP development server (no file:// protocol) - Modular CSS with component scoping - Comprehensive error handling and debugging Ready for Phase 2: Converting legacy modules to new architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
243 lines
8.8 KiB
JavaScript
243 lines
8.8 KiB
JavaScript
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
import { strict as assert } from 'node:assert';
|
|
import { createMockDOM, cleanupMockDOM, createMockFetch } from '../utils/test-helpers.js';
|
|
import { readFileSync } from 'fs';
|
|
import path from 'path';
|
|
|
|
describe('EnvConfig - Tests Unitaires', () => {
|
|
let EnvConfig;
|
|
|
|
beforeEach(() => {
|
|
createMockDOM();
|
|
|
|
// Charger le module EnvConfig
|
|
const envConfigPath = path.resolve(process.cwd(), '../js/core/env-config.js');
|
|
const code = readFileSync(envConfigPath, 'utf8');
|
|
|
|
// Adapter le code pour l'environnement de test
|
|
const testCode = code
|
|
.replace(/window\./g, 'global.')
|
|
.replace(/typeof window !== 'undefined'/g, 'true')
|
|
.replace(/typeof module !== 'undefined' && module\.exports/g, 'false');
|
|
|
|
eval(testCode);
|
|
EnvConfig = global.EnvConfig;
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanupMockDOM();
|
|
});
|
|
|
|
describe('Construction et configuration', () => {
|
|
test('devrait créer une instance EnvConfig avec configuration par défaut', () => {
|
|
const config = new EnvConfig();
|
|
|
|
assert.ok(config instanceof EnvConfig);
|
|
assert.equal(config.get('DO_ENDPOINT'), 'https://autocollant.fra1.digitaloceanspaces.com');
|
|
assert.equal(config.get('DO_CONTENT_PATH'), 'Class_generator/ContentMe');
|
|
assert.equal(config.get('USE_REMOTE_CONTENT'), true);
|
|
assert.equal(config.get('DEBUG_MODE'), true);
|
|
});
|
|
|
|
test('devrait construire l\'URL de contenu distant correctement', () => {
|
|
const config = new EnvConfig();
|
|
const expectedUrl = 'https://autocollant.fra1.digitaloceanspaces.com/Class_generator/ContentMe/';
|
|
|
|
assert.equal(config.getRemoteContentUrl(), expectedUrl);
|
|
});
|
|
|
|
test('devrait permettre de modifier la configuration', () => {
|
|
const config = new EnvConfig();
|
|
|
|
config.set('DEBUG_MODE', false);
|
|
config.set('REMOTE_TIMEOUT', 5000);
|
|
|
|
assert.equal(config.get('DEBUG_MODE'), false);
|
|
assert.equal(config.get('REMOTE_TIMEOUT'), 5000);
|
|
});
|
|
|
|
test('devrait reconstruire l\'URL lors du changement d\'endpoint', () => {
|
|
const config = new EnvConfig();
|
|
const newEndpoint = 'https://new-endpoint.com';
|
|
|
|
config.set('DO_ENDPOINT', newEndpoint);
|
|
|
|
assert.ok(config.getRemoteContentUrl().includes('new-endpoint.com'));
|
|
});
|
|
});
|
|
|
|
describe('Méthodes utilitaires', () => {
|
|
test('isRemoteContentEnabled devrait retourner la bonne valeur', () => {
|
|
const config = new EnvConfig();
|
|
|
|
assert.equal(config.isRemoteContentEnabled(), true);
|
|
|
|
config.set('USE_REMOTE_CONTENT', false);
|
|
assert.equal(config.isRemoteContentEnabled(), false);
|
|
});
|
|
|
|
test('isFallbackEnabled devrait retourner la bonne valeur', () => {
|
|
const config = new EnvConfig();
|
|
|
|
assert.equal(config.isFallbackEnabled(), true);
|
|
|
|
config.set('FALLBACK_TO_LOCAL', false);
|
|
assert.equal(config.isFallbackEnabled(), false);
|
|
});
|
|
|
|
test('isDebugMode devrait retourner la bonne valeur', () => {
|
|
const config = new EnvConfig();
|
|
|
|
assert.equal(config.isDebugMode(), true);
|
|
|
|
config.set('DEBUG_MODE', false);
|
|
assert.equal(config.isDebugMode(), false);
|
|
});
|
|
|
|
test('shouldLogContentLoading devrait retourner la bonne valeur', () => {
|
|
const config = new EnvConfig();
|
|
|
|
assert.equal(config.shouldLogContentLoading(), true);
|
|
|
|
config.set('LOG_CONTENT_LOADING', false);
|
|
assert.equal(config.shouldLogContentLoading(), false);
|
|
});
|
|
});
|
|
|
|
describe('Test de connectivité', () => {
|
|
test('testRemoteConnection devrait réussir avec une réponse 200', async () => {
|
|
const config = new EnvConfig();
|
|
|
|
// Mock fetch pour simuler une connexion réussie
|
|
global.fetch = createMockFetch({
|
|
'http://localhost:8083/do-proxy/english-class-demo.json': {
|
|
ok: true,
|
|
status: 200,
|
|
data: { test: 'data' }
|
|
}
|
|
});
|
|
|
|
const result = await config.testRemoteConnection();
|
|
|
|
assert.equal(result.success, true);
|
|
assert.equal(result.status, 200);
|
|
assert.ok(result.url.includes('localhost:8083'));
|
|
});
|
|
|
|
test('testRemoteConnection devrait gérer les erreurs 403', async () => {
|
|
const config = new EnvConfig();
|
|
|
|
global.fetch = createMockFetch({
|
|
'http://localhost:8083/do-proxy/english-class-demo.json': {
|
|
ok: false,
|
|
status: 403,
|
|
data: { error: 'Forbidden' }
|
|
}
|
|
});
|
|
|
|
const result = await config.testRemoteConnection();
|
|
|
|
assert.equal(result.success, true); // 403 considéré comme succès (connexion OK mais privé)
|
|
assert.equal(result.status, 403);
|
|
assert.equal(result.isPrivate, true);
|
|
});
|
|
|
|
test('testRemoteConnection devrait gérer les timeouts', async () => {
|
|
const config = new EnvConfig();
|
|
config.set('REMOTE_TIMEOUT', 10); // Timeout très court
|
|
|
|
global.fetch = async () => {
|
|
await new Promise(resolve => setTimeout(resolve, 50)); // Plus long que le timeout
|
|
return { ok: true };
|
|
};
|
|
|
|
const result = await config.testRemoteConnection();
|
|
|
|
assert.equal(result.success, false);
|
|
assert.equal(result.isTimeout, true);
|
|
});
|
|
|
|
test('testRemoteConnection devrait gérer les erreurs réseau', async () => {
|
|
const config = new EnvConfig();
|
|
|
|
global.fetch = async () => {
|
|
throw new Error('Network error');
|
|
};
|
|
|
|
const result = await config.testRemoteConnection();
|
|
|
|
assert.equal(result.success, false);
|
|
assert.ok(result.error.includes('Network error'));
|
|
});
|
|
});
|
|
|
|
describe('Configuration dynamique', () => {
|
|
test('updateRemoteConfig devrait mettre à jour endpoint et path', () => {
|
|
const config = new EnvConfig();
|
|
const newEndpoint = 'https://new-server.com';
|
|
const newPath = 'new/content/path';
|
|
|
|
config.updateRemoteConfig(newEndpoint, newPath);
|
|
|
|
assert.equal(config.get('DO_ENDPOINT'), newEndpoint);
|
|
assert.equal(config.get('DO_CONTENT_PATH'), newPath);
|
|
assert.ok(config.getRemoteContentUrl().includes('new-server.com'));
|
|
assert.ok(config.getRemoteContentUrl().includes('new/content/path'));
|
|
});
|
|
});
|
|
|
|
describe('Diagnostics', () => {
|
|
test('getDiagnostics devrait retourner toutes les informations', () => {
|
|
const config = new EnvConfig();
|
|
const diagnostics = config.getDiagnostics();
|
|
|
|
assert.ok(diagnostics.remoteContentUrl);
|
|
assert.equal(typeof diagnostics.remoteEnabled, 'boolean');
|
|
assert.equal(typeof diagnostics.fallbackEnabled, 'boolean');
|
|
assert.equal(typeof diagnostics.debugMode, 'boolean');
|
|
assert.ok(diagnostics.endpoint);
|
|
assert.ok(diagnostics.contentPath);
|
|
assert.ok(diagnostics.timestamp);
|
|
});
|
|
});
|
|
|
|
describe('AWS Signature V4', () => {
|
|
test('generateAWSSignature devrait créer les headers d\'authentification', async () => {
|
|
const config = new EnvConfig();
|
|
|
|
// Mock crypto.subtle pour les tests
|
|
global.crypto = {
|
|
subtle: {
|
|
digest: async (algorithm, data) => {
|
|
return new ArrayBuffer(32); // Mock hash
|
|
},
|
|
importKey: async () => ({}),
|
|
sign: async () => new ArrayBuffer(32)
|
|
}
|
|
};
|
|
|
|
global.TextEncoder = class {
|
|
encode(text) {
|
|
return new Uint8Array(Buffer.from(text));
|
|
}
|
|
};
|
|
|
|
const headers = await config.generateAWSSignature('GET', 'https://test.com/file.json');
|
|
|
|
assert.ok(headers.Authorization);
|
|
assert.ok(headers['X-Amz-Date']);
|
|
assert.ok(headers['X-Amz-Content-Sha256']);
|
|
assert.ok(headers.Authorization.includes('AWS4-HMAC-SHA256'));
|
|
});
|
|
|
|
test('getAuthHeaders devrait retourner headers vides si pas de clés', async () => {
|
|
const config = new EnvConfig();
|
|
config.set('DO_ACCESS_KEY', '');
|
|
config.set('DO_SECRET_KEY', '');
|
|
|
|
const headers = await config.getAuthHeaders();
|
|
|
|
assert.equal(Object.keys(headers).length, 0);
|
|
});
|
|
});
|
|
}); |