/** * Testeur de workflows pour valider tous les processus métier * Simule les interactions utilisateur réelles avec le système */ import testDataService from '../services/testDataService'; import phaseValidationService from '../services/phaseValidationService'; import chantierTemplateService from '../services/chantierTemplateService'; import type { PhaseChantier, Chantier, Client } from '../types/btp'; import type { TypeChantier } from '../types/chantier-templates'; export interface WorkflowTestResult { workflowName: string; success: boolean; steps: WorkflowStep[]; duration: number; errorMessage?: string; } export interface WorkflowStep { stepName: string; success: boolean; message: string; data?: any; } class WorkflowTester { /** * Test du workflow complet de création d'un projet */ async testProjectCreationWorkflow(): Promise { const startTime = Date.now(); const steps: WorkflowStep[] = []; try { // Étape 1: Création du client steps.push({ stepName: 'Création client', success: true, message: 'Client créé avec succès', data: testDataService.generateTestClient(1) }); const client = steps[0].data as Client; // Étape 2: Sélection du type de chantier steps.push({ stepName: 'Sélection type chantier', success: true, message: 'Type MAISON_INDIVIDUELLE sélectionné', data: 'MAISON_INDIVIDUELLE' }); const typeChantier = steps[1].data as TypeChantier; // Étape 3: Prévisualisation des phases (template) const template = chantierTemplateService.getTemplate(typeChantier); steps.push({ stepName: 'Prévisualisation phases', success: template.phases.length > 0, message: `${template.phases.length} phases prévues pour ce type`, data: template }); // Étape 4: Création du chantier const chantier = testDataService.generateTestChantier(1, typeChantier, client); steps.push({ stepName: 'Création chantier', success: !!chantier.id && !!chantier.typeChantier, message: `Chantier "${chantier.nom}" créé`, data: chantier }); // Étape 5: Génération automatique des phases const phases = testDataService.generateTestPhases(chantier); steps.push({ stepName: 'Génération phases', success: phases.length > 0, message: `${phases.length} phases générées automatiquement`, data: phases }); // Étape 6: Validation de la cohérence const validation = phaseValidationService.validateProjectSchedule(phases); steps.push({ stepName: 'Validation cohérence', success: validation.globalErrors.length === 0, message: validation.isValid ? 'Planning cohérent' : `${validation.globalErrors.length} erreurs détectées`, data: validation }); return { workflowName: 'Création de projet complet', success: steps.every(s => s.success), steps, duration: Date.now() - startTime }; } catch (error) { return { workflowName: 'Création de projet complet', success: false, steps, duration: Date.now() - startTime, errorMessage: error instanceof Error ? error.message : 'Erreur inconnue' }; } } /** * Test du workflow de filtrage avancé */ async testAdvancedFilteringWorkflow(): Promise { const startTime = Date.now(); const steps: WorkflowStep[] = []; try { // Génération de données de test variées const dataset = testDataService.generateTestDataset(5); const allPhases = dataset.flatMap(d => d.phases); steps.push({ stepName: 'Génération données test', success: allPhases.length > 0, message: `${allPhases.length} phases générées pour ${dataset.length} projets`, data: { dataset, allPhases } }); // Test filtre par statut const filtres = { enCours: allPhases.filter(p => p.statut === 'EN_COURS'), terminees: allPhases.filter(p => p.statut === 'TERMINEE'), planifiees: allPhases.filter(p => p.statut === 'PLANIFIEE'), critiques: allPhases.filter(p => p.critique), sousPhases: allPhases.filter(p => p.phaseParent), principales: allPhases.filter(p => !p.phaseParent) }; steps.push({ stepName: 'Filtres par statut', success: true, message: `Filtres appliqués: ${filtres.enCours.length} en cours, ${filtres.terminees.length} terminées, ${filtres.planifiees.length} planifiées`, data: filtres }); // Test filtre hiérarchique const hierarchie = { principales: filtres.principales.length, sousPhases: filtres.sousPhases.length, total: allPhases.length }; steps.push({ stepName: 'Filtre hiérarchique', success: hierarchie.principales + hierarchie.sousPhases === hierarchie.total, message: `Hiérarchie: ${hierarchie.principales} principales + ${hierarchie.sousPhases} sous-phases = ${hierarchie.total} total`, data: hierarchie }); // Test filtre par criticité steps.push({ stepName: 'Filtre par criticité', success: true, message: `${filtres.critiques.length} phases critiques identifiées`, data: filtres.critiques }); // Test filtre par dates (simulation) const aujourd_hui = new Date(); const phasesActuelles = allPhases.filter(p => { if (!p.dateDebutPrevue || !p.dateFinPrevue) return false; const debut = new Date(p.dateDebutPrevue); const fin = new Date(p.dateFinPrevue); return debut <= aujourd_hui && fin >= aujourd_hui; }); steps.push({ stepName: 'Filtre par période', success: true, message: `${phasesActuelles.length} phases dans la période actuelle`, data: phasesActuelles }); return { workflowName: 'Filtrage avancé', success: steps.every(s => s.success), steps, duration: Date.now() - startTime }; } catch (error) { return { workflowName: 'Filtrage avancé', success: false, steps, duration: Date.now() - startTime, errorMessage: error instanceof Error ? error.message : 'Erreur inconnue' }; } } /** * Test du workflow de validation et démarrage de phases */ async testPhaseValidationWorkflow(): Promise { const startTime = Date.now(); const steps: WorkflowStep[] = []; try { // Génération d'un projet avec prérequis const client = testDataService.generateTestClient(1); const chantier = testDataService.generateTestChantier(1, 'MAISON_INDIVIDUELLE', client); const phases = testDataService.generatePhasesWithPrerequisites(chantier); steps.push({ stepName: 'Génération projet avec prérequis', success: phases.length > 0, message: `Projet avec ${phases.length} phases et prérequis généré`, data: { chantier, phases } }); // Test validation première phase (sans prérequis) const premierePhase = phases.find(p => !p.prerequis || p.prerequis.length === 0); if (!premierePhase) { throw new Error('Aucune phase sans prérequis trouvée'); } const validationPremiere = phaseValidationService.validatePhaseStart(premierePhase, phases); steps.push({ stepName: 'Validation première phase', success: validationPremiere.canStart, message: `Première phase "${premierePhase.nom}" peut ${validationPremiere.canStart ? '' : 'ne pas '}démarrer`, data: validationPremiere }); // Simulation démarrage première phase premierePhase.statut = 'EN_COURS'; premierePhase.dateDebutReelle = new Date().toISOString().split('T')[0]; premierePhase.pourcentageAvancement = 30; steps.push({ stepName: 'Démarrage première phase', success: premierePhase.statut === 'EN_COURS', message: `Phase "${premierePhase.nom}" démarrée avec succès`, data: premierePhase }); // Test validation phase avec prérequis const phaseAvecPrerequis = phases.find(p => p.prerequis && p.prerequis.length > 0); if (phaseAvecPrerequis) { const validationPrerequis = phaseValidationService.validatePhaseStart(phaseAvecPrerequis, phases); const prerequis = phaseAvecPrerequis.prerequis!; const prerequisTermines = prerequis.every(prereqId => { const prereq = phases.find(p => p.id === prereqId); return prereq && prereq.statut === 'TERMINEE'; }); steps.push({ stepName: 'Validation phase avec prérequis', success: prerequisTermines || !validationPrerequis.canStart, message: `Phase "${phaseAvecPrerequis.nom}" - ${prerequis.length} prérequis, peut ${validationPrerequis.canStart ? '' : 'ne pas '}démarrer`, data: validationPrerequis }); } // Test termination et cascade premierePhase.statut = 'TERMINEE'; premierePhase.dateFinReelle = new Date().toISOString().split('T')[0]; premierePhase.pourcentageAvancement = 100; // Revalider les phases suivantes const phasesBloquees = phases.filter(p => p.prerequis?.includes(premierePhase.id!) && p.statut === 'PLANIFIEE' ); const validationsApres = phasesBloquees.map(p => ({ phase: p.nom, validation: phaseValidationService.validatePhaseStart(p, phases) })); steps.push({ stepName: 'Cascade de déverrouillage', success: true, message: `${phasesBloquees.length} phases déverrouillées après fin de "${premierePhase.nom}"`, data: validationsApres }); return { workflowName: 'Validation et démarrage phases', success: steps.every(s => s.success), steps, duration: Date.now() - startTime }; } catch (error) { return { workflowName: 'Validation et démarrage phases', success: false, steps, duration: Date.now() - startTime, errorMessage: error instanceof Error ? error.message : 'Erreur inconnue' }; } } /** * Test du workflow de gestion hiérarchique */ async testHierarchicalManagementWorkflow(): Promise { const startTime = Date.now(); const steps: WorkflowStep[] = []; try { // Génération d'un projet avec phases et sous-phases const client = testDataService.generateTestClient(1); const chantier = testDataService.generateTestChantier(1, 'MAISON_INDIVIDUELLE', client); const phases = testDataService.generateTestPhases(chantier); const phasesPrincipales = phases.filter(p => !p.phaseParent); const sousPhases = phases.filter(p => p.phaseParent); steps.push({ stepName: 'Génération structure hiérarchique', success: phasesPrincipales.length > 0 && sousPhases.length > 0, message: `Structure: ${phasesPrincipales.length} phases principales, ${sousPhases.length} sous-phases`, data: { phasesPrincipales, sousPhases } }); // Vérification des liens parent-enfant let liensCorrects = 0; let liensIncorrects = 0; sousPhases.forEach(sousPhase => { const parent = phases.find(p => p.id === sousPhase.phaseParent); if (parent) { liensCorrects++; } else { liensIncorrects++; } }); steps.push({ stepName: 'Vérification liens hiérarchiques', success: liensIncorrects === 0, message: `${liensCorrects} liens corrects, ${liensIncorrects} liens incorrects`, data: { liensCorrects, liensIncorrects } }); // Test d'affichage hiérarchique (simulation) const affichageHierarchique = phasesPrincipales.map(principale => ({ phase: principale, sousPhases: sousPhases.filter(sp => sp.phaseParent === principale.id), niveau: 0 })); const totalElements = affichageHierarchique.reduce((acc, item) => acc + 1 + item.sousPhases.length, 0 ); steps.push({ stepName: 'Simulation affichage hiérarchique', success: totalElements === phases.length, message: `Affichage hiérarchique: ${totalElements} éléments organisés`, data: affichageHierarchique }); // Test de filtrage hiérarchique const filtreSeulementPrincipales = phases.filter(p => !p.phaseParent); const filtreAvecSousPhases = phases; // Tous steps.push({ stepName: 'Filtrage hiérarchique', success: filtreSeulementPrincipales.length < filtreAvecSousPhases.length, message: `Filtres: ${filtreSeulementPrincipales.length} principales uniquement, ${filtreAvecSousPhases.length} avec sous-phases`, data: { filtreSeulementPrincipales, filtreAvecSousPhases } }); return { workflowName: 'Gestion hiérarchique', success: steps.every(s => s.success), steps, duration: Date.now() - startTime }; } catch (error) { return { workflowName: 'Gestion hiérarchique', success: false, steps, duration: Date.now() - startTime, errorMessage: error instanceof Error ? error.message : 'Erreur inconnue' }; } } /** * Test du workflow de templates et auto-génération */ async testTemplateAutoGenerationWorkflow(): Promise { const startTime = Date.now(); const steps: WorkflowStep[] = []; try { // Test de tous les types de chantier disponibles const typesDisponibles = chantierTemplateService.getAvailableTypes(); steps.push({ stepName: 'Récupération types chantier', success: typesDisponibles.length > 0, message: `${typesDisponibles.length} types de chantier disponibles`, data: typesDisponibles }); // Génération pour chaque type const generationsParType: any[] = []; for (const type of typesDisponibles.slice(0, 3)) { // Limiter à 3 pour les tests const client = testDataService.generateTestClient(1); const chantier = testDataService.generateTestChantier(1, type.id as TypeChantier, client); const phases = testDataService.generateTestPhases(chantier); generationsParType.push({ type: type.id, nom: type.nom, phases: phases.length, sousPhases: phases.filter(p => p.phaseParent).length, dureeEstimee: phases.reduce((acc, p) => acc + (p.dureeEstimeeHeures || 0), 0) }); } steps.push({ stepName: 'Auto-génération par type', success: generationsParType.every(g => g.phases > 0), message: `Génération réussie pour ${generationsParType.length} types`, data: generationsParType }); // Comparaison avec templates const comparaisonTemplates = generationsParType.map(gen => { const template = chantierTemplateService.getTemplate(gen.type as TypeChantier); return { type: gen.type, templatePhases: template.phases.length, generatedPhases: gen.phases, match: template.phases.length <= gen.phases // Généré peut inclure sous-phases }; }); steps.push({ stepName: 'Comparaison avec templates', success: comparaisonTemplates.every(c => c.match), message: `Correspondance templates: ${comparaisonTemplates.filter(c => c.match).length}/${comparaisonTemplates.length}`, data: comparaisonTemplates }); // Test cohérence des prérequis générés const client = testDataService.generateTestClient(1); const chantier = testDataService.generateTestChantier(1, 'MAISON_INDIVIDUELLE', client); const phasesAvecPrerequis = testDataService.generatePhasesWithPrerequisites(chantier); const prerequisValides = phasesAvecPrerequis.every(phase => { if (!phase.prerequis) return true; return phase.prerequis.every(prereqId => phasesAvecPrerequis.some(p => p.id === prereqId) ); }); steps.push({ stepName: 'Cohérence prérequis', success: prerequisValides, message: `Prérequis ${prerequisValides ? 'cohérents' : 'incohérents'} pour ${phasesAvecPrerequis.length} phases`, data: { phasesAvecPrerequis, prerequisValides } }); return { workflowName: 'Templates et auto-génération', success: steps.every(s => s.success), steps, duration: Date.now() - startTime }; } catch (error) { return { workflowName: 'Templates et auto-génération', success: false, steps, duration: Date.now() - startTime, errorMessage: error instanceof Error ? error.message : 'Erreur inconnue' }; } } /** * Exécute tous les workflows de test */ async runAllWorkflows(): Promise { const results = await Promise.all([ this.testProjectCreationWorkflow(), this.testAdvancedFilteringWorkflow(), this.testPhaseValidationWorkflow(), this.testHierarchicalManagementWorkflow(), this.testTemplateAutoGenerationWorkflow() ]); return results; } /** * Génère un rapport de workflows */ generateWorkflowReport(results: WorkflowTestResult[]): string { const totalWorkflows = results.length; const successfulWorkflows = results.filter(r => r.success).length; const totalSteps = results.reduce((acc, r) => acc + r.steps.length, 0); const successfulSteps = results.reduce((acc, r) => acc + r.steps.filter(s => s.success).length, 0); const totalDuration = results.reduce((acc, r) => acc + r.duration, 0); let report = `# Rapport des workflows BTPXpress\n\n`; report += `## Résumé global\n`; report += `- Workflows testés: ${totalWorkflows}\n`; report += `- Workflows réussis: ${successfulWorkflows} (${Math.round((successfulWorkflows/totalWorkflows)*100)}%)\n`; report += `- Étapes totales: ${totalSteps}\n`; report += `- Étapes réussies: ${successfulSteps} (${Math.round((successfulSteps/totalSteps)*100)}%)\n`; report += `- Durée totale: ${totalDuration}ms\n\n`; results.forEach(result => { report += `## ${result.workflowName}\n`; report += `**Statut:** ${result.success ? '✅ Réussi' : '❌ Échoué'}\n`; report += `**Durée:** ${result.duration}ms\n`; if (result.errorMessage) { report += `**Erreur:** ${result.errorMessage}\n`; } report += `**Étapes:**\n`; result.steps.forEach((step, index) => { const status = step.success ? '✅' : '❌'; report += `${index + 1}. ${status} ${step.stepName}: ${step.message}\n`; }); report += `\n`; }); return report; } } export default new WorkflowTester();