seo-generator-server/tests/integration/content-quality.test.js
StillHammer 4f60de68d6 Fix BatchProcessor initialization and add comprehensive test suite
- Fix BatchProcessor constructor to avoid server blocking during startup
- Add comprehensive integration tests for all modular combinations
- Enhance CLAUDE.md documentation with new test commands
- Update SelectiveLayers configuration for better LLM allocation
- Add AutoReporter system for test automation
- Include production workflow validation tests

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-19 14:17:49 +08:00

320 lines
13 KiB
JavaScript

import { AutoReporter } from '../reporters/AutoReporter.js';
// ========================================
// TESTS D'INTÉGRATION - QUALITÉ CONTENU
// Description: Valide que le contenu généré est de vraie qualité
// ========================================
import assert from 'node:assert';
import { test, describe } from 'node:test';
import { requireCommonJS } from '../_helpers/commonjs-bridge.js';
// Imports système
const { handleModularWorkflow } = requireCommonJS('Main');
const { StepExecutor } = requireCommonJS('StepExecutor');
const { AIContentValidator } = requireCommonJS('../validators/AIContentValidator', '../tests');
// Auto-Reporter Configuration
const autoReporter = new AutoReporter();
describe('🔥 Tests qualité contenu - Validation comportement réel', () => {
// Données de test réalistes
const realTestScenarios = [
{
name: 'Plaque personnalisée',
csvData: {
mc0: 'plaque personnalisée',
t0: 'Créer une plaque personnalisée unique pour votre intérieur',
personality: { nom: 'Sophie', style: 'déco', ton: 'chaleureux' },
tMinus1: 'décoration murale personnalisée',
mcPlus1: 'plaque gravée,plaque métal,plaque bois,plaque acrylique',
tPlus1: 'Plaque Gravée Premium,Plaque Métal Design,Plaque Bois Naturel,Plaque Acrylique Moderne'
},
expectedKeywords: ['plaque', 'personnalisé', 'gravé', 'décoration'],
minLength: 800,
maxLength: 3000
},
{
name: 'Formation développement web',
csvData: {
mc0: 'formation développement web',
t0: 'Apprendre le développement web moderne avec les dernières technologies',
personality: { nom: 'Marc', style: 'technique', ton: 'pédagogique' },
tMinus1: 'apprentissage programmation web',
mcPlus1: 'formation JavaScript,formation React,formation Node.js,formation PHP',
tPlus1: 'Bootcamp JavaScript,Formation React Avancée,Cours Node.js,Formation PHP Laravel'
},
expectedKeywords: ['développement', 'web', 'formation', 'javascript', 'apprentissage'],
minLength: 1000,
maxLength: 4000
}
];
// ========================================
// TEST: QUALITÉ CONTENU WORKFLOW COMPLET
// ========================================
test('🔥 CRITIQUE: Contenu généré est de vraie qualité professionnelle', async () => {
console.log('🧪 Test qualité contenu workflow complet...');
for (const scenario of realTestScenarios) {
console.log(`\n📋 Scénario: ${scenario.name}`);
// Créer template XML réaliste
const xmlTemplate = Buffer.from(`<?xml version='1.0' encoding='UTF-8'?>
<article>
<h1>|Titre_Principal{{T0}}{Rédige un titre H1 accrocheur et SEO pour "${scenario.csvData.mc0}"}|</h1>
<intro>|Introduction{{MC0}}{Rédige une introduction engageante de 2-3 phrases sur "${scenario.csvData.mc0}"}|</intro>
<section1>
<h2>|Sous_Titre_1{{MC+1_1}}{Rédige un sous-titre H2 pour "${scenario.csvData.mc0}"}|</h2>
<content>|Contenu_1{{MC+1_1}}{Développe un paragraphe détaillé sur "${scenario.csvData.mc0}"}|</content>
</section1>
<section2>
<h2>|Sous_Titre_2{{MC+1_2}}{Rédige un autre sous-titre H2 pour "${scenario.csvData.mc0}"}|</h2>
<content>|Contenu_2{{MC+1_2}}{Développe un autre paragraphe sur "${scenario.csvData.mc0}"}|</content>
</section2>
<conclusion>|Conclusion{{MC0}}{Rédige une conclusion professionnelle sur "${scenario.csvData.mc0}"}|</conclusion>
</article>`).toString('base64');
// Exécuter workflow complet
const result = await handleModularWorkflow({
csvData: scenario.csvData,
xmlTemplate,
selectiveStack: 'standardEnhancement',
adversarialMode: 'light',
humanSimulationMode: 'lightSimulation',
patternBreakingMode: 'none',
source: 'quality_test'
});
// ============= VALIDATIONS BASIQUES =============
assert.ok(result.success, `Workflow ${scenario.name} doit réussir`);
assert.ok(result.compiledText, `${scenario.name} doit avoir du texte compilé`);
const finalText = result.compiledText;
console.log(`📊 Longueur texte final: ${finalText.length} chars`);
// Valider longueur
assert.ok(finalText.length >= scenario.minLength,
`Texte trop court: ${finalText.length} < ${scenario.minLength}`);
assert.ok(finalText.length <= scenario.maxLength,
`Texte trop long: ${finalText.length} > ${scenario.maxLength}`);
// ============= VALIDATION MOTS-CLÉS =============
const lowerText = finalText.toLowerCase();
for (const keyword of scenario.expectedKeywords) {
assert.ok(lowerText.includes(keyword.toLowerCase()),
`Mot-clé manquant: "${keyword}" dans texte ${scenario.name}`);
}
// ============= VALIDATION QUALITÉ IA =============
console.log(`🤖 Validation qualité IA pour ${scenario.name}...`);
const qualityResult = await AIContentValidator.quickValidate(finalText, {
context: `Article sur ${scenario.csvData.mc0}`,
expectedKeywords: scenario.expectedKeywords,
personality: scenario.csvData.personality
});
console.log(`📊 Score qualité global: ${qualityResult.overall}/100`);
console.log(`📊 Cohérence: ${qualityResult.coherence || 'N/A'}`);
console.log(`📊 Pertinence: ${qualityResult.relevance || 'N/A'}`);
// Seuils de qualité
assert.ok(qualityResult.overall >= 60,
`Qualité insuffisante: ${qualityResult.overall}/100 pour ${scenario.name}`);
// ============= VALIDATION STRUCTURE =============
// Vérifier que le contenu a une structure cohérente
assert.ok(finalText.includes(scenario.csvData.mc0),
`Mot-clé principal manquant: ${scenario.csvData.mc0}`);
// Vérifier présence éléments structurels
const hasTitle = /^[^.!?]*[.!?]?\s*$/m.test(finalText.split('\n')[0]);
assert.ok(finalText.split('\n').length > 3,
`Structure trop simple: ${finalText.split('\n').length} lignes`);
console.log(`${scenario.name}: Qualité validée (${qualityResult.overall}/100)`);
}
console.log('✅ Tous les scénarios de qualité validés');
}, { timeout: 300000 }); // 5 minutes pour génération complète
// ========================================
// TEST: COHÉRENCE ENTRE STACKS
// ========================================
test('🔥 CRITIQUE: Différentes stacks produisent des variations cohérentes', async () => {
console.log('🧪 Test cohérence variations entre stacks...');
const testData = realTestScenarios[0]; // Utiliser premier scénario
const xmlTemplate = Buffer.from(`<?xml version='1.0' encoding='UTF-8'?>
<article>
<h1>|Titre{{T0}}{Titre pour "${testData.csvData.mc0}"}|</h1>
<content>|Contenu{{MC0}}{Contenu sur "${testData.csvData.mc0}"}|</content>
</article>`).toString('base64');
// Test différentes stacks
const stacks = ['lightEnhancement', 'standardEnhancement', 'fullEnhancement'];
const results = {};
for (const stack of stacks) {
console.log(`🧪 Test avec stack: ${stack}`);
const result = await handleModularWorkflow({
csvData: testData.csvData,
xmlTemplate,
selectiveStack: stack,
adversarialMode: 'none',
humanSimulationMode: 'none',
patternBreakingMode: 'none',
source: `stack_test_${stack}`
});
assert.ok(result.success, `Stack ${stack} doit réussir`);
assert.ok(result.compiledText, `Stack ${stack} doit générer du texte`);
results[stack] = {
text: result.compiledText,
length: result.compiledText.length,
duration: result.stats?.totalDuration || 0
};
console.log(`📊 ${stack}: ${results[stack].length} chars, ${results[stack].duration}ms`);
}
// ============= VALIDATIONS VARIATIONS =============
// Toutes les stacks doivent produire du contenu
for (const stack of stacks) {
assert.ok(results[stack].length > 100,
`Stack ${stack} doit produire plus de 100 chars`);
}
// Les contenus doivent être différents (variations)
const texts = stacks.map(stack => results[stack].text);
for (let i = 0; i < texts.length; i++) {
for (let j = i + 1; j < texts.length; j++) {
const similarity = calculateSimilarity(texts[i], texts[j]);
console.log(`📊 Similarité ${stacks[i]}${stacks[j]}: ${Math.round(similarity * 100)}%`);
// Les textes ne doivent pas être identiques (= stacks différentes)
assert.ok(similarity < 0.95,
`Textes trop similaires entre ${stacks[i]} et ${stacks[j]}: ${Math.round(similarity * 100)}%`);
}
}
// Progression logique des stacks (plus de contenu avec stacks plus avancées)
const lightLength = results['lightEnhancement'].length;
const standardLength = results['standardEnhancement'].length;
const fullLength = results['fullEnhancement'].length;
console.log(`📊 Progression: Light(${lightLength}) → Standard(${standardLength}) → Full(${fullLength})`);
// Note: Cette vérification peut être assouplie selon l'implémentation
// assert.ok(standardLength >= lightLength * 0.8, 'Standard doit être au moins 80% de Light');
console.log('✅ Variations cohérentes entre stacks validées');
}, { timeout: 180000 });
// ========================================
// TEST: PERSONNALITÉ APPLIQUÉE
// ========================================
test('🔥 CRITIQUE: Personnalité réellement appliquée dans le contenu', async () => {
console.log('🧪 Test application personnalité...');
const baseData = {
mc0: 'plaque personnalisée',
t0: 'Guide plaque personnalisée',
tMinus1: 'décoration personnalisée',
mcPlus1: 'plaque gravée,plaque métal',
tPlus1: 'Plaque Gravée,Plaque Métal'
};
const personalities = [
{ nom: 'Marc', style: 'technique', ton: 'professionnel' },
{ nom: 'Sophie', style: 'déco', ton: 'chaleureux' },
{ nom: 'Laurent', style: 'commercial', ton: 'persuasif' }
];
const xmlTemplate = Buffer.from(`<?xml version='1.0' encoding='UTF-8'?>
<article>
<intro>|Introduction{{MC0}}{Introduction sur "${baseData.mc0}" avec style de personnalité}|</intro>
</article>`).toString('base64');
const personalityResults = {};
for (const personality of personalities) {
console.log(`🧪 Test personnalité: ${personality.nom} (${personality.style})`);
const result = await handleModularWorkflow({
csvData: { ...baseData, personality },
xmlTemplate,
selectiveStack: 'lightEnhancement',
adversarialMode: 'none',
humanSimulationMode: 'none',
patternBreakingMode: 'none',
source: `personality_test_${personality.nom}`
});
assert.ok(result.success, `Personnalité ${personality.nom} doit réussir`);
assert.ok(result.compiledText, `${personality.nom} doit générer du texte`);
personalityResults[personality.nom] = {
text: result.compiledText,
style: personality.style,
tone: personality.ton
};
console.log(`📊 ${personality.nom}: ${result.compiledText.length} chars`);
}
// ============= VALIDATIONS PERSONNALITÉ =============
// Tous doivent contenir le mot-clé de base
for (const name of Object.keys(personalityResults)) {
const text = personalityResults[name].text.toLowerCase();
assert.ok(text.includes('plaque'),
`Personnalité ${name} doit contenir le mot-clé principal`);
}
// Les textes doivent être différents (= personnalités appliquées)
const names = Object.keys(personalityResults);
for (let i = 0; i < names.length; i++) {
for (let j = i + 1; j < names.length; j++) {
const text1 = personalityResults[names[i]].text;
const text2 = personalityResults[names[j]].text;
const similarity = calculateSimilarity(text1, text2);
console.log(`📊 Similarité ${names[i]}${names[j]}: ${Math.round(similarity * 100)}%`);
assert.ok(similarity < 0.9,
`Personnalités trop similaires ${names[i]}${names[j]}: ${Math.round(similarity * 100)}%`);
}
}
console.log('✅ Personnalités correctement appliquées');
}, { timeout: 180000 });
});
// ========================================
// HELPER FUNCTIONS
// ========================================
function calculateSimilarity(text1, text2) {
// Calcul de similarité basique (Jaccard sur mots)
const words1 = new Set(text1.toLowerCase().split(/\s+/));
const words2 = new Set(text2.toLowerCase().split(/\s+/));
const intersection = new Set([...words1].filter(x => words2.has(x)));
const union = new Set([...words1, ...words2]);
return intersection.size / union.size;
}