Some checks failed
SourceFinder CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
SourceFinder CI/CD Pipeline / Unit Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Integration Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Performance Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Code Coverage Report (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (16.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (18.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (20.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Regression Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Audit (push) Has been cancelled
SourceFinder CI/CD Pipeline / Notify Results (push) Has been cancelled
- Architecture modulaire avec injection de dépendances - Système de scoring intelligent multi-facteurs (spécificité, fraîcheur, qualité, réutilisation) - Moteur anti-injection 4 couches (preprocessing, patterns, sémantique, pénalités) - API REST complète avec validation et rate limiting - Repository JSON avec index mémoire et backup automatique - Provider LLM modulaire pour génération de contenu - Suite de tests complète (Jest) : * Tests unitaires pour sécurité et scoring * Tests d'intégration API end-to-end * Tests de sécurité avec simulation d'attaques * Tests de performance et charge - Pipeline CI/CD avec GitHub Actions - Logging structuré et monitoring - Configuration ESLint et environnement de test 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
271 lines
10 KiB
JavaScript
271 lines
10 KiB
JavaScript
/**
|
||
* 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 }; |