Files
btpxpress-backend/src/test/java/dev/lions/btpxpress/application/service/StatisticsServiceCompletTest.java
dahoud fba7666268 Refactor: Standardisation complète de l'architecture REST
🔧 RESTRUCTURATION
- UserResource déplacé de adapter.http vers application.rest
- FournisseurResource déplacé vers application.rest
- Suppression des contrôleurs obsolètes (presentation.controller)
- Suppression de MaterielFournisseurService en doublon

📝 STANDARDISATION DOCUMENTATION
- Annotations OpenAPI uniformes (@Operation, @APIResponse, @Parameter)
- Descriptions concises et cohérentes pour tous les endpoints
- Codes de réponse HTTP standards (200, 201, 400, 404, 500)

🛠️ ENDPOINTS USERS STANDARDISÉS
- GET /api/v1/users - Liste tous les utilisateurs
- GET /api/v1/users/{id} - Détails d'un utilisateur
- GET /api/v1/users/stats - Statistiques globales
- GET /api/v1/users/count - Comptage
- GET /api/v1/users/pending - Utilisateurs en attente
- POST /api/v1/users - Création
- PUT /api/v1/users/{id} - Mise à jour
- DELETE /api/v1/users/{id} - Suppression
- POST /api/v1/users/{id}/approve - Approbation
- POST /api/v1/users/{id}/reject - Rejet
- PUT /api/v1/users/{id}/status - Changement de statut
- PUT /api/v1/users/{id}/role - Changement de rôle

⚠️ GESTION D'ERREURS
- Format uniforme: Map.of("error", "message")
- Codes HTTP cohérents avec les autres ressources
- Validation des entrées standardisée

 VALIDATION
- Compilation réussie: mvn clean compile -DskipTests
- Pattern conforme aux autres ressources (PhaseTemplate, Fournisseur)
- Documentation OpenAPI/Swagger complète et cohérente
2025-10-23 10:43:32 +00:00

320 lines
11 KiB
Java

