package dev.lions.unionflow.server.service; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO; import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse; import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide; import dev.lions.unionflow.server.api.enums.solidarite.StatutAide; import dev.lions.unionflow.server.api.enums.solidarite.TypeAide; import dev.lions.unionflow.server.entity.DemandeAide; import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.mapper.DemandeAideMapper; import io.quarkus.test.InjectMock; import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; /** * Tests ciblant les branches L99 et L214 de {@link DemandeAideService}. * *

Ces branches nécessitent de contrôler ce que retourne {@link DemandeAideMapper#toDTO}, * tout en utilisant les vrais repositories pour éviter les NPE causés par le champ * {@code entityManager} null dans les spies Quarkus {@code @InjectMock} sur les dépôts * qui surchargent {@code findById(UUID)}. * *

Stratégie : {@code @InjectMock} uniquement sur le mapper, repos réels via * {@code @TestTransaction} + entités persistées manuellement. * *

*/ @QuarkusTest @DisplayName("DemandeAideService — branches L99 et L214 (vrais repositories)") class DemandeAideServiceL99L214Test { @Inject DemandeAideService demandeAideService; /** Seul mock : le mapper, pour contrôler ce que retourne toDTO() */ @InjectMock DemandeAideMapper demandeAideMapper; @Inject EntityManager entityManager; // ========================================================================= // Helpers — persistance d'entités minimales valides // ========================================================================= private Membre creerMembre() { Membre m = Membre.builder() .numeroMembre("UF-L99-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase()) .prenom("Test") .nom("L99") .email("l99." + UUID.randomUUID() + "@test.com") .dateNaissance(LocalDate.of(1990, 1, 1)) .build(); m.setDateCreation(LocalDateTime.now()); m.setActif(true); m.setStatutCompte("ACTIF"); entityManager.persist(m); entityManager.flush(); return m; } private Organisation creerOrganisation() { Organisation org = Organisation.builder() .nom("Org L99 " + UUID.randomUUID().toString().substring(0, 8)) .email("org.l99." + UUID.randomUUID() + "@test.com") .typeOrganisation("ASSOCIATION") .statut("ACTIF") .build(); org.setDateCreation(LocalDateTime.now()); org.setActif(true); entityManager.persist(org); entityManager.flush(); return org; } // ========================================================================= // L99 — creerDemande() — ternaire false : // auteurId(response.getMembreDemandeurId() != null ? ... : null) // // Le mapper mock retourne un DTO avec membreDemandeurId = null. // → auteurId du HistoriqueStatutDTO initial est null (branche false L99). // ========================================================================= @Test @TestTransaction @DisplayName("L99 branche false : auteurId=null dans l'historique initial quand mapper retourne membreDemandeurId=null") void creerDemande_mapperRetourneMembreDemandeurIdNull_auteurIdNullDansHistorique_L99() { Membre membre = creerMembre(); Organisation org = creerOrganisation(); // L'entité retournée par le mapper mock doit être persistable : on lui donne // le vrai demandeur et la vraie organisation pour satisfaire les contraintes NOT NULL. DemandeAide entityMockRetour = DemandeAide.builder() .titre("Demande L99") .description("Desc L99") .typeAide(TypeAide.AIDE_ALIMENTAIRE) .statut(StatutAide.EN_ATTENTE) .demandeur(membre) .organisation(org) .build(); when(demandeAideMapper.toEntity(any(), any(), any(), any())).thenReturn(entityMockRetour); // toDTO retourne un DTO SANS membreDemandeurId (null) // → condition L99 : response.getMembreDemandeurId() != null → false // → auteurId = null dans le HistoriqueStatutDTO initial DemandeAideResponse dtoNull = new DemandeAideResponse(); dtoNull.setId(UUID.randomUUID()); dtoNull.setStatut(StatutAide.EN_ATTENTE); dtoNull.setPriorite(PrioriteAide.NORMALE); dtoNull.setTypeAide(TypeAide.AIDE_ALIMENTAIRE); dtoNull.setMontantDemande(new BigDecimal("200.00")); dtoNull.setDateCreation(LocalDateTime.now()); dtoNull.setMembreDemandeurId(null); // ← null → L99 branche false → auteurId = null when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(dtoNull); dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest request = dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest.builder() .titre("Demande L99") .description("Description L99") .typeAide(TypeAide.AIDE_ALIMENTAIRE) .priorite(PrioriteAide.NORMALE) .membreDemandeurId(membre.getId()) .associationId(org.getId()) .build(); DemandeAideResponse result = demandeAideService.creerDemande(request); assertThat(result).isNotNull(); // Le service crée un HistoriqueStatutDTO initial avec auteurId = null (L99 branche false) assertThat(result.getHistoriqueStatuts()) .as("Un historique initial doit être présent") .isNotNull() .hasSize(1); assertThat(result.getHistoriqueStatuts().get(0).getAuteurId()) .as("L99 branche false : auteurId doit être null quand membreDemandeurId est null") .isNull(); } // ========================================================================= // L214 — changerStatut() — branche true : // response.getHistoriqueStatuts() != null → utilise la liste existante // // Le mapper mock retourne un DTO avec historiqueStatuts déjà renseigné (non-null). // → l'historique existant est combiné avec le nouveau → L214 branche true. // ========================================================================= @Test @TestTransaction @DisplayName("L214 branche true : historique existant conservé quand mapper retourne historiqueStatuts non-null") void changerStatut_mapperRetourneHistoriqueNonNull_historiqueCombine_L214() { Membre membre = creerMembre(); Organisation org = creerOrganisation(); // Persister une vraie DemandeAide (statut EN_ATTENTE → transition vers EN_COURS_EVALUATION valide) DemandeAide demande = DemandeAide.builder() .titre("Demande L214") .description("Desc L214") .typeAide(TypeAide.AIDE_ALIMENTAIRE) .statut(StatutAide.EN_ATTENTE) .demandeur(membre) .organisation(org) .build(); entityManager.persist(demande); entityManager.flush(); UUID demandeId = demande.getId(); // Le mapper retourne un DTO avec historiqueStatuts DÉJÀ NON-NULL // → L214 branche true : la liste existante est utilisée + le nouvel historique s'y ajoute HistoriqueStatutDTO historiqueExistant = HistoriqueStatutDTO.builder() .id(UUID.randomUUID().toString()) .ancienStatut(null) .nouveauStatut(StatutAide.EN_ATTENTE) .dateChangement(LocalDateTime.now().minusHours(1)) .motif("Création initiale") .estAutomatique(true) .build(); DemandeAideResponse dtoAvecHistorique = new DemandeAideResponse(); dtoAvecHistorique.setId(demandeId); dtoAvecHistorique.setStatut(StatutAide.EN_COURS_EVALUATION); dtoAvecHistorique.setPriorite(PrioriteAide.NORMALE); dtoAvecHistorique.setTypeAide(TypeAide.AIDE_ALIMENTAIRE); dtoAvecHistorique.setMontantDemande(new BigDecimal("100.00")); dtoAvecHistorique.setDateCreation(LocalDateTime.now().minusDays(1)); dtoAvecHistorique.setMembreDemandeurId(membre.getId()); dtoAvecHistorique.setHistoriqueStatuts(List.of(historiqueExistant)); // ← non-null → L214 true when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(dtoAvecHistorique); DemandeAideResponse result = demandeAideService.changerStatut( demandeId, StatutAide.EN_COURS_EVALUATION, "Évaluation débutée"); // L214 branche true : 1 historique existant + 1 nouvel historique = 2 entrées assertThat(result.getHistoriqueStatuts()) .as("L214 branche true : historique existant (1) + nouveau (1) = 2 entrées") .hasSize(2); assertThat(result.getHistoriqueStatuts().get(0).getMotif()) .isEqualTo("Création initiale"); assertThat(result.getHistoriqueStatuts().get(1).getNouveauStatut()) .isEqualTo(StatutAide.EN_COURS_EVALUATION); } }