/** * Test complet du NewsSearchService * Valide l'orchestration complète: Stock + LLM + Scoring */ require('dotenv').config(); // Activer logging complet process.env.ENABLE_CONSOLE_LOG = 'true'; process.env.LOG_LEVEL = 'debug'; const NewsSearchService = require('./src/services/NewsSearchService'); const LLMNewsProvider = require('./src/implementations/news/LLMNewsProvider'); const JSONStockRepository = require('./src/implementations/storage/JSONStockRepository'); const BasicScoringEngine = require('./src/implementations/scoring/BasicScoringEngine'); const logger = require('./src/utils/logger'); async function testNewsSearchService() { console.log('🧪 Testing NewsSearchService - Complete Integration...\n'); try { // 1. Initialiser tous les composants console.log('🔧 1. Initializing all components...'); const newsProvider = new LLMNewsProvider({ model: 'gpt-4o-mini', maxTokens: 1000, temperature: 0.3 }); const stockRepository = new JSONStockRepository({ dataPath: './test-data/stock', autoBackup: false }); const scoringEngine = new BasicScoringEngine(); // Initialiser le service principal const newsService = new NewsSearchService(newsProvider, scoringEngine, stockRepository); await newsService.init(); logger.info('All components initialized', { newsProvider: newsProvider.constructor.name, scoringEngine: scoringEngine.constructor.name, stockRepository: stockRepository.constructor.name }); // 2. Test health check console.log('\n💡 2. Testing service health check...'); const health = await newsService.healthCheck(); if (health.status !== 'healthy') { throw new Error(`Health check failed: ${JSON.stringify(health)}`); } console.log('✅ Health check passed'); logger.logHealthCheck('NewsSearchService', 'healthy', health); // 3. Test recherche avec stock vide (fallback vers LLM) console.log('\n📝 3. Testing search with empty stock (LLM fallback)...'); const emptyStockQuery = { raceCode: '352-1', productContext: 'Guide éducatif pour Berger Allemand', contentType: 'education', clientId: 'test-client-1' }; const emptyStockOptions = { limit: 3, minScore: 50 }; logger.newsSearch('Testing empty stock scenario', emptyStockQuery, []); const emptyStockResult = await newsService.search(emptyStockQuery, emptyStockOptions); if (!emptyStockResult.success) { throw new Error(`Empty stock search failed: ${emptyStockResult.error}`); } console.log(`✅ Generated ${emptyStockResult.articles.length} articles via LLM`); console.log(` Strategy: ${emptyStockResult.metadata.searchStrategy}`); console.log(` Average score: ${emptyStockResult.metadata.quality.averageScore}`); console.log(` Total time: ${emptyStockResult.metadata.performance.totalTime}ms`); // Afficher quelques articles générés emptyStockResult.articles.slice(0, 2).forEach((article, index) => { console.log(`\n📄 Generated Article ${index + 1}:`); console.log(` Title: ${article.title}`); console.log(` Score: ${article.finalScore}`); console.log(` Category: ${article.scoreCategory}`); console.log(` Content length: ${article.content.length} chars`); }); // 4. Test recherche avec stock existant console.log('\n📦 4. Testing search with existing stock...'); // Le stock devrait maintenant contenir les articles générés précédemment const stockSearchQuery = { raceCode: '352-1', productContext: 'Articles de qualité sur Berger Allemand', contentType: 'education', clientId: 'test-client-2' }; const stockSearchResult = await newsService.search(stockSearchQuery, { limit: 5, minScore: 60 }); console.log(`✅ Found ${stockSearchResult.articles.length} articles`); console.log(` Strategy: ${stockSearchResult.metadata.searchStrategy}`); console.log(` Stock hits: ${stockSearchResult.metadata.performance.stockResults}`); console.log(` Generated: ${stockSearchResult.metadata.performance.generatedResults}`); // 5. Test avec différents scores minimums console.log('\n🎯 5. Testing different minimum scores...'); const scoringTests = [ { minScore: 30, label: 'Low threshold' }, { minScore: 60, label: 'Medium threshold' }, { minScore: 80, label: 'High threshold' } ]; for (const test of scoringTests) { const scoreResult = await newsService.search(stockSearchQuery, { limit: 10, minScore: test.minScore }); console.log(` ${test.label} (min: ${test.minScore}): ${scoreResult.articles.length} articles`); if (scoreResult.articles.length > 0) { const scores = scoreResult.articles.map(a => a.finalScore); console.log(` Score range: ${Math.min(...scores)}-${Math.max(...scores)}`); } } // 6. Test cache et performance console.log('\n⚡ 6. Testing cache and performance...'); const cacheTestQuery = { raceCode: '001-1', // Nouvelle race pour forcer génération productContext: 'Guide Labrador', contentType: 'soins', clientId: 'test-client-cache' }; // Première recherche (sans cache) const startTime = Date.now(); const firstSearch = await newsService.search(cacheTestQuery, { limit: 2 }); const firstSearchTime = Date.now() - startTime; // Deuxième recherche (avec cache) const cachedStartTime = Date.now(); const cachedSearch = await newsService.search(cacheTestQuery, { limit: 2 }); const cachedSearchTime = Date.now() - cachedStartTime; console.log(` First search: ${firstSearchTime}ms`); console.log(` Cached search: ${cachedSearchTime}ms`); console.log(` Cache speedup: ${Math.round(firstSearchTime / cachedSearchTime)}x`); // 7. Test limites client console.log('\n🛡️ 7. Testing client rate limiting...'); // Faire plusieurs requêtes rapides const rateLimitPromises = []; for (let i = 0; i < 3; i++) { rateLimitPromises.push( newsService.search({ raceCode: '208-1', productContext: `Test rate limit ${i}`, clientId: 'rate-test-client' }, { limit: 1 }) ); } const rateLimitResults = await Promise.all(rateLimitPromises); const successfulRequests = rateLimitResults.filter(r => r.success).length; console.log(` ${successfulRequests}/3 requests succeeded (rate limiting working)`); // 8. Test validation des requêtes console.log('\n✅ 8. Testing request validation...'); const invalidQueries = [ { query: {}, label: 'Empty query' }, { query: { raceCode: '' }, label: 'Empty race code' }, { query: { raceCode: 'invalid' }, label: 'Invalid race code format' } ]; for (const test of invalidQueries) { try { await newsService.search(test.query, {}); console.log(` ❌ ${test.label}: Should have failed but didn't`); } catch (error) { console.log(` ✅ ${test.label}: Correctly rejected (${error.message.substring(0, 50)}...)`); } } // 9. Test diversification des sources console.log('\n🌈 9. Testing source diversification...'); const diversityQuery = { raceCode: '352-1', productContext: 'Test diversité sources', clientId: 'diversity-test' }; const diversityResult = await newsService.search(diversityQuery, { limit: 8 }); const sourceDistribution = diversityResult.metadata.sources; console.log(' Source distribution:'); Object.entries(sourceDistribution).forEach(([source, count]) => { console.log(` ${source}: ${count} articles`); }); // 10. Afficher métriques finales console.log('\n📊 10. Final service metrics...'); const finalMetrics = newsService.getMetrics(); console.log(' Service Performance:'); console.log(` - Total searches: ${finalMetrics.totalSearches}`); console.log(` - Stock hits: ${finalMetrics.stockHits}`); console.log(` - LLM generations: ${finalMetrics.llmGenerations}`); console.log(` - Cache hits: ${finalMetrics.cacheHits}`); console.log(` - Fallback activations: ${finalMetrics.fallbackActivations}`); console.log(` - Average response time: ${Math.round(finalMetrics.averageResponseTime)}ms`); console.log(` - Average quality score: ${Math.round(finalMetrics.qualityScoreAverage)}`); console.log(` - Active clients: ${finalMetrics.activeClients}`); console.log(` - Cache size: ${finalMetrics.cacheSize}`); logger.performance('NewsSearchService test completed', 'full_integration_test', Date.now() - startTime, { totalSearches: finalMetrics.totalSearches, avgResponseTime: finalMetrics.averageResponseTime, avgQuality: finalMetrics.qualityScoreAverage }); // 11. Test stock repository stats console.log('\n💾 11. Stock repository statistics...'); const stockStats = await stockRepository.getStats(); console.log(' Stock Statistics:'); console.log(` - Total articles: ${stockStats.totalArticles}`); console.log(` - Average score: ${stockStats.avgScore}`); console.log(` - Operations: ${stockStats.operations}`); console.log(` - Errors: ${stockStats.errors}`); console.log('\n✅ All NewsSearchService tests passed!'); console.log('🎯 Complete integration validated'); console.log('🔄 Stock-LLM-Scoring orchestration working perfectly'); // Nettoyer les données de test await stockRepository.close(); } catch (error) { console.error('\n❌ NewsSearchService test failed:', error.message); logger.error('NewsSearchService integration test failed', error); throw error; } // Attendre un peu pour que tous les logs soient écrits await new Promise(resolve => setTimeout(resolve, 1000)); } // Exécuter le test si appelé directement if (require.main === module) { testNewsSearchService().catch(error => { console.error('Integration test execution failed:', error); process.exit(1); }); } module.exports = { testNewsSearchService };