package dev.lions.btpxpress.application.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import dev.lions.btpxpress.domain.core.entity.*;
import dev.lions.btpxpress.domain.infrastructure.repository.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* Tests complets pour StatisticsService Couverture exhaustive de toutes les méthodes et cas d'usage
*/
@ExtendWith(MockitoExtension.class)
@DisplayName("📊 Tests StatisticsService - Calculs de Statistiques")
class StatisticsServiceCompletTest {
@InjectMocks StatisticsService statisticsService;
@Mock ChantierRepository chantierRepository;
@Mock PhaseChantierRepository phaseChantierRepository;
@Mock EmployeRepository employeRepository;
@Mock EquipeRepository equipeRepository;
@Mock FournisseurRepository fournisseurRepository;
@Mock StockRepository stockRepository;
@Mock BonCommandeRepository bonCommandeRepository;
private UUID chantierId;
private UUID equipeId;
private UUID employeId;
private UUID fournisseurId;
private Chantier testChantier;
private Equipe testEquipe;
private Stock testStock;
private BonCommande testCommande;
@BeforeEach
void setUp() {
Mockito.reset(
chantierRepository,
phaseChantierRepository,
employeRepository,
equipeRepository,
fournisseurRepository,
stockRepository,
bonCommandeRepository);
chantierId = UUID.randomUUID();
equipeId = UUID.randomUUID();
employeId = UUID.randomUUID();
fournisseurId = UUID.randomUUID();
testChantier = new Chantier();
testChantier.setId(chantierId);
testChantier.setNom("Chantier Test");
testChantier.setStatut(StatutChantier.EN_COURS);
testChantier.setMontantPrevu(new BigDecimal("100000"));
testChantier.setMontantReel(new BigDecimal("80000"));
testChantier.setDateDebutPrevue(LocalDate.now().minusDays(30));
testChantier.setDateFinPrevue(LocalDate.now().plusDays(30));
testEquipe = new Equipe();
testEquipe.setId(equipeId);
testEquipe.setNom("Équipe Test");
testEquipe.setStatut(StatutEquipe.ACTIVE);
testStock = new Stock();
testStock.setId(UUID.randomUUID());
testStock.setDesignation("Article Test");
testStock.setQuantiteStock(new BigDecimal("100"));
testStock.setQuantiteMinimum(new BigDecimal("10"));
testStock.setCoutMoyenPondere(new BigDecimal("50"));
testStock.setCategorie(CategorieStock.OUTILLAGE);
testStock.setDateDerniereSortie(LocalDateTime.now().minusDays(5));
testCommande = new BonCommande();
testCommande.setId(UUID.randomUUID());
testCommande.setNumero("CMD-001");
testCommande.setMontantTTC(new BigDecimal("15000"));
testCommande.setDateCommande(LocalDate.now().minusDays(10));
testCommande.setDateLivraisonReelle(LocalDate.now().minusDays(3));
testCommande.setStatut(StatutBonCommande.LIVREE);
}
@Nested
@DisplayName("🏗️ Statistiques de Performance des Chantiers")
class PerformanceChantierTests {
@Test
@DisplayName("Calculer performance chantiers - période valide")
void testCalculerPerformanceChantiers_Valid() {
// Arrange
LocalDate dateDebut = LocalDate.now().minusMonths(1);
LocalDate dateFin = LocalDate.now();
List<Chantier> chantiers = Arrays.asList(testChantier);
when(chantierRepository.findChantiersParPeriode(dateDebut, dateFin)).thenReturn(chantiers);
// Act
Map<String, Object> result =
statisticsService.calculerPerformanceChantiers(dateDebut, dateFin);
// Assert
assertNotNull(result);
assertTrue(result.containsKey("chantiersParStatut"));
assertTrue(result.containsKey("tauxRespectDelais"));
assertTrue(result.containsKey("rentabiliteMoyenne"));
verify(chantierRepository).findChantiersParPeriode(dateDebut, dateFin);
}
@Test
@DisplayName("Calculer performance chantiers - aucun chantier")
void testCalculerPerformanceChantiers_Empty() {
// Arrange
LocalDate dateDebut = LocalDate.now().minusMonths(1);
LocalDate dateFin = LocalDate.now();
when(chantierRepository.findChantiersParPeriode(dateDebut, dateFin))
.thenReturn(Collections.emptyList());
// Act
Map<String, Object> result =
statisticsService.calculerPerformanceChantiers(dateDebut, dateFin);
// Assert
assertNotNull(result);
assertTrue(result.containsKey("chantiersParStatut"));
assertEquals(100.0, result.get("tauxRespectDelais"));
assertEquals(0.0, result.get("rentabiliteMoyenne"));
verify(chantierRepository).findChantiersParPeriode(dateDebut, dateFin);
}
}
@Nested
@DisplayName("👥 Statistiques de Performance des Équipes")
class PerformanceEquipeTests {
@Test
@DisplayName("Calculer productivité équipes")
void testCalculerProductiviteEquipes() {
// Arrange
List<Equipe> equipes = Arrays.asList(testEquipe);
List<PhaseChantier> phases =
Arrays.asList(
createPhaseChantier(StatutPhaseChantier.TERMINEE),
createPhaseChantier(StatutPhaseChantier.EN_COURS));
when(equipeRepository.listAll()).thenReturn(equipes);
when(phaseChantierRepository.findPhasesByEquipe(equipeId)).thenReturn(phases);
// Act
Map<String, Object> result = statisticsService.calculerProductiviteEquipes();
// Assert
assertNotNull(result);
assertTrue(result.containsKey("productiviteParEquipe"));
verify(equipeRepository).listAll();
verify(phaseChantierRepository).findPhasesByEquipe(equipeId);
}
private PhaseChantier createPhaseChantier(StatutPhaseChantier statut) {
PhaseChantier phase = new PhaseChantier();
phase.setId(UUID.randomUUID());
phase.setStatut(statut);
return phase;
}
}
@Nested
@DisplayName("📦 Statistiques de Rotation des Stocks")
class RotationStockTests {
@Test
@DisplayName("Calculer rotation stocks")
void testCalculerRotationStocks() {
// Arrange
List<Stock> stocks = Arrays.asList(testStock);
when(stockRepository.listAll()).thenReturn(stocks);
// Act
Map<String, Object> result = statisticsService.calculerRotationStocks();
// Assert
assertNotNull(result);
assertTrue(result.containsKey("articlesLesPlusActifs"));
assertTrue(result.containsKey("articlesSansMouvement"));
assertTrue(result.containsKey("valeurStocksDormants"));
assertTrue(result.containsKey("rotationParCategorie"));
verify(stockRepository).listAll();
}
@Test
@DisplayName("Calculer rotation stocks - stocks vides")
void testCalculerRotationStocks_Empty() {
// Arrange
when(stockRepository.listAll()).thenReturn(Collections.emptyList());
// Act
Map<String, Object> result = statisticsService.calculerRotationStocks();
// Assert
assertNotNull(result);
assertEquals(0, ((List<?>) result.get("articlesLesPlusActifs")).size());
assertEquals(0, result.get("articlesSansMouvement"));
assertEquals(BigDecimal.ZERO, result.get("valeurStocksDormants"));
verify(stockRepository).listAll();
}
}
@Nested
@DisplayName("🛒 Analyse des Tendances d'Achat")
class TendancesAchatTests {
@Test
@DisplayName("Calculer tendances")
void testCalculerTendances() {
// Arrange
LocalDate dateDebut = LocalDate.now().minusMonths(1);
LocalDate dateFin = LocalDate.now();
List<BonCommande> commandes = Arrays.asList(testCommande);
when(bonCommandeRepository.findCommandesParPeriode(dateDebut, dateFin)).thenReturn(commandes);
// Act
Map<String, Object> result =
statisticsService.calculerTendances(dateDebut, dateFin, "MENSUEL");
// Assert
assertNotNull(result);
assertTrue(result.containsKey("chantiers"));
assertTrue(result.containsKey("achats"));
verify(bonCommandeRepository).findCommandesParPeriode(dateDebut, dateFin);
}
}
@Nested
@DisplayName("⭐ Évaluation de la Qualité des Fournisseurs")
class QualiteFournisseurTests {
@Test
@DisplayName("Calculer qualité fournisseurs")
void testCalculerQualiteFournisseurs() {
// Arrange
Fournisseur fournisseur = new Fournisseur();
fournisseur.setId(fournisseurId);
fournisseur.setNom("Fournisseur Test");
fournisseur.setEmail("test@fournisseur.com");
fournisseur.setTelephone("0123456789");
fournisseur.setAdresse("123 Rue Test");
fournisseur.setActif(true);
List<Fournisseur> fournisseurs = Arrays.asList(fournisseur);
List<BonCommande> commandesEnCours = Collections.emptyList();
when(fournisseurRepository.listAll()).thenReturn(fournisseurs);
when(bonCommandeRepository.findByFournisseurAndStatut(
fournisseurId, StatutBonCommande.ENVOYEE))
.thenReturn(commandesEnCours);
// Act
Map<String, Object> result = statisticsService.calculerQualiteFournisseurs();
// Assert
assertNotNull(result);
assertTrue(result.containsKey("qualiteParFournisseur"));
assertTrue(result.containsKey("meilleursFournisseurs"));
assertTrue(result.containsKey("fournisseursASurveiller"));
verify(fournisseurRepository).listAll();
}
}
@Nested
@DisplayName("📈 Calculs de Tendances")
class TendancesTests {
@Test
@DisplayName("Calculer tendances avec granularité mensuelle")
void testCalculerTendancesMensuel() {
// Arrange
LocalDate dateDebut = LocalDate.now().minusMonths(3);
LocalDate dateFin = LocalDate.now();
List<Chantier> chantiers = Arrays.asList(testChantier);
List<BonCommande> commandes = Arrays.asList(testCommande);
when(chantierRepository.findChantiersParPeriode(dateDebut, dateFin)).thenReturn(chantiers);
when(bonCommandeRepository.findCommandesParPeriode(dateDebut, dateFin)).thenReturn(commandes);
// Act
Map<String, Object> result =
statisticsService.calculerTendances(dateDebut, dateFin, "MENSUEL");
// Assert
assertNotNull(result);
assertTrue(result.containsKey("chantiers"));
assertTrue(result.containsKey("achats"));
verify(chantierRepository).findChantiersParPeriode(dateDebut, dateFin);
verify(bonCommandeRepository).findCommandesParPeriode(dateDebut, dateFin);
}
}
}