Files
btpxpress-frontend/utils/workflowTester.ts
2025-10-01 01:39:07 +00:00

547 lines
22 KiB
TypeScript

/**
* 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<WorkflowTestResult> {
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<WorkflowTestResult> {
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<WorkflowTestResult> {
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<WorkflowTestResult> {
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<WorkflowTestResult> {
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<WorkflowTestResult[]> {
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();