- 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>
320 lines
13 KiB
JavaScript
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;
|
|
} |