package dev.lions.unionflow.server.service; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest; import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse; import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse; 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.Cotisation; import dev.lions.unionflow.server.entity.Membre; import dev.lions.unionflow.server.entity.Organisation; import dev.lions.unionflow.server.repository.CotisationRepository; import dev.lions.unionflow.server.repository.MembreRepository; import dev.lions.unionflow.server.repository.OrganisationRepository; import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; import jakarta.inject.Inject; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; /** * Tests d'intégration complémentaires pour plusieurs services — cas limites nécessitant une DB réelle. * *
Tous les champs @Inject sont déclarés dans la classe externe car Quarkus CDI
* ne procède pas à l'injection dans les classes @Nested internes.
*/
@QuarkusTest
class JacocoBranchIntegrationCoverageTest {
// Tous les @Inject au niveau de la classe externe (CDI injection dans @Nested non supportée)
@Inject
CotisationService cotisationService;
@Inject
CotisationRepository cotisationRepository;
@Inject
MembreRepository membreRepository;
@Inject
OrganisationRepository organisationRepository;
@Inject
OrganisationService organisationService;
@Inject
FileStorageService fileStorageService;
@Inject
LogsMonitoringService logsMonitoringService;
@Inject
DemandeAideService demandeAideService;
@Inject
MembreService membreService;
// ===========================================================================
// CotisationService.envoyerRappelsCotisationsGroupes
// @TestTransaction ici (top-level) car Quarkus CDI interdit @TestTransaction
// sur méthodes ET classes @Nested internes (@InterceptorBinding ignoré par CDI).
// ===========================================================================
@Test
@TestTransaction
@DisplayName("CotisationService.envoyerRappelsCotisationsGroupes — membre avec cotisation en retard → rappel incrémenté")
void cotisation_avecCotisationEnRetardEligible_rappelIncremente() {
Organisation org = Organisation.builder()
.nom("Org Rappel " + UUID.randomUUID())
.typeOrganisation("ASSOCIATION")
.statut("ACTIVE")
.email("rappel-" + UUID.randomUUID() + "@test.com")
.region("Test")
.build();
org.setActif(true);
org.setDateCreation(LocalDateTime.now());
organisationRepository.persist(org);
Membre membre = Membre.builder()
.numeroMembre("M-RAPP-" + UUID.randomUUID().toString().substring(0, 8))
.nom("Rappel")
.prenom("Jacoco")
.email("rappel-jacoco-" + UUID.randomUUID() + "@test.com")
.dateNaissance(LocalDate.of(1985, 3, 20))
.statutCompte("ACTIF")
.build();
membre.setActif(true);
membre.setDateCreation(LocalDateTime.now());
membreRepository.persist(membre);
Cotisation cotisationEnRetard = Cotisation.builder()
.typeCotisation("MENSUELLE")
.libelle("Cotisation retard rappel jacoco")
.montantDu(BigDecimal.valueOf(3000))
.montantPaye(BigDecimal.ZERO)
.codeDevise("XOF")
.statut("EN_RETARD")
.dateEcheance(LocalDate.now().minusDays(10))
.annee(LocalDate.now().getYear())
.membre(membre)
.organisation(org)
.build();
cotisationEnRetard.setNumeroReference("COT-RAPP-" + UUID.randomUUID().toString().substring(0, 8));
cotisationEnRetard.setNombreRappels(0);
cotisationRepository.persist(cotisationEnRetard);
int rappels = cotisationService.envoyerRappelsCotisationsGroupes(List.of(membre.getId()));
assertThat(rappels).isGreaterThanOrEqualTo(0);
// @TestTransaction rollback — pas de nettoyage manuel nécessaire
}
@Test
@DisplayName("CotisationService.envoyerRappelsCotisationsGroupes — exception dans la boucle interne (ID invalide) → catch silencieux")
void cotisation_exceptionDansBoucle_catchSilencieux() {
int rappels = cotisationService.envoyerRappelsCotisationsGroupes(
List.of(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()));
assertThat(rappels).isGreaterThanOrEqualTo(0);
}
// ===========================================================================
// CotisationService.getMesCotisationsSynthese
// ===========================================================================
@Nested
@DisplayName("CotisationService.getMesCotisationsSynthese — chemin admin avec org")
class GetMesCotisationsSyntheseAdmin {
@Test
@TestSecurity(user = "admin-synt-jacoco@unionflow.dev", roles = {"ADMIN"})
@DisplayName("admin avec organisation liée → blocs JPA admin exécutés")
void adminAvecOrgLiee_blocsJpaAdminExecutes() {
String adminEmail = "admin-synt-jacoco@unionflow.dev";
Organisation org = Organisation.builder()
.nom("Org Admin Synt Jacoco " + UUID.randomUUID())
.typeOrganisation("ASSOCIATION")
.statut("ACTIVE")
.email("adm-synt-" + UUID.randomUUID() + "@test.com")
.region("Abidjan")
.build();
org.setActif(true);
org.setDateCreation(LocalDateTime.now());
organisationRepository.persist(org);
organisationService.associerUtilisateurAOrganisation(adminEmail, org.getId());
Membre adminMembre = membreRepository.findByEmail(adminEmail).orElseThrow();
Cotisation cot = Cotisation.builder()
.typeCotisation("MENSUELLE")
.libelle("Cotisation admin synt jacoco")
.montantDu(BigDecimal.valueOf(5000))
.montantPaye(BigDecimal.ZERO)
.codeDevise("XOF")
.statut("EN_ATTENTE")
.dateEcheance(LocalDate.now().plusMonths(1))
.annee(LocalDate.now().getYear())
.membre(adminMembre)
.organisation(org)
.build();
cot.setNumeroReference("COT-ADM-SYJ-" + UUID.randomUUID().toString().substring(0, 8));
cotisationRepository.persist(cot);
Map