test: couverture Messaging + Versement + ContactPolicy + MemberBlock

Tests unitaires pour les nouveaux modules :
- Entity tests : ContactPolicyTest, ConversationParticipantTest, MemberBlockTest,
  VersementTest, VersementObjetTest
- Repository tests : ContactPolicyRepositoryTest, ConversationParticipantRepositoryTest,
  MemberBlockRepositoryTest, VersementRepositoryTest
- Resource tests : MessagingResourceTest, VersementResourceTest
- Service tests : MessagingServiceTest, VersementServiceTest
This commit is contained in:
dahoud
2026-04-15 20:24:33 +00:00
parent 2f7bb545d0
commit 4e1a6d4007
13 changed files with 2898 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.messagerie.TypePolitiqueCommunication;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("ContactPolicy")
class ContactPolicyTest {
private static Organisation newOrg() {
Organisation org = new Organisation();
org.setId(UUID.randomUUID());
org.setNom("Org Test");
return org;
}
@Test
@DisplayName("getters/setters — tous les champs")
void gettersSetters() {
ContactPolicy policy = new ContactPolicy();
Organisation org = newOrg();
policy.setOrganisation(org);
policy.setTypePolitique(TypePolitiqueCommunication.BUREAU_SEULEMENT);
policy.setAutoriserMembreVersMembre(false);
policy.setAutoriserMembreVersRole(true);
policy.setAutoriserNotesVocales(false);
assertThat(policy.getOrganisation()).isEqualTo(org);
assertThat(policy.getTypePolitique()).isEqualTo(TypePolitiqueCommunication.BUREAU_SEULEMENT);
assertThat(policy.getAutoriserMembreVersMembre()).isFalse();
assertThat(policy.getAutoriserMembreVersRole()).isTrue();
assertThat(policy.getAutoriserNotesVocales()).isFalse();
}
@Test
@DisplayName("onCreate — valeurs par défaut : OUVERT, tout à true")
void onCreate_defaults() {
ContactPolicy policy = new ContactPolicy();
policy.setOrganisation(newOrg());
policy.onCreate();
assertThat(policy.getTypePolitique()).isEqualTo(TypePolitiqueCommunication.OUVERT);
assertThat(policy.getAutoriserMembreVersMembre()).isTrue();
assertThat(policy.getAutoriserMembreVersRole()).isTrue();
assertThat(policy.getAutoriserNotesVocales()).isTrue();
assertThat(policy.getDateCreation()).isNotNull();
assertThat(policy.getActif()).isTrue();
}
@Test
@DisplayName("onCreate — ne remplace pas les valeurs existantes")
void onCreate_preservesExisting() {
ContactPolicy policy = new ContactPolicy();
policy.setOrganisation(newOrg());
policy.setTypePolitique(TypePolitiqueCommunication.GROUPES_INTERNES);
policy.setAutoriserNotesVocales(false);
policy.onCreate();
assertThat(policy.getTypePolitique()).isEqualTo(TypePolitiqueCommunication.GROUPES_INTERNES);
assertThat(policy.getAutoriserNotesVocales()).isFalse();
}
@Test
@DisplayName("builder — tous les champs")
void builder_allFields() {
Organisation org = newOrg();
ContactPolicy policy = ContactPolicy.builder()
.organisation(org)
.typePolitique(TypePolitiqueCommunication.OUVERT)
.autoriserMembreVersMembre(true)
.autoriserMembreVersRole(true)
.autoriserNotesVocales(true)
.build();
assertThat(policy.getOrganisation()).isEqualTo(org);
assertThat(policy.getTypePolitique()).isEqualTo(TypePolitiqueCommunication.OUVERT);
assertThat(policy.getAutoriserNotesVocales()).isTrue();
}
@Test
@DisplayName("equals et hashCode")
void equalsHashCode() {
UUID id = UUID.randomUUID();
Organisation org = newOrg();
ContactPolicy a = ContactPolicy.builder().organisation(org)
.typePolitique(TypePolitiqueCommunication.OUVERT).build();
a.setId(id);
ContactPolicy b = ContactPolicy.builder().organisation(org)
.typePolitique(TypePolitiqueCommunication.OUVERT).build();
b.setId(id);
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
@DisplayName("toString non null")
void toString_nonNull() {
ContactPolicy policy = ContactPolicy.builder().organisation(newOrg()).build();
assertThat(policy.toString()).isNotNull().isNotEmpty();
}
}

View File

@@ -0,0 +1,128 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.messagerie.StatutConversation;
import dev.lions.unionflow.server.api.enums.messagerie.TypeConversation;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("ConversationParticipant")
class ConversationParticipantTest {
private static Membre newMembre() {
Membre m = new Membre();
m.setId(UUID.randomUUID());
m.setNumeroMembre("M1");
m.setPrenom("Alpha");
m.setNom("Diallo");
m.setEmail("alpha@test.com");
m.setDateNaissance(LocalDate.now());
return m;
}
private static Conversation newConversation() {
Organisation org = new Organisation();
org.setId(UUID.randomUUID());
org.setNom("Org Test");
Conversation c = new Conversation();
c.setId(UUID.randomUUID());
c.setOrganisation(org);
c.setTypeConversation(TypeConversation.DIRECTE);
c.setStatut(StatutConversation.ACTIVE);
c.setNombreMessages(0);
return c;
}
@Test
@DisplayName("getters/setters")
void gettersSetters() {
ConversationParticipant p = new ConversationParticipant();
Conversation conv = newConversation();
Membre membre = newMembre();
p.setConversation(conv);
p.setMembre(membre);
p.setRoleDansConversation("INITIATEUR");
p.setNotifier(true);
assertThat(p.getConversation()).isEqualTo(conv);
assertThat(p.getMembre()).isEqualTo(membre);
assertThat(p.getRoleDansConversation()).isEqualTo("INITIATEUR");
assertThat(p.getNotifier()).isTrue();
}
@Test
@DisplayName("estInitiateur — INITIATEUR → true")
void estInitiateur_true() {
ConversationParticipant p = buildMinimal("INITIATEUR");
assertThat(p.estInitiateur()).isTrue();
}
@Test
@DisplayName("estInitiateur — PARTICIPANT → false")
void estInitiateur_false() {
ConversationParticipant p = buildMinimal("PARTICIPANT");
assertThat(p.estInitiateur()).isFalse();
}
@Test
@DisplayName("marquerLu — luJusqua mis à jour")
void marquerLu() {
ConversationParticipant p = buildMinimal("PARTICIPANT");
p.setLuJusqua(null);
p.marquerLu();
assertThat(p.getLuJusqua()).isNotNull();
}
@Test
@DisplayName("marquerLu — luJusqua remplace l'ancien")
void marquerLu_replaceOld() {
ConversationParticipant p = buildMinimal("PARTICIPANT");
LocalDateTime old = LocalDateTime.now().minusDays(1);
p.setLuJusqua(old);
p.marquerLu();
assertThat(p.getLuJusqua()).isAfter(old);
}
@Test
@DisplayName("builder — valeurs par défaut")
void builder_defaults() {
ConversationParticipant p = ConversationParticipant.builder()
.conversation(newConversation())
.membre(newMembre())
.build();
assertThat(p.getRoleDansConversation()).isEqualTo("PARTICIPANT");
assertThat(p.getNotifier()).isTrue();
}
@Test
@DisplayName("equals et hashCode")
void equalsHashCode() {
UUID id = UUID.randomUUID();
ConversationParticipant a = buildMinimal("PARTICIPANT");
a.setId(id);
ConversationParticipant b = buildMinimal("PARTICIPANT");
b.setId(id);
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
@DisplayName("toString non null")
void toString_nonNull() {
assertThat(buildMinimal("PARTICIPANT").toString()).isNotNull().isNotEmpty();
}
private ConversationParticipant buildMinimal(String role) {
ConversationParticipant p = new ConversationParticipant();
p.setConversation(newConversation());
p.setMembre(newMembre());
p.setRoleDansConversation(role);
p.setNotifier(true);
return p;
}
}

View File

@@ -0,0 +1,94 @@
package dev.lions.unionflow.server.entity;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("MemberBlock")
class MemberBlockTest {
private static Membre newMembre(String email) {
Membre m = new Membre();
m.setId(UUID.randomUUID());
m.setNumeroMembre("M-" + UUID.randomUUID().toString().substring(0, 4));
m.setPrenom("Test");
m.setNom("User");
m.setEmail(email);
m.setDateNaissance(LocalDate.now());
return m;
}
private static Organisation newOrg() {
Organisation org = new Organisation();
org.setId(UUID.randomUUID());
org.setNom("Org Test");
return org;
}
@Test
@DisplayName("getters/setters")
void gettersSetters() {
Membre bloqueur = newMembre("bloqueur@test.com");
Membre bloque = newMembre("bloque@test.com");
Organisation org = newOrg();
MemberBlock block = new MemberBlock();
block.setBloqueur(bloqueur);
block.setBloque(bloque);
block.setOrganisation(org);
assertThat(block.getBloqueur()).isEqualTo(bloqueur);
assertThat(block.getBloque()).isEqualTo(bloque);
assertThat(block.getOrganisation()).isEqualTo(org);
}
@Test
@DisplayName("builder — tous les champs")
void builder_allFields() {
Membre bloqueur = newMembre("a@test.com");
Membre bloque = newMembre("b@test.com");
Organisation org = newOrg();
MemberBlock block = MemberBlock.builder()
.bloqueur(bloqueur)
.bloque(bloque)
.organisation(org)
.build();
assertThat(block.getBloqueur()).isEqualTo(bloqueur);
assertThat(block.getBloque()).isEqualTo(bloque);
assertThat(block.getOrganisation()).isEqualTo(org);
}
@Test
@DisplayName("equals et hashCode")
void equalsHashCode() {
UUID id = UUID.randomUUID();
Membre bloqueur = newMembre("a@test.com");
Membre bloque = newMembre("b@test.com");
Organisation org = newOrg();
MemberBlock a = MemberBlock.builder().bloqueur(bloqueur).bloque(bloque).organisation(org).build();
a.setId(id);
MemberBlock b = MemberBlock.builder().bloqueur(bloqueur).bloque(bloque).organisation(org).build();
b.setId(id);
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
@DisplayName("toString non null")
void toString_nonNull() {
MemberBlock block = MemberBlock.builder()
.bloqueur(newMembre("a@test.com"))
.bloque(newMembre("b@test.com"))
.organisation(newOrg())
.build();
assertThat(block.toString()).isNotNull().isNotEmpty();
}
}

View File

@@ -0,0 +1,108 @@
package dev.lions.unionflow.server.entity;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("VersementObjet")
class VersementObjetTest {
private static Versement newVersement() {
Membre m = new Membre();
m.setId(UUID.randomUUID());
m.setNumeroMembre("M1");
m.setPrenom("A");
m.setNom("B");
m.setEmail("a@test.com");
m.setDateNaissance(java.time.LocalDate.now());
Versement v = new Versement();
v.setId(UUID.randomUUID());
v.setNumeroReference("VRS-2026-001");
v.setMontant(BigDecimal.TEN);
v.setCodeDevise("XOF");
v.setMethodePaiement("WAVE");
v.setMembre(m);
return v;
}
@Test
@DisplayName("getters/setters")
void gettersSetters() {
VersementObjet vo = new VersementObjet();
vo.setVersement(newVersement());
vo.setTypeObjetCible("COTISATION");
vo.setObjetCibleId(UUID.randomUUID());
vo.setMontantApplique(new BigDecimal("5000.00"));
vo.setDateApplication(LocalDateTime.now());
vo.setCommentaire("Cotisation janvier");
assertThat(vo.getTypeObjetCible()).isEqualTo("COTISATION");
assertThat(vo.getMontantApplique()).isEqualByComparingTo("5000.00");
assertThat(vo.getCommentaire()).isEqualTo("Cotisation janvier");
}
@Test
@DisplayName("equals et hashCode")
void equalsHashCode() {
UUID id = UUID.randomUUID();
UUID objId = UUID.randomUUID();
Versement v = newVersement();
VersementObjet a = new VersementObjet();
a.setId(id);
a.setVersement(v);
a.setTypeObjetCible("COTISATION");
a.setObjetCibleId(objId);
a.setMontantApplique(BigDecimal.ONE);
VersementObjet b = new VersementObjet();
b.setId(id);
b.setVersement(v);
b.setTypeObjetCible("COTISATION");
b.setObjetCibleId(objId);
b.setMontantApplique(BigDecimal.ONE);
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
@DisplayName("toString non null")
void toString_nonNull() {
VersementObjet vo = new VersementObjet();
vo.setVersement(newVersement());
vo.setTypeObjetCible("COTISATION");
vo.setObjetCibleId(UUID.randomUUID());
vo.setMontantApplique(BigDecimal.ONE);
assertThat(vo.toString()).isNotNull().isNotEmpty();
}
@Test
@DisplayName("onCreate initialise dateApplication si null")
void onCreate_setsDateApplicationWhenNull() {
VersementObjet vo = new VersementObjet();
vo.setVersement(newVersement());
vo.setTypeObjetCible("COTISATION");
vo.setObjetCibleId(UUID.randomUUID());
vo.setMontantApplique(BigDecimal.ONE);
vo.onCreate();
assertThat(vo.getDateApplication()).isNotNull();
assertThat(vo.getActif()).isTrue();
}
@Test
@DisplayName("onCreate ne remplace pas une dateApplication existante")
void onCreate_doesNotOverrideDateApplication() {
LocalDateTime fixed = LocalDateTime.of(2026, 1, 1, 0, 0);
VersementObjet vo = new VersementObjet();
vo.setDateApplication(fixed);
vo.onCreate();
assertThat(vo.getDateApplication()).isEqualTo(fixed);
}
}

View File

@@ -0,0 +1,160 @@
package dev.lions.unionflow.server.entity;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Versement")
class VersementTest {
private static Membre newMembre() {
Membre m = new Membre();
m.setId(UUID.randomUUID());
m.setNumeroMembre("M1");
m.setPrenom("A");
m.setNom("B");
m.setEmail("a@test.com");
m.setDateNaissance(java.time.LocalDate.now());
return m;
}
@Test
@DisplayName("getters/setters")
void gettersSetters() {
Versement v = new Versement();
v.setNumeroReference("VRS-2026-001");
v.setMontant(new BigDecimal("10000.00"));
v.setCodeDevise("XOF");
v.setMethodePaiement("WAVE");
v.setStatutPaiement("CONFIRME");
v.setDatePaiement(LocalDateTime.now());
v.setNumeroTelephone("771234567");
v.setMembre(newMembre());
assertThat(v.getNumeroReference()).isEqualTo("VRS-2026-001");
assertThat(v.getMontant()).isEqualByComparingTo("10000.00");
assertThat(v.getCodeDevise()).isEqualTo("XOF");
assertThat(v.getStatutPaiement()).isEqualTo("CONFIRME");
assertThat(v.getNumeroTelephone()).isEqualTo("771234567");
}
@Test
@DisplayName("genererNumeroReference commence par VRS-")
void genererNumeroReference_startsWithVRS() {
String ref = Versement.genererNumeroReference();
assertThat(ref).startsWith("VRS-").isNotNull();
}
@Test
@DisplayName("genererNumeroReference retourne des références uniques")
void genererNumeroReference_unique() {
String ref1 = Versement.genererNumeroReference();
String ref2 = Versement.genererNumeroReference();
assertThat(ref1).isNotEqualTo(ref2);
}
@Test
@DisplayName("isConfirme — statut CONFIRME")
void isConfirme_statutCONFIRME() {
Versement v = buildMinimal();
v.setStatutPaiement("CONFIRME");
assertThat(v.isConfirme()).isTrue();
assertThat(v.peutEtreModifie()).isFalse();
}
@Test
@DisplayName("isConfirme — statut VALIDE (compatibilité)")
void isConfirme_statutVALIDE() {
Versement v = buildMinimal();
v.setStatutPaiement("VALIDE");
assertThat(v.isConfirme()).isTrue();
assertThat(v.peutEtreModifie()).isFalse();
}
@Test
@DisplayName("isConfirme — statut EN_ATTENTE")
void isConfirme_statutEnAttente() {
Versement v = buildMinimal();
v.setStatutPaiement("EN_ATTENTE");
assertThat(v.isConfirme()).isFalse();
assertThat(v.peutEtreModifie()).isTrue();
}
@Test
@DisplayName("peutEtreModifie — statut ANNULE")
void peutEtreModifie_false_whenANNULE() {
Versement v = buildMinimal();
v.setStatutPaiement("ANNULE");
assertThat(v.peutEtreModifie()).isFalse();
}
@Test
@DisplayName("peutEtreModifie — statut EN_ATTENTE_VALIDATION")
void peutEtreModifie_true_whenEnAttenteValidation() {
Versement v = buildMinimal();
v.setStatutPaiement("EN_ATTENTE_VALIDATION");
assertThat(v.peutEtreModifie()).isTrue();
}
@Test
@DisplayName("onCreate initialise reference, statut et datePaiement si null")
void onCreate_initDefaults() {
Versement v = new Versement();
v.setMontant(BigDecimal.TEN);
v.setCodeDevise("XOF");
v.setMethodePaiement("WAVE");
v.setMembre(newMembre());
v.onCreate();
assertThat(v.getNumeroReference()).startsWith("VRS-");
assertThat(v.getStatutPaiement()).isEqualTo("EN_ATTENTE");
assertThat(v.getDatePaiement()).isNotNull();
}
@Test
@DisplayName("onCreate conserve la référence existante")
void onCreate_preservesExistingReference() {
Versement v = buildMinimal();
v.setNumeroReference("VRS-CUSTOM-001");
v.onCreate();
assertThat(v.getNumeroReference()).isEqualTo("VRS-CUSTOM-001");
}
@Test
@DisplayName("equals et hashCode")
void equalsHashCode() {
UUID id = UUID.randomUUID();
Membre m = newMembre();
Versement a = buildMinimal();
a.setId(id);
a.setMembre(m);
Versement b = buildMinimal();
b.setId(id);
b.setMembre(m);
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
@DisplayName("toString non null")
void toString_nonNull() {
assertThat(buildMinimal().toString()).isNotNull().isNotEmpty();
}
private Versement buildMinimal() {
Versement v = new Versement();
v.setNumeroReference("VRS-2026-TEST");
v.setMontant(BigDecimal.ONE);
v.setCodeDevise("XOF");
v.setMethodePaiement("WAVE");
v.setStatutPaiement("EN_ATTENTE");
v.setMembre(newMembre());
return v;
}
}

View File

@@ -0,0 +1,43 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.ContactPolicy;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@QuarkusTest
@DisplayName("ContactPolicyRepository")
class ContactPolicyRepositoryTest {
@Inject
ContactPolicyRepository contactPolicyRepository;
@Test
@TestTransaction
@DisplayName("findByOrganisationId retourne empty pour organisation inexistante")
void findByOrganisationId_inexistant_returnsEmpty() {
Optional<ContactPolicy> opt = contactPolicyRepository.findByOrganisationId(UUID.randomUUID());
assertThat(opt).isEmpty();
}
@Test
@TestTransaction
@DisplayName("listAll retourne liste non nulle")
void listAll_returnsNonNull() {
assertThat(contactPolicyRepository.listAll()).isNotNull();
}
@Test
@TestTransaction
@DisplayName("count retourne >= 0")
void count_returnsNonNegative() {
assertThat(contactPolicyRepository.count()).isGreaterThanOrEqualTo(0);
}
}

View File

@@ -0,0 +1,63 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.ConversationParticipant;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@QuarkusTest
@DisplayName("ConversationParticipantRepository")
class ConversationParticipantRepositoryTest {
@Inject
ConversationParticipantRepository conversationParticipantRepository;
@Test
@TestTransaction
@DisplayName("findParticipant retourne empty pour conversation inexistante")
void findParticipant_returnsEmpty() {
Optional<ConversationParticipant> opt =
conversationParticipantRepository.findParticipant(UUID.randomUUID(), UUID.randomUUID());
assertThat(opt).isEmpty();
}
@Test
@TestTransaction
@DisplayName("findByConversation retourne liste vide pour conversation inexistante")
void findByConversation_returnsEmpty() {
List<ConversationParticipant> list =
conversationParticipantRepository.findByConversation(UUID.randomUUID());
assertThat(list).isNotNull().isEmpty();
}
@Test
@TestTransaction
@DisplayName("estParticipant retourne false pour conversation inexistante")
void estParticipant_returnsFalse() {
boolean result = conversationParticipantRepository.estParticipant(
UUID.randomUUID(), UUID.randomUUID());
assertThat(result).isFalse();
}
@Test
@TestTransaction
@DisplayName("count retourne >= 0")
void count_returnsNonNegative() {
assertThat(conversationParticipantRepository.count()).isGreaterThanOrEqualTo(0);
}
@Test
@TestTransaction
@DisplayName("listAll retourne liste non nulle")
void listAll_returnsNonNull() {
assertThat(conversationParticipantRepository.listAll()).isNotNull();
}
}

View File

@@ -0,0 +1,64 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.entity.MemberBlock;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@QuarkusTest
@DisplayName("MemberBlockRepository")
class MemberBlockRepositoryTest {
@Inject
MemberBlockRepository memberBlockRepository;
@Test
@TestTransaction
@DisplayName("estBloque retourne false si aucun blocage")
void estBloque_returns_false() {
boolean result = memberBlockRepository.estBloque(
UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID());
assertThat(result).isFalse();
}
@Test
@TestTransaction
@DisplayName("findBlocage retourne empty si aucun blocage")
void findBlocage_returnsEmpty() {
Optional<MemberBlock> opt = memberBlockRepository.findBlocage(
UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID());
assertThat(opt).isEmpty();
}
@Test
@TestTransaction
@DisplayName("findByBloqueur retourne liste vide pour membre inexistant")
void findByBloqueur_returnsEmpty() {
List<MemberBlock> list = memberBlockRepository.findByBloqueur(UUID.randomUUID());
assertThat(list).isNotNull().isEmpty();
}
@Test
@TestTransaction
@DisplayName("findByBloqueurEtOrganisation retourne liste vide")
void findByBloqueurEtOrganisation_returnsEmpty() {
List<MemberBlock> list = memberBlockRepository.findByBloqueurEtOrganisation(
UUID.randomUUID(), UUID.randomUUID());
assertThat(list).isNotNull().isEmpty();
}
@Test
@TestTransaction
@DisplayName("count retourne >= 0")
void count_returnsNonNegative() {
assertThat(memberBlockRepository.count()).isGreaterThanOrEqualTo(0);
}
}

View File

@@ -0,0 +1,109 @@
package dev.lions.unionflow.server.repository;
import dev.lions.unionflow.server.api.enums.paiement.MethodePaiement;
import dev.lions.unionflow.server.api.enums.paiement.StatutPaiement;
import dev.lions.unionflow.server.entity.Versement;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@QuarkusTest
@DisplayName("VersementRepository")
class VersementRepositoryTest {
@Inject
VersementRepository versementRepository;
@Test
@TestTransaction
@DisplayName("findById retourne null pour UUID inexistant")
void findById_inexistant_returnsNull() {
assertThat(versementRepository.findById(UUID.randomUUID())).isNull();
}
@Test
@TestTransaction
@DisplayName("findVersementById retourne empty pour UUID inexistant")
void findVersementById_inexistant_returnsEmpty() {
Optional<Versement> opt = versementRepository.findVersementById(UUID.randomUUID());
assertThat(opt).isEmpty();
}
@Test
@TestTransaction
@DisplayName("findByNumeroReference retourne empty pour référence inexistante")
void findByNumeroReference_inexistant_returnsEmpty() {
Optional<Versement> opt = versementRepository.findByNumeroReference("VRS-" + UUID.randomUUID());
assertThat(opt).isEmpty();
}
@Test
@TestTransaction
@DisplayName("listAll retourne une liste")
void listAll_returnsList() {
List<Versement> list = versementRepository.listAll();
assertThat(list).isNotNull();
}
@Test
@TestTransaction
@DisplayName("count retourne un nombre >= 0")
void count_returnsNonNegative() {
assertThat(versementRepository.count()).isGreaterThanOrEqualTo(0);
}
@Test
@TestTransaction
@DisplayName("findByMembreId retourne liste vide pour membre inexistant")
void findByMembreId_inconnu_returnsEmpty() {
List<Versement> list = versementRepository.findByMembreId(UUID.randomUUID());
assertThat(list).isNotNull().isEmpty();
}
@Test
@TestTransaction
@DisplayName("findByStatut retourne liste non nulle")
void findByStatut_returnsNonNull() {
List<Versement> list = versementRepository.findByStatut(StatutPaiement.EN_ATTENTE);
assertThat(list).isNotNull();
}
@Test
@TestTransaction
@DisplayName("findByMethode retourne liste non nulle")
void findByMethode_returnsNonNull() {
List<Versement> list = versementRepository.findByMethode(MethodePaiement.WAVE_MOBILE_MONEY);
assertThat(list).isNotNull();
}
@Test
@TestTransaction
@DisplayName("findConfirmesParPeriode retourne liste non nulle")
void findConfirmesParPeriode_returnsNonNull() {
LocalDateTime debut = LocalDateTime.now().minusDays(30);
LocalDateTime fin = LocalDateTime.now();
List<Versement> list = versementRepository.findConfirmesParPeriode(debut, fin);
assertThat(list).isNotNull();
}
@Test
@TestTransaction
@DisplayName("calculerMontantTotalConfirmes retourne ZERO si aucun versement")
void calculerMontantTotalConfirmes_returnsZeroWhenEmpty() {
LocalDateTime debut = LocalDateTime.now().minusSeconds(1);
LocalDateTime fin = LocalDateTime.now().plusSeconds(1);
BigDecimal total = versementRepository.calculerMontantTotalConfirmes(debut, fin);
// Peut être > 0 selon les données de test, mais ne doit pas être null
assertThat(total).isNotNull().isGreaterThanOrEqualTo(BigDecimal.ZERO);
}
}

View File

@@ -0,0 +1,439 @@
package dev.lions.unionflow.server.resource;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import dev.lions.unionflow.server.api.dto.messagerie.response.ContactPolicyResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.ConversationResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.ConversationSummaryResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.MessageResponse;
import dev.lions.unionflow.server.service.MessagingService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
/**
* Tests d'intégration REST pour {@link MessagingResource}.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@QuarkusTest
@DisplayName("MessagingResource")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MessagingResourceTest {
private static final String BASE = "/api/messagerie";
private static final String CONV_ID = "00000000-0000-0000-0000-000000000001";
private static final String MSG_ID = "00000000-0000-0000-0000-000000000002";
private static final String MEMBRE_ID = "00000000-0000-0000-0000-000000000003";
private static final String ORG_ID = "00000000-0000-0000-0000-000000000004";
@InjectMock
MessagingService messagingService;
// ── POST /conversations/directe ───────────────────────────────────────────
@Test
@Order(1)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("demarrerConversationDirecte — requête valide → 201")
void demarrerConversationDirecte_valid_returns201() {
ConversationResponse conv = ConversationResponse.builder()
.id(UUID.fromString(CONV_ID))
.typeConversation("DIRECTE")
.statut("ACTIVE")
.build();
when(messagingService.demarrerConversationDirecte(any())).thenReturn(conv);
given()
.contentType(ContentType.JSON)
.body("{\"destinataireId\":\"" + MEMBRE_ID + "\",\"organisationId\":\"" + ORG_ID + "\"}")
.when().post(BASE + "/conversations/directe")
.then().statusCode(201)
.body("typeConversation", equalTo("DIRECTE"))
.body("statut", equalTo("ACTIVE"));
}
@Test
@Order(2)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("demarrerConversationDirecte — destinataireId manquant → 400")
void demarrerConversationDirecte_missingDestinataire_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"organisationId\":\"" + ORG_ID + "\"}")
.when().post(BASE + "/conversations/directe")
.then().statusCode(400);
}
// ── POST /conversations/role ──────────────────────────────────────────────
@Test
@Order(3)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("demarrerConversationRole — requête valide → 201")
void demarrerConversationRole_valid_returns201() {
ConversationResponse conv = ConversationResponse.builder()
.id(UUID.fromString(CONV_ID))
.typeConversation("ROLE_CANAL")
.roleCible("TRESORIER")
.build();
when(messagingService.demarrerConversationRole(any())).thenReturn(conv);
given()
.contentType(ContentType.JSON)
.body("{\"organisationId\":\"" + ORG_ID + "\","
+ "\"roleCible\":\"TRESORIER\","
+ "\"contenuInitial\":\"Bonjour\"}")
.when().post(BASE + "/conversations/role")
.then().statusCode(201)
.body("typeConversation", equalTo("ROLE_CANAL"))
.body("roleCible", equalTo("TRESORIER"));
}
@Test
@Order(4)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("demarrerConversationRole — rôle invalide → 400")
void demarrerConversationRole_invalidRole_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"organisationId\":\"" + ORG_ID + "\","
+ "\"roleCible\":\"DIRECTEUR\","
+ "\"contenuInitial\":\"Bonjour\"}")
.when().post(BASE + "/conversations/role")
.then().statusCode(400);
}
// ── GET /conversations ────────────────────────────────────────────────────
@Test
@Order(5)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getMesConversations — liste vide → 200")
void getMesConversations_empty_returns200() {
when(messagingService.getMesConversations()).thenReturn(Collections.emptyList());
given()
.when().get(BASE + "/conversations")
.then().statusCode(200);
}
@Test
@Order(6)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getMesConversations — liste non vide → 200")
void getMesConversations_nonEmpty_returns200() {
ConversationSummaryResponse summary = ConversationSummaryResponse.builder()
.id(UUID.fromString(CONV_ID))
.typeConversation("DIRECTE")
.titre("Alice Dupont")
.build();
when(messagingService.getMesConversations()).thenReturn(List.of(summary));
given()
.when().get(BASE + "/conversations")
.then().statusCode(200)
.body("[0].titre", equalTo("Alice Dupont"));
}
// ── GET /conversations/{id} ───────────────────────────────────────────────
@Test
@Order(7)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getConversation — trouvée → 200")
void getConversation_found_returns200() {
ConversationResponse conv = ConversationResponse.builder()
.id(UUID.fromString(CONV_ID))
.typeConversation("DIRECTE")
.titre("Bob Martin")
.build();
when(messagingService.getConversation(any(UUID.class))).thenReturn(conv);
given()
.when().get(BASE + "/conversations/" + CONV_ID)
.then().statusCode(200)
.body("titre", equalTo("Bob Martin"));
}
@Test
@Order(8)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getConversation — non trouvée → 404")
void getConversation_notFound_returns404() {
when(messagingService.getConversation(any(UUID.class)))
.thenThrow(new NotFoundException("conversation non trouvée"));
given()
.when().get(BASE + "/conversations/" + CONV_ID)
.then().statusCode(404);
}
// ── DELETE /conversations/{id} ────────────────────────────────────────────
@Test
@Order(9)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("archiverConversation → 200")
void archiverConversation_returns200() {
ConversationResponse conv = ConversationResponse.builder()
.id(UUID.fromString(CONV_ID))
.statut("ARCHIVEE")
.build();
when(messagingService.archiverConversation(any(UUID.class))).thenReturn(conv);
given()
.when().delete(BASE + "/conversations/" + CONV_ID)
.then().statusCode(200)
.body("statut", equalTo("ARCHIVEE"));
}
// ── POST /conversations/{id}/messages ─────────────────────────────────────
@Test
@Order(10)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("envoyerMessage — message texte → 201")
void envoyerMessage_texte_returns201() {
MessageResponse msg = MessageResponse.builder()
.id(UUID.fromString(MSG_ID))
.typeMessage("TEXTE")
.contenu("Bonjour !")
.build();
when(messagingService.envoyerMessage(any(UUID.class), any())).thenReturn(msg);
given()
.contentType(ContentType.JSON)
.body("{\"typeMessage\":\"TEXTE\",\"contenu\":\"Bonjour !\"}")
.when().post(BASE + "/conversations/" + CONV_ID + "/messages")
.then().statusCode(201)
.body("typeMessage", equalTo("TEXTE"))
.body("contenu", equalTo("Bonjour !"));
}
@Test
@Order(11)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("envoyerMessage — type invalide → 400")
void envoyerMessage_invalidType_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"typeMessage\":\"VIDEO\",\"contenu\":\"test\"}")
.when().post(BASE + "/conversations/" + CONV_ID + "/messages")
.then().statusCode(400);
}
// ── GET /conversations/{id}/messages ──────────────────────────────────────
@Test
@Order(12)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getMessages — page 0 → 200")
void getMessages_page0_returns200() {
when(messagingService.getMessages(any(UUID.class), anyInt()))
.thenReturn(Collections.emptyList());
given()
.when().get(BASE + "/conversations/" + CONV_ID + "/messages")
.then().statusCode(200);
}
@Test
@Order(13)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getMessages — page custom → 200")
void getMessages_customPage_returns200() {
when(messagingService.getMessages(any(UUID.class), anyInt()))
.thenReturn(Collections.emptyList());
given()
.queryParam("page", 2)
.when().get(BASE + "/conversations/" + CONV_ID + "/messages")
.then().statusCode(200);
}
// ── PUT /conversations/{id}/lire ──────────────────────────────────────────
@Test
@Order(14)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("marquerLu → 204")
void marquerLu_returns204() {
doNothing().when(messagingService).marquerConversationLue(any(UUID.class));
given()
.when().put(BASE + "/conversations/" + CONV_ID + "/lire")
.then().statusCode(204);
}
// ── DELETE /conversations/{cId}/messages/{mId} ────────────────────────────
@Test
@Order(15)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("supprimerMessage → 204")
void supprimerMessage_returns204() {
doNothing().when(messagingService).supprimerMessage(any(UUID.class), any(UUID.class));
given()
.when().delete(BASE + "/conversations/" + CONV_ID + "/messages/" + MSG_ID)
.then().statusCode(204);
}
@Test
@Order(16)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("supprimerMessage — non trouvé → 404")
void supprimerMessage_notFound_returns404() {
org.mockito.Mockito.doThrow(new NotFoundException("message introuvable"))
.when(messagingService).supprimerMessage(any(UUID.class), any(UUID.class));
given()
.when().delete(BASE + "/conversations/" + CONV_ID + "/messages/" + MSG_ID)
.then().statusCode(404);
}
// ── POST /blocages ────────────────────────────────────────────────────────
@Test
@Order(17)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("bloquerMembre — requête valide → 204")
void bloquerMembre_valid_returns204() {
doNothing().when(messagingService).bloquerMembre(any());
given()
.contentType(ContentType.JSON)
.body("{\"membreABloquerId\":\"" + MEMBRE_ID + "\","
+ "\"organisationId\":\"" + ORG_ID + "\"}")
.when().post(BASE + "/blocages")
.then().statusCode(204);
}
@Test
@Order(18)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("bloquerMembre — membreABloquerId manquant → 400")
void bloquerMembre_missingId_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"organisationId\":\"" + ORG_ID + "\"}")
.when().post(BASE + "/blocages")
.then().statusCode(400);
}
// ── DELETE /blocages/{membreId} ───────────────────────────────────────────
@Test
@Order(19)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("debloquerMembre → 204")
void debloquerMembre_returns204() {
doNothing().when(messagingService).debloquerMembre(any(UUID.class), any());
given()
.queryParam("organisationId", ORG_ID)
.when().delete(BASE + "/blocages/" + MEMBRE_ID)
.then().statusCode(204);
}
// ── GET /blocages ─────────────────────────────────────────────────────────
@Test
@Order(20)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getMesBlocages → 200")
void getMesBlocages_returns200() {
when(messagingService.getMesBlocages()).thenReturn(Collections.emptyList());
given()
.when().get(BASE + "/blocages")
.then().statusCode(200);
}
// ── GET /politique/{organisationId} ───────────────────────────────────────
@Test
@Order(21)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("getPolitique → 200")
void getPolitique_returns200() {
ContactPolicyResponse policy = ContactPolicyResponse.builder()
.id(UUID.randomUUID())
.organisationId(UUID.fromString(ORG_ID))
.typePolitique("OUVERT")
.autoriserMembreVersMembre(true)
.autoriserMembreVersRole(true)
.autoriserNotesVocales(true)
.build();
when(messagingService.getPolitique(any(UUID.class))).thenReturn(policy);
given()
.when().get(BASE + "/politique/" + ORG_ID)
.then().statusCode(200)
.body("typePolitique", equalTo("OUVERT"))
.body("autoriserNotesVocales", equalTo(true));
}
// ── PUT /politique/{organisationId} ───────────────────────────────────────
@Test
@Order(22)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
@DisplayName("mettreAJourPolitique — ADMIN → 200")
void mettreAJourPolitique_admin_returns200() {
ContactPolicyResponse updated = ContactPolicyResponse.builder()
.typePolitique("BUREAU_SEULEMENT")
.build();
when(messagingService.mettreAJourPolitique(any(UUID.class), any())).thenReturn(updated);
given()
.contentType(ContentType.JSON)
.body("{\"typePolitique\":\"BUREAU_SEULEMENT\"}")
.when().put(BASE + "/politique/" + ORG_ID)
.then().statusCode(200)
.body("typePolitique", equalTo("BUREAU_SEULEMENT"));
}
@Test
@Order(23)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
@DisplayName("mettreAJourPolitique — MEMBRE → 403")
void mettreAJourPolitique_membre_returns403() {
given()
.contentType(ContentType.JSON)
.body("{\"typePolitique\":\"BUREAU_SEULEMENT\"}")
.when().put(BASE + "/politique/" + ORG_ID)
.then().statusCode(403);
}
@Test
@Order(24)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
@DisplayName("mettreAJourPolitique — politique invalide → 400")
void mettreAJourPolitique_invalidPolicy_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"typePolitique\":\"LIBRE\"}")
.when().put(BASE + "/politique/" + ORG_ID)
.then().statusCode(400);
}
}

View File

@@ -0,0 +1,317 @@
package dev.lions.unionflow.server.resource;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import dev.lions.unionflow.server.api.dto.versement.response.VersementGatewayResponse;
import dev.lions.unionflow.server.api.dto.versement.response.VersementResponse;
import dev.lions.unionflow.server.api.dto.versement.response.VersementStatutResponse;
import dev.lions.unionflow.server.api.dto.versement.response.VersementSummaryResponse;
import dev.lions.unionflow.server.service.VersementService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
/**
* Tests d'intégration REST pour {@link VersementResource}.
*
* @author UnionFlow Team
* @version 4.0
* @since 2026-04-13
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class VersementResourceTest {
private static final String BASE = "/api/versements";
private static final String VERSEMENT_ID = "00000000-0000-0000-0000-000000000010";
private static final String MEMBRE_ID = "00000000-0000-0000-0000-000000000020";
private static final String INTENTION_ID = "00000000-0000-0000-0000-000000000030";
@InjectMock
VersementService versementService;
// ── GET /{id} ─────────────────────────────────────────────────────────────
@Test
@Order(1)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void trouverParId_found_returns200() {
VersementResponse response = VersementResponse.builder()
.numeroReference("VRS-2026-001")
.build();
when(versementService.trouverParId(any(UUID.class))).thenReturn(response);
given()
.when().get(BASE + "/" + VERSEMENT_ID)
.then().statusCode(200)
.body("numeroReference", equalTo("VRS-2026-001"));
}
@Test
@Order(2)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void trouverParId_notFound_returns404() {
when(versementService.trouverParId(any(UUID.class)))
.thenThrow(new NotFoundException("non trouvé"));
given()
.when().get(BASE + "/" + VERSEMENT_ID)
.then().statusCode(404);
}
// ── GET /reference/{ref} ──────────────────────────────────────────────────
@Test
@Order(3)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void trouverParReference_found_returns200() {
VersementResponse response = VersementResponse.builder()
.numeroReference("VRS-2026-001")
.build();
when(versementService.trouverParNumeroReference(anyString())).thenReturn(response);
given()
.when().get(BASE + "/reference/VRS-2026-001")
.then().statusCode(200);
}
// ── GET /membre/{id} ──────────────────────────────────────────────────────
@Test
@Order(4)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void listerParMembre_returns200() {
when(versementService.listerParMembre(any(UUID.class)))
.thenReturn(List.of(new VersementSummaryResponse()));
given()
.when().get(BASE + "/membre/" + MEMBRE_ID)
.then().statusCode(200);
}
// ── GET /mes-versements ────────────────────────────────────────────────────
@Test
@Order(5)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void getMesVersements_returns200() {
when(versementService.getMesVersements(anyInt()))
.thenReturn(Collections.emptyList());
given()
.when().get(BASE + "/mes-versements")
.then().statusCode(200);
}
@Test
@Order(6)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void getMesVersements_customLimit_returns200() {
when(versementService.getMesVersements(10))
.thenReturn(Collections.emptyList());
given()
.queryParam("limit", 10)
.when().get(BASE + "/mes-versements")
.then().statusCode(200);
}
// ── POST /{id}/valider ────────────────────────────────────────────────────
@Test
@Order(7)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void validerVersement_returns200() {
VersementResponse response = VersementResponse.builder()
.statutPaiement("CONFIRME")
.build();
when(versementService.validerVersement(any(UUID.class))).thenReturn(response);
given()
.contentType(ContentType.JSON)
.when().post(BASE + "/" + VERSEMENT_ID + "/valider")
.then().statusCode(200)
.body("statutPaiement", equalTo("CONFIRME"));
}
@Test
@Order(8)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void validerVersement_notFound_returns404() {
when(versementService.validerVersement(any(UUID.class)))
.thenThrow(new NotFoundException("non trouvé"));
given()
.contentType(ContentType.JSON)
.when().post(BASE + "/" + VERSEMENT_ID + "/valider")
.then().statusCode(404);
}
// ── POST /{id}/annuler ────────────────────────────────────────────────────
@Test
@Order(9)
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void annulerVersement_returns200() {
VersementResponse response = VersementResponse.builder()
.statutPaiement("ANNULE")
.build();
when(versementService.annulerVersement(any(UUID.class))).thenReturn(response);
given()
.contentType(ContentType.JSON)
.when().post(BASE + "/" + VERSEMENT_ID + "/annuler")
.then().statusCode(200);
}
// ── POST /initier-wave ────────────────────────────────────────────────────
@Test
@Order(10)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierWave_validRequest_returns201() {
VersementGatewayResponse gateway = VersementGatewayResponse.builder()
.versementId(UUID.fromString(VERSEMENT_ID))
.waveLaunchUrl("wave://checkout/abc123")
.waveCheckoutSessionId("cos-abc")
.clientReference(INTENTION_ID)
.montant(new BigDecimal("5000"))
.statut("EN_ATTENTE")
.build();
when(versementService.initierVersementWave(any())).thenReturn(gateway);
given()
.contentType(ContentType.JSON)
.body("{\"cotisationId\":\"" + UUID.randomUUID() + "\",\"numeroTelephone\":\"771234567\"}")
.when().post(BASE + "/initier-wave")
.then().statusCode(201)
.body("waveLaunchUrl", equalTo("wave://checkout/abc123"))
.body("waveCheckoutSessionId", equalTo("cos-abc"));
}
@Test
@Order(11)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierWave_missingCotisationId_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"numeroTelephone\":\"771234567\"}")
.when().post(BASE + "/initier-wave")
.then().statusCode(400);
}
// ── GET /statut/{intentionId} ─────────────────────────────────────────────
@Test
@Order(12)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void getStatutVersement_completee_returns200() {
VersementStatutResponse statut = VersementStatutResponse.builder()
.intentionId(UUID.fromString(INTENTION_ID))
.statut("COMPLETEE")
.confirme(true)
.message("Versement confirmé !")
.build();
when(versementService.verifierStatutVersement(any(UUID.class))).thenReturn(statut);
given()
.when().get(BASE + "/statut/" + INTENTION_ID)
.then().statusCode(200)
.body("confirme", equalTo(true))
.body("statut", equalTo("COMPLETEE"));
}
@Test
@Order(13)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void getStatutVersement_notFound_returns404() {
when(versementService.verifierStatutVersement(any(UUID.class)))
.thenThrow(new NotFoundException("non trouvé"));
given()
.when().get(BASE + "/statut/" + INTENTION_ID)
.then().statusCode(404);
}
// ── POST /declarer-manuel ─────────────────────────────────────────────────
@Test
@Order(14)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void declarerManuel_validRequest_returns201() {
VersementResponse response = VersementResponse.builder()
.statutPaiement("EN_ATTENTE_VALIDATION")
.methodePaiement("ESPECES")
.build();
when(versementService.declarerVersementManuel(any())).thenReturn(response);
given()
.contentType(ContentType.JSON)
.body("{\"cotisationId\":\"" + UUID.randomUUID()
+ "\",\"methodePaiement\":\"ESPECES\"}")
.when().post(BASE + "/declarer-manuel")
.then().statusCode(201)
.body("statutPaiement", equalTo("EN_ATTENTE_VALIDATION"));
}
@Test
@Order(15)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void declarerManuel_methodInvalide_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"cotisationId\":\"" + UUID.randomUUID()
+ "\",\"methodePaiement\":\"PAYPAL\"}")
.when().post(BASE + "/declarer-manuel")
.then().statusCode(400);
}
// ── POST /initier-depot-epargne ───────────────────────────────────────────
@Test
@Order(16)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierDepotEpargne_validRequest_returns201() {
VersementGatewayResponse gateway = VersementGatewayResponse.builder()
.waveLaunchUrl("wave://checkout/epargne")
.statut("EN_ATTENTE")
.montant(new BigDecimal("10000"))
.build();
when(versementService.initierDepotEpargneEnLigne(any())).thenReturn(gateway);
given()
.contentType(ContentType.JSON)
.body("{\"compteId\":\"" + UUID.randomUUID()
+ "\",\"montant\":10000,\"numeroTelephone\":\"771234567\"}")
.when().post(BASE + "/initier-depot-epargne")
.then().statusCode(201)
.body("waveLaunchUrl", notNullValue());
}
@Test
@Order(17)
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierDepotEpargne_montantManquant_returns400() {
given()
.contentType(ContentType.JSON)
.body("{\"compteId\":\"" + UUID.randomUUID() + "\"}")
.when().post(BASE + "/initier-depot-epargne")
.then().statusCode(400);
}
}

View File

@@ -0,0 +1,509 @@
package dev.lions.unionflow.server.service;
import dev.lions.unionflow.server.api.dto.messagerie.request.BloquerMembreRequest;
import dev.lions.unionflow.server.api.dto.messagerie.request.DemarrerConversationDirecteRequest;
import dev.lions.unionflow.server.api.dto.messagerie.request.DemarrerConversationRoleRequest;
import dev.lions.unionflow.server.api.dto.messagerie.request.EnvoyerMessageRequest;
import dev.lions.unionflow.server.api.dto.messagerie.request.MettreAJourPolitiqueRequest;
import dev.lions.unionflow.server.api.dto.messagerie.response.ContactPolicyResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.ConversationResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.ConversationSummaryResponse;
import dev.lions.unionflow.server.api.dto.messagerie.response.MessageResponse;
import dev.lions.unionflow.server.api.enums.messagerie.StatutConversation;
import dev.lions.unionflow.server.api.enums.messagerie.TypeContenu;
import dev.lions.unionflow.server.api.enums.messagerie.TypeConversation;
import dev.lions.unionflow.server.api.enums.messagerie.TypePolitiqueCommunication;
import dev.lions.unionflow.server.entity.ContactPolicy;
import dev.lions.unionflow.server.entity.Conversation;
import dev.lions.unionflow.server.entity.ConversationParticipant;
import dev.lions.unionflow.server.entity.MemberBlock;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Message;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.ContactPolicyRepository;
import dev.lions.unionflow.server.repository.ConversationParticipantRepository;
import dev.lions.unionflow.server.repository.ConversationRepository;
import dev.lions.unionflow.server.repository.MemberBlockRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.MessageRepository;
import dev.lions.unionflow.server.messaging.KafkaEventProducer;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests unitaires du MessagingService avec mocks.
*/
@QuarkusTest
@DisplayName("MessagingService")
class MessagingServiceTest {
@Inject
MessagingService messagingService;
@InjectMock ConversationRepository conversationRepository;
@InjectMock ConversationParticipantRepository participantRepository;
@InjectMock MessageRepository messageRepository;
@InjectMock ContactPolicyRepository contactPolicyRepository;
@InjectMock MemberBlockRepository memberBlockRepository;
@InjectMock MembreRepository membreRepository;
@InjectMock MembreOrganisationRepository membreOrganisationRepository;
@InjectMock KafkaEventProducer kafkaEventProducer;
@InjectMock SecurityIdentity securityIdentity;
private static final UUID MEMBRE_ID = UUID.fromString("00000000-0000-0000-0000-000000000001");
private static final UUID DESTINATAIRE_ID = UUID.fromString("00000000-0000-0000-0000-000000000002");
private static final UUID ORG_ID = UUID.fromString("00000000-0000-0000-0000-000000000003");
private static final UUID CONV_ID = UUID.fromString("00000000-0000-0000-0000-000000000004");
private static final UUID MESSAGE_ID = UUID.fromString("00000000-0000-0000-0000-000000000005");
@BeforeEach
void setUp() {
// Mock du membre connecté
io.quarkus.security.runtime.QuarkusPrincipal principal =
new io.quarkus.security.runtime.QuarkusPrincipal("membre@test.com");
when(securityIdentity.getPrincipal()).thenReturn(principal);
Membre moi = newMembre(MEMBRE_ID, "membre@test.com", "Alpha", "Diallo");
when(membreRepository.find("email", "membre@test.com"))
.thenReturn(io.quarkus.hibernate.orm.panache.PanacheQuery.class.cast(
mockSingleResultQuery(moi)));
}
// ── getMesConversations ───────────────────────────────────────────────────
@Test
@DisplayName("getMesConversations — retourne liste vide")
void getMesConversations_returnsEmpty() {
when(conversationRepository.findByMembreId(MEMBRE_ID))
.thenReturn(Collections.emptyList());
List<ConversationSummaryResponse> result = messagingService.getMesConversations();
assertThat(result).isNotNull().isEmpty();
}
@Test
@DisplayName("getMesConversations — retourne la liste des conversations")
void getMesConversations_returnsList() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findByMembreId(MEMBRE_ID)).thenReturn(List.of(conv));
when(messageRepository.findDernierMessage(CONV_ID)).thenReturn(Optional.empty());
when(messageRepository.countNonLus(CONV_ID, MEMBRE_ID)).thenReturn(0L);
when(participantRepository.findByConversation(CONV_ID)).thenReturn(Collections.emptyList());
List<ConversationSummaryResponse> result = messagingService.getMesConversations();
assertThat(result).hasSize(1);
assertThat(result.get(0).getTypeConversation()).isEqualTo("DIRECTE");
}
// ── getConversation ───────────────────────────────────────────────────────
@Test
@DisplayName("getConversation — notFound → NotFoundException")
void getConversation_notFound() {
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.empty());
assertThatThrownBy(() -> messagingService.getConversation(CONV_ID))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("getConversation — non participant → ForbiddenException")
void getConversation_nonParticipant() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(false);
assertThatThrownBy(() -> messagingService.getConversation(CONV_ID))
.isInstanceOf(ForbiddenException.class);
}
@Test
@DisplayName("getConversation — success → retourne ConversationResponse")
void getConversation_success() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
when(participantRepository.findByConversation(CONV_ID)).thenReturn(Collections.emptyList());
when(messageRepository.findByConversationPagine(eq(CONV_ID), eq(0), anyInt()))
.thenReturn(Collections.emptyList());
when(messageRepository.countNonLus(CONV_ID, MEMBRE_ID)).thenReturn(2L);
ConversationResponse result = messagingService.getConversation(CONV_ID);
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(CONV_ID);
assertThat(result.getNonLus()).isEqualTo(2L);
}
// ── archiverConversation ──────────────────────────────────────────────────
@Test
@DisplayName("archiverConversation — success → statut ARCHIVEE")
void archiverConversation_success() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
when(participantRepository.findByConversation(CONV_ID)).thenReturn(Collections.emptyList());
when(messageRepository.findByConversationPagine(eq(CONV_ID), eq(0), anyInt()))
.thenReturn(Collections.emptyList());
when(messageRepository.countNonLus(CONV_ID, MEMBRE_ID)).thenReturn(0L);
ConversationResponse result = messagingService.archiverConversation(CONV_ID);
assertThat(result.getStatut()).isEqualTo("ARCHIVEE");
}
// ── envoyerMessage ────────────────────────────────────────────────────────
@Test
@DisplayName("envoyerMessage — conversation non trouvée → NotFoundException")
void envoyerMessage_notFound() {
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.empty());
EnvoyerMessageRequest req = EnvoyerMessageRequest.builder()
.typeMessage("TEXTE").contenu("Bonjour").build();
assertThatThrownBy(() -> messagingService.envoyerMessage(CONV_ID, req))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("envoyerMessage — non participant → ForbiddenException")
void envoyerMessage_nonParticipant() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(false);
EnvoyerMessageRequest req = EnvoyerMessageRequest.builder()
.typeMessage("TEXTE").contenu("Bonjour").build();
assertThatThrownBy(() -> messagingService.envoyerMessage(CONV_ID, req))
.isInstanceOf(ForbiddenException.class);
}
@Test
@DisplayName("envoyerMessage — conversation archivée → BadRequestException")
void envoyerMessage_archived() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
conv.setStatut(StatutConversation.ARCHIVEE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
EnvoyerMessageRequest req = EnvoyerMessageRequest.builder()
.typeMessage("TEXTE").contenu("Bonjour").build();
assertThatThrownBy(() -> messagingService.envoyerMessage(CONV_ID, req))
.isInstanceOf(BadRequestException.class);
}
@Test
@DisplayName("envoyerMessage — TEXTE sans contenu → BadRequestException")
void envoyerMessage_texteSansContenu() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
EnvoyerMessageRequest req = EnvoyerMessageRequest.builder()
.typeMessage("TEXTE").contenu("").build();
assertThatThrownBy(() -> messagingService.envoyerMessage(CONV_ID, req))
.isInstanceOf(BadRequestException.class);
}
@Test
@DisplayName("envoyerMessage — VOCAL sans urlFichier → BadRequestException")
void envoyerMessage_vocalSansUrl() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
EnvoyerMessageRequest req = EnvoyerMessageRequest.builder()
.typeMessage("VOCAL").dureeAudio(30).build();
assertThatThrownBy(() -> messagingService.envoyerMessage(CONV_ID, req))
.isInstanceOf(BadRequestException.class);
}
@Test
@DisplayName("envoyerMessage — VOCAL sans dureeAudio → BadRequestException")
void envoyerMessage_vocalSansDuree() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
EnvoyerMessageRequest req = EnvoyerMessageRequest.builder()
.typeMessage("VOCAL").urlFichier("https://example.com/audio.opus").build();
assertThatThrownBy(() -> messagingService.envoyerMessage(CONV_ID, req))
.isInstanceOf(BadRequestException.class);
}
// ── getMessages ───────────────────────────────────────────────────────────
@Test
@DisplayName("getMessages — success → retourne liste de messages")
void getMessages_success() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
Message msg = buildMessage(conv);
when(conversationRepository.findConversationById(CONV_ID)).thenReturn(Optional.of(conv));
when(participantRepository.estParticipant(CONV_ID, MEMBRE_ID)).thenReturn(true);
when(messageRepository.findByConversationPagine(eq(CONV_ID), eq(0), anyInt()))
.thenReturn(List.of(msg));
List<MessageResponse> result = messagingService.getMessages(CONV_ID, 0);
assertThat(result).hasSize(1);
assertThat(result.get(0).getTypeMessage()).isEqualTo("TEXTE");
}
// ── marquerConversationLue ────────────────────────────────────────────────
@Test
@DisplayName("marquerConversationLue — participant trouvé → luJusqua mis à jour")
void marquerConversationLue_success() {
ConversationParticipant participant = new ConversationParticipant();
participant.setMembre(newMembre(MEMBRE_ID, "membre@test.com", "Alpha", "Diallo"));
when(participantRepository.findParticipant(CONV_ID, MEMBRE_ID))
.thenReturn(Optional.of(participant));
messagingService.marquerConversationLue(CONV_ID);
assertThat(participant.getLuJusqua()).isNotNull();
}
@Test
@DisplayName("marquerConversationLue — participant absent → no-op")
void marquerConversationLue_noParticipant() {
when(participantRepository.findParticipant(CONV_ID, MEMBRE_ID))
.thenReturn(Optional.empty());
// ne lève pas d'exception
messagingService.marquerConversationLue(CONV_ID);
}
// ── supprimerMessage ──────────────────────────────────────────────────────
@Test
@DisplayName("supprimerMessage — message non trouvé → NotFoundException")
void supprimerMessage_notFound() {
when(messageRepository.findMessageById(MESSAGE_ID)).thenReturn(Optional.empty());
assertThatThrownBy(() -> messagingService.supprimerMessage(CONV_ID, MESSAGE_ID))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("supprimerMessage — message dans autre conversation → NotFoundException")
void supprimerMessage_wrongConversation() {
Conversation autreConv = buildConversation(TypeConversation.DIRECTE);
autreConv.setId(UUID.randomUUID()); // ID différent de CONV_ID
Message msg = buildMessage(autreConv);
when(messageRepository.findMessageById(MESSAGE_ID)).thenReturn(Optional.of(msg));
assertThatThrownBy(() -> messagingService.supprimerMessage(CONV_ID, MESSAGE_ID))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("supprimerMessage — pas l'auteur → ForbiddenException")
void supprimerMessage_notAuthor() {
Conversation conv = buildConversation(TypeConversation.DIRECTE);
Membre autreExpedireur = newMembre(DESTINATAIRE_ID, "other@test.com", "Beta", "Koné");
Message msg = new Message();
msg.setId(MESSAGE_ID);
msg.setConversation(conv);
msg.setExpediteur(autreExpedireur);
msg.setTypeMessage(TypeContenu.TEXTE);
msg.setContenu("Message d'un autre");
when(messageRepository.findMessageById(MESSAGE_ID)).thenReturn(Optional.of(msg));
assertThatThrownBy(() -> messagingService.supprimerMessage(CONV_ID, MESSAGE_ID))
.isInstanceOf(ForbiddenException.class);
}
// ── bloquerMembre ─────────────────────────────────────────────────────────
@Test
@DisplayName("bloquerMembre — soi-même → BadRequestException")
void bloquerMembre_soiMeme() {
BloquerMembreRequest req = BloquerMembreRequest.builder()
.membreABloquerId(MEMBRE_ID)
.organisationId(ORG_ID)
.build();
assertThatThrownBy(() -> messagingService.bloquerMembre(req))
.isInstanceOf(BadRequestException.class);
}
@Test
@DisplayName("bloquerMembre — membre introuvable → NotFoundException")
void bloquerMembre_destinataireInconnu() {
when(membreRepository.findById(DESTINATAIRE_ID)).thenReturn(null);
BloquerMembreRequest req = BloquerMembreRequest.builder()
.membreABloquerId(DESTINATAIRE_ID)
.organisationId(ORG_ID)
.build();
assertThatThrownBy(() -> messagingService.bloquerMembre(req))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("bloquerMembre — déjà bloqué → BadRequestException")
void bloquerMembre_dejaBloque() {
Membre aBloquer = newMembre(DESTINATAIRE_ID, "dest@test.com", "Beta", "Koné");
when(membreRepository.findById(DESTINATAIRE_ID)).thenReturn(aBloquer);
when(memberBlockRepository.estBloque(MEMBRE_ID, DESTINATAIRE_ID, ORG_ID)).thenReturn(true);
BloquerMembreRequest req = BloquerMembreRequest.builder()
.membreABloquerId(DESTINATAIRE_ID)
.organisationId(ORG_ID)
.build();
assertThatThrownBy(() -> messagingService.bloquerMembre(req))
.isInstanceOf(BadRequestException.class);
}
// ── debloquerMembre ───────────────────────────────────────────────────────
@Test
@DisplayName("debloquerMembre — blocage non trouvé → NotFoundException")
void debloquerMembre_notFound() {
when(memberBlockRepository.findBlocage(MEMBRE_ID, DESTINATAIRE_ID, ORG_ID))
.thenReturn(Optional.empty());
assertThatThrownBy(() -> messagingService.debloquerMembre(DESTINATAIRE_ID, ORG_ID))
.isInstanceOf(NotFoundException.class);
}
// ── getPolitique ──────────────────────────────────────────────────────────
@Test
@DisplayName("getPolitique — politique existante → retourne la politique")
void getPolitique_existing() {
Organisation org = newOrg();
ContactPolicy policy = ContactPolicy.builder()
.organisation(org)
.typePolitique(TypePolitiqueCommunication.BUREAU_SEULEMENT)
.autoriserMembreVersMembre(false)
.autoriserMembreVersRole(true)
.autoriserNotesVocales(true)
.build();
policy.setId(UUID.randomUUID());
when(contactPolicyRepository.findByOrganisationId(ORG_ID)).thenReturn(Optional.of(policy));
ContactPolicyResponse result = messagingService.getPolitique(ORG_ID);
assertThat(result.getTypePolitique()).isEqualTo("BUREAU_SEULEMENT");
assertThat(result.isAutoriserMembreVersMembre()).isFalse();
}
// ── mettreAJourPolitique ──────────────────────────────────────────────────
@Test
@DisplayName("mettreAJourPolitique — change typePolitique")
void mettreAJourPolitique_changeType() {
Organisation org = newOrg();
ContactPolicy policy = ContactPolicy.builder()
.organisation(org)
.typePolitique(TypePolitiqueCommunication.OUVERT)
.autoriserMembreVersMembre(true)
.autoriserMembreVersRole(true)
.autoriserNotesVocales(true)
.build();
policy.setId(UUID.randomUUID());
when(contactPolicyRepository.findByOrganisationId(ORG_ID)).thenReturn(Optional.of(policy));
MettreAJourPolitiqueRequest req = MettreAJourPolitiqueRequest.builder()
.typePolitique("BUREAU_SEULEMENT")
.autoriserMembreVersMembre(false)
.build();
ContactPolicyResponse result = messagingService.mettreAJourPolitique(ORG_ID, req);
assertThat(result.getTypePolitique()).isEqualTo("BUREAU_SEULEMENT");
assertThat(result.isAutoriserMembreVersMembre()).isFalse();
}
// ── Helpers ───────────────────────────────────────────────────────────────
private Membre newMembre(UUID id, String email, String prenom, String nom) {
Membre m = new Membre();
m.setId(id);
m.setNumeroMembre("M-" + id.toString().substring(0, 4));
m.setPrenom(prenom);
m.setNom(nom);
m.setEmail(email);
m.setDateNaissance(LocalDate.now());
return m;
}
private Organisation newOrg() {
Organisation org = new Organisation();
org.setId(ORG_ID);
org.setNom("Tontine Test");
return org;
}
private Conversation buildConversation(TypeConversation type) {
Conversation conv = new Conversation();
conv.setId(CONV_ID);
conv.setOrganisation(newOrg());
conv.setTypeConversation(type);
conv.setStatut(StatutConversation.ACTIVE);
conv.setNombreMessages(0);
return conv;
}
private Message buildMessage(Conversation conv) {
Membre expediteur = newMembre(MEMBRE_ID, "membre@test.com", "Alpha", "Diallo");
Message msg = new Message();
msg.setId(MESSAGE_ID);
msg.setConversation(conv);
msg.setExpediteur(expediteur);
msg.setTypeMessage(TypeContenu.TEXTE);
msg.setContenu("Bonjour !");
msg.setDateCreation(LocalDateTime.now());
return msg;
}
@SuppressWarnings("unchecked")
private <T> io.quarkus.hibernate.orm.panache.PanacheQuery<T> mockSingleResultQuery(T entity) {
io.quarkus.hibernate.orm.panache.PanacheQuery<T> query =
org.mockito.Mockito.mock(io.quarkus.hibernate.orm.panache.PanacheQuery.class);
when(query.firstResult()).thenReturn(entity);
return query;
}
}

View File

@@ -0,0 +1,755 @@
package dev.lions.unionflow.server.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import dev.lions.unionflow.server.api.dto.versement.request.DeclarerVersementManuelRequest;
import dev.lions.unionflow.server.api.dto.versement.request.InitierDepotEpargneRequest;
import dev.lions.unionflow.server.api.dto.versement.request.InitierVersementWaveRequest;
import dev.lions.unionflow.server.api.dto.versement.response.VersementGatewayResponse;
import dev.lions.unionflow.server.api.dto.versement.response.VersementResponse;
import dev.lions.unionflow.server.api.dto.versement.response.VersementStatutResponse;
import dev.lions.unionflow.server.api.dto.versement.response.VersementSummaryResponse;
import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.IntentionPaiement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.Versement;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.repository.IntentionPaiementRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.TypeReferenceRepository;
import dev.lions.unionflow.server.repository.VersementRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.service.WaveCheckoutService.WaveCheckoutException;
import dev.lions.unionflow.server.service.WaveCheckoutService.WaveCheckoutSessionResponse;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Tests unitaires pour {@link VersementService}.
*
* Couverture : trouverParId, trouverParNumeroReference, listerParMembre,
* calculerMontantTotalConfirmes, getMesVersements, validerVersement,
* annulerVersement, initierVersementWave (succès + Wave error + cotisation
* inconnue + cotisation d'un autre membre), declarerVersementManuel,
* verifierStatutVersement (complété / expiré / en attente),
* confirmerVersementWave (idempotence), toE164 (toutes branches).
*/
@QuarkusTest
@DisplayName("VersementService")
class VersementServiceTest {
@Inject
VersementService versementService;
@InjectMock VersementRepository versementRepository;
@InjectMock MembreRepository membreRepository;
@InjectMock KeycloakService keycloakService;
@InjectMock TypeReferenceRepository typeReferenceRepository;
@InjectMock IntentionPaiementRepository intentionPaiementRepository;
@InjectMock WaveCheckoutService waveCheckoutService;
@InjectMock CompteEpargneRepository compteEpargneRepository;
@InjectMock MembreOrganisationRepository membreOrganisationRepository;
@InjectMock NotificationService notificationService;
private Membre testMembre;
private Membre autreMembre;
private Organisation testOrg;
private Cotisation testCotisation;
private Versement testVersement;
private CompteEpargne testCompte;
private EntityManager mockEm;
@BeforeEach
void setUp() {
testOrg = new Organisation();
testOrg.setId(UUID.randomUUID());
testOrg.setNom("Org Test");
testMembre = new Membre();
testMembre.setId(UUID.randomUUID());
testMembre.setNumeroMembre("M001");
testMembre.setEmail("membre@test.com");
testMembre.setPrenom("Jean");
testMembre.setNom("Dupont");
testMembre.setDateNaissance(LocalDate.of(1990, 1, 1));
autreMembre = new Membre();
autreMembre.setId(UUID.randomUUID());
autreMembre.setNumeroMembre("M002");
autreMembre.setEmail("autre@test.com");
autreMembre.setDateNaissance(LocalDate.of(1985, 5, 15));
testCotisation = new Cotisation();
testCotisation.setId(UUID.randomUUID());
testCotisation.setNumeroReference("COT-2026-001");
testCotisation.setMembre(testMembre);
testCotisation.setOrganisation(testOrg);
testCotisation.setMontantDu(new BigDecimal("5000"));
testCotisation.setCodeDevise("XOF");
testCotisation.setStatut("EN_ATTENTE");
testCotisation.setDateEcheance(LocalDate.now().plusDays(30));
testCotisation.setTypeCotisation("MENSUELLE");
testCotisation.setLibelle("Cotisation janvier 2026");
testCotisation.setAnnee(2026);
testVersement = new Versement();
testVersement.setId(UUID.randomUUID());
testVersement.setNumeroReference("VRS-2026-001");
testVersement.setMontant(new BigDecimal("5000"));
testVersement.setCodeDevise("XOF");
testVersement.setMethodePaiement("WAVE");
testVersement.setStatutPaiement("EN_ATTENTE");
testVersement.setMembre(testMembre);
testVersement.setDatePaiement(LocalDateTime.now());
testCompte = new CompteEpargne();
testCompte.setId(UUID.randomUUID());
testCompte.setMembre(testMembre);
testCompte.setOrganisation(testOrg);
mockEm = mock(EntityManager.class);
when(versementRepository.getEntityManager()).thenReturn(mockEm);
when(typeReferenceRepository.findByDomaineAndCode(anyString(), anyString()))
.thenReturn(Optional.empty());
when(keycloakService.getCurrentUserEmail()).thenReturn(testMembre.getEmail());
}
// ── trouverParId ──────────────────────────────────────────────────────────
@Test
@DisplayName("trouverParId — trouvé → retourne response")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void trouverParId_found() {
when(versementRepository.findVersementById(testVersement.getId()))
.thenReturn(Optional.of(testVersement));
VersementResponse r = versementService.trouverParId(testVersement.getId());
assertThat(r.getNumeroReference()).isEqualTo("VRS-2026-001");
}
@Test
@DisplayName("trouverParId — non trouvé → NotFoundException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void trouverParId_notFound() {
when(versementRepository.findVersementById(any())).thenReturn(Optional.empty());
assertThatThrownBy(() -> versementService.trouverParId(UUID.randomUUID()))
.isInstanceOf(NotFoundException.class);
}
// ── trouverParNumeroReference ─────────────────────────────────────────────
@Test
@DisplayName("trouverParNumeroReference — trouvé → response")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void trouverParNumeroReference_found() {
when(versementRepository.findByNumeroReference("VRS-2026-001"))
.thenReturn(Optional.of(testVersement));
VersementResponse r = versementService.trouverParNumeroReference("VRS-2026-001");
assertThat(r).isNotNull();
}
@Test
@DisplayName("trouverParNumeroReference — non trouvé → NotFoundException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void trouverParNumeroReference_notFound() {
when(versementRepository.findByNumeroReference(anyString())).thenReturn(Optional.empty());
assertThatThrownBy(() -> versementService.trouverParNumeroReference("VRS-INCONNU"))
.isInstanceOf(NotFoundException.class);
}
// ── listerParMembre ───────────────────────────────────────────────────────
@Test
@DisplayName("listerParMembre — retourne liste non nulle")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void listerParMembre_returnsList() {
when(versementRepository.findByMembreId(testMembre.getId()))
.thenReturn(List.of(testVersement));
List<VersementSummaryResponse> list = versementService.listerParMembre(testMembre.getId());
assertThat(list).hasSize(1);
}
// ── calculerMontantTotalConfirmes ─────────────────────────────────────────
@Test
@DisplayName("calculerMontantTotalConfirmes — délègue au repository")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void calculerMontantTotalConfirmes_delegates() {
LocalDateTime debut = LocalDateTime.now().minusDays(30);
LocalDateTime fin = LocalDateTime.now();
when(versementRepository.calculerMontantTotalConfirmes(debut, fin))
.thenReturn(new BigDecimal("15000"));
BigDecimal total = versementService.calculerMontantTotalConfirmes(debut, fin);
assertThat(total).isEqualByComparingTo("15000");
}
// ── getMesVersements ──────────────────────────────────────────────────────
@Test
@DisplayName("getMesVersements — retourne historique du membre connecté")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void getMesVersements_returnsHistory() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
@SuppressWarnings("unchecked")
TypedQuery<Versement> query = mock(TypedQuery.class);
doReturn(query).when(mockEm).createQuery(anyString(), any());
when(query.setParameter(anyString(), any())).thenReturn(query);
when(query.setMaxResults(anyInt())).thenReturn(query);
when(query.getResultList()).thenReturn(List.of(testVersement));
List<VersementSummaryResponse> list = versementService.getMesVersements(5);
assertThat(list).hasSize(1);
}
@Test
@DisplayName("getMesVersements — membre non trouvé → NotFoundException")
@TestSecurity(user = "inconnu@test.com", roles = {"MEMBRE"})
void getMesVersements_memberNotFound() {
when(membreRepository.findByEmail(anyString())).thenReturn(Optional.empty());
assertThatThrownBy(() -> versementService.getMesVersements(5))
.isInstanceOf(NotFoundException.class);
}
// ── validerVersement ──────────────────────────────────────────────────────
@Test
@DisplayName("validerVersement — passe à CONFIRME")
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void validerVersement_setsConfirme() {
when(versementRepository.findVersementById(testVersement.getId()))
.thenReturn(Optional.of(testVersement));
VersementResponse r = versementService.validerVersement(testVersement.getId());
assertThat(r.getStatutPaiement()).isEqualTo("CONFIRME");
}
@Test
@DisplayName("validerVersement — déjà confirmé → idempotent")
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void validerVersement_alreadyConfirme_idempotent() {
testVersement.setStatutPaiement("CONFIRME");
when(versementRepository.findVersementById(testVersement.getId()))
.thenReturn(Optional.of(testVersement));
VersementResponse r = versementService.validerVersement(testVersement.getId());
assertThat(r.getStatutPaiement()).isEqualTo("CONFIRME");
}
@Test
@DisplayName("validerVersement — non trouvé → NotFoundException")
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void validerVersement_notFound() {
when(versementRepository.findVersementById(any())).thenReturn(Optional.empty());
assertThatThrownBy(() -> versementService.validerVersement(UUID.randomUUID()))
.isInstanceOf(NotFoundException.class);
}
// ── annulerVersement ──────────────────────────────────────────────────────
@Test
@DisplayName("annulerVersement — passe à ANNULE")
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void annulerVersement_setsAnnule() {
when(versementRepository.findVersementById(testVersement.getId()))
.thenReturn(Optional.of(testVersement));
VersementResponse r = versementService.annulerVersement(testVersement.getId());
assertThat(r.getStatutPaiement()).isEqualTo("ANNULE");
}
@Test
@DisplayName("annulerVersement — déjà ANNULE → IllegalStateException")
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
void annulerVersement_alreadyAnnule() {
testVersement.setStatutPaiement("ANNULE");
when(versementRepository.findVersementById(testVersement.getId()))
.thenReturn(Optional.of(testVersement));
assertThatThrownBy(() -> versementService.annulerVersement(testVersement.getId()))
.isInstanceOf(IllegalStateException.class);
}
// ── initierVersementWave ──────────────────────────────────────────────────
@Test
@DisplayName("initierVersementWave — succès Wave → retourne waveLaunchUrl")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierVersementWave_success() throws WaveCheckoutException {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(mockEm.find(Cotisation.class, testCotisation.getId()))
.thenReturn(testCotisation);
when(waveCheckoutService.getRedirectBaseUrl()).thenReturn("https://api.test.com");
WaveCheckoutSessionResponse session = new WaveCheckoutSessionResponse("cos-abc123", "wave://checkout/abc123");
when(waveCheckoutService.createSession(
anyString(), anyString(), anyString(), anyString(), anyString(), anyString()))
.thenReturn(session);
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
doAnswer(inv -> null).when(versementRepository).persist(any(Versement.class));
when(mockEm.merge(any())).thenReturn(testCotisation);
InitierVersementWaveRequest request = InitierVersementWaveRequest.builder()
.cotisationId(testCotisation.getId())
.numeroTelephone("771234567")
.build();
VersementGatewayResponse r = versementService.initierVersementWave(request);
assertThat(r.getWaveLaunchUrl()).isEqualTo("wave://checkout/abc123");
assertThat(r.getWaveCheckoutSessionId()).isEqualTo("cos-abc123");
assertThat(r.getMontant()).isEqualByComparingTo("5000");
assertThat(r.getClientReference()).isNotNull();
}
@Test
@DisplayName("initierVersementWave — cotisation inconnue → NotFoundException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierVersementWave_cotisationInconnue() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(mockEm.find(Cotisation.class, any())).thenReturn(null);
InitierVersementWaveRequest request = InitierVersementWaveRequest.builder()
.cotisationId(UUID.randomUUID())
.numeroTelephone("771234567")
.build();
assertThatThrownBy(() -> versementService.initierVersementWave(request))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("initierVersementWave — cotisation d'un autre membre → IllegalArgumentException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierVersementWave_cotisationAutreMembre() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
testCotisation.setMembre(autreMembre);
when(mockEm.find(Cotisation.class, testCotisation.getId()))
.thenReturn(testCotisation);
InitierVersementWaveRequest request = InitierVersementWaveRequest.builder()
.cotisationId(testCotisation.getId())
.numeroTelephone("771234567")
.build();
assertThatThrownBy(() -> versementService.initierVersementWave(request))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
@DisplayName("initierVersementWave — Wave API error → BadRequestException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierVersementWave_waveApiError() throws WaveCheckoutException {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(mockEm.find(Cotisation.class, testCotisation.getId()))
.thenReturn(testCotisation);
when(waveCheckoutService.getRedirectBaseUrl()).thenReturn("https://api.test.com");
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
doThrow(new WaveCheckoutException("Wave down"))
.when(waveCheckoutService).createSession(
anyString(), anyString(), anyString(), anyString(), anyString(), anyString());
InitierVersementWaveRequest request = InitierVersementWaveRequest.builder()
.cotisationId(testCotisation.getId())
.numeroTelephone("771234567")
.build();
assertThatThrownBy(() -> versementService.initierVersementWave(request))
.isInstanceOf(jakarta.ws.rs.BadRequestException.class);
}
@Test
@DisplayName("initierVersementWave — sans numéro téléphone (web QR) → successUrl web")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierVersementWave_noPhone_webContext() throws WaveCheckoutException {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(mockEm.find(Cotisation.class, testCotisation.getId()))
.thenReturn(testCotisation);
when(waveCheckoutService.getRedirectBaseUrl()).thenReturn("https://api.test.com");
WaveCheckoutSessionResponse session = new WaveCheckoutSessionResponse("cos-web", "wave://checkout/web");
when(waveCheckoutService.createSession(
anyString(), anyString(), anyString(), anyString(), anyString(), anyString()))
.thenReturn(session);
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
doAnswer(inv -> null).when(versementRepository).persist(any(Versement.class));
when(mockEm.merge(any())).thenReturn(testCotisation);
InitierVersementWaveRequest request = InitierVersementWaveRequest.builder()
.cotisationId(testCotisation.getId())
.build(); // pas de numéro → web
VersementGatewayResponse r = versementService.initierVersementWave(request);
assertThat(r.getWaveLaunchUrl()).isEqualTo("wave://checkout/web");
}
// ── initierDepotEpargneEnLigne ────────────────────────────────────────────
@Test
@DisplayName("initierDepotEpargneEnLigne — succès Wave")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierDepotEpargne_success() throws WaveCheckoutException {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(compteEpargneRepository.findByIdOptional(testCompte.getId()))
.thenReturn(Optional.of(testCompte));
when(waveCheckoutService.getRedirectBaseUrl()).thenReturn("https://api.test.com");
WaveCheckoutSessionResponse session = new WaveCheckoutSessionResponse("cos-epargne", "wave://checkout/epargne");
when(waveCheckoutService.createSession(
anyString(), anyString(), anyString(), anyString(), anyString(), anyString()))
.thenReturn(session);
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
InitierDepotEpargneRequest request = InitierDepotEpargneRequest.builder()
.compteId(testCompte.getId())
.montant(new BigDecimal("10000"))
.numeroTelephone("771234567")
.build();
VersementGatewayResponse r = versementService.initierDepotEpargneEnLigne(request);
assertThat(r.getWaveLaunchUrl()).isEqualTo("wave://checkout/epargne");
assertThat(r.getMontant()).isEqualByComparingTo("10000");
}
@Test
@DisplayName("initierDepotEpargneEnLigne — compte inconnu → NotFoundException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierDepotEpargne_compteInconnu() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(compteEpargneRepository.findByIdOptional(any())).thenReturn(Optional.empty());
assertThatThrownBy(() -> versementService.initierDepotEpargneEnLigne(
InitierDepotEpargneRequest.builder()
.compteId(UUID.randomUUID())
.montant(BigDecimal.TEN)
.build()))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("initierDepotEpargneEnLigne — compte d'un autre membre → IllegalArgumentException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierDepotEpargne_compteAutreMembre() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
testCompte.setMembre(autreMembre);
when(compteEpargneRepository.findByIdOptional(testCompte.getId()))
.thenReturn(Optional.of(testCompte));
assertThatThrownBy(() -> versementService.initierDepotEpargneEnLigne(
InitierDepotEpargneRequest.builder()
.compteId(testCompte.getId())
.montant(BigDecimal.TEN)
.build()))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
@DisplayName("initierDepotEpargneEnLigne — Wave error → BadRequestException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void initierDepotEpargne_waveError() throws WaveCheckoutException {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
when(compteEpargneRepository.findByIdOptional(testCompte.getId()))
.thenReturn(Optional.of(testCompte));
when(waveCheckoutService.getRedirectBaseUrl()).thenReturn("https://api.test.com");
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
doThrow(new WaveCheckoutException("Wave down"))
.when(waveCheckoutService).createSession(
anyString(), anyString(), anyString(), anyString(), anyString(), anyString());
assertThatThrownBy(() -> versementService.initierDepotEpargneEnLigne(
InitierDepotEpargneRequest.builder()
.compteId(testCompte.getId())
.montant(BigDecimal.TEN)
.build()))
.isInstanceOf(jakarta.ws.rs.BadRequestException.class);
}
// ── declarerVersementManuel ───────────────────────────────────────────────
@Test
@DisplayName("declarerVersementManuel — créé EN_ATTENTE_VALIDATION")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void declarerVersementManuel_success() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
@SuppressWarnings("unchecked")
TypedQuery<Cotisation> query = mock(TypedQuery.class);
doReturn(query).when(mockEm).createQuery(anyString(), any());
when(query.setParameter(anyString(), any())).thenReturn(query);
when(query.getResultList()).thenReturn(List.of(testCotisation));
doAnswer(inv -> null).when(versementRepository).persist(any(Versement.class));
when(membreOrganisationRepository.findFirstByMembreId(any()))
.thenReturn(Optional.empty());
DeclarerVersementManuelRequest request = DeclarerVersementManuelRequest.builder()
.cotisationId(testCotisation.getId())
.methodePaiement("ESPECES")
.commentaire("Remis en main propre")
.build();
VersementResponse r = versementService.declarerVersementManuel(request);
assertThat(r.getStatutPaiement()).isEqualTo("EN_ATTENTE_VALIDATION");
assertThat(r.getMethodePaiement()).isEqualTo("ESPECES");
}
@Test
@DisplayName("declarerVersementManuel — cotisation inconnue → NotFoundException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void declarerVersementManuel_cotisationInconnue() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
@SuppressWarnings("unchecked")
TypedQuery<Cotisation> query = mock(TypedQuery.class);
doReturn(query).when(mockEm).createQuery(anyString(), any());
when(query.setParameter(anyString(), any())).thenReturn(query);
when(query.getResultList()).thenReturn(Collections.emptyList());
assertThatThrownBy(() -> versementService.declarerVersementManuel(
DeclarerVersementManuelRequest.builder()
.cotisationId(UUID.randomUUID())
.methodePaiement("ESPECES")
.build()))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("declarerVersementManuel — cotisation d'un autre membre → IllegalArgumentException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void declarerVersementManuel_cotisationAutreMembre() {
when(membreRepository.findByEmail("membre@test.com"))
.thenReturn(Optional.of(testMembre));
testCotisation.setMembre(autreMembre);
@SuppressWarnings("unchecked")
TypedQuery<Cotisation> query = mock(TypedQuery.class);
doReturn(query).when(mockEm).createQuery(anyString(), any());
when(query.setParameter(anyString(), any())).thenReturn(query);
when(query.getResultList()).thenReturn(List.of(testCotisation));
assertThatThrownBy(() -> versementService.declarerVersementManuel(
DeclarerVersementManuelRequest.builder()
.cotisationId(testCotisation.getId())
.methodePaiement("ESPECES")
.build()))
.isInstanceOf(IllegalArgumentException.class);
}
// ── verifierStatutVersement ───────────────────────────────────────────────
@Test
@DisplayName("verifierStatutVersement — intention inconnue → NotFoundException")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void verifierStatutVersement_notFound() {
when(intentionPaiementRepository.findById(any())).thenReturn(null);
assertThatThrownBy(() -> versementService.verifierStatutVersement(UUID.randomUUID()))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("verifierStatutVersement — déjà COMPLETEE → confirme=true")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void verifierStatutVersement_completee() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.COMPLETEE);
when(intentionPaiementRepository.findById(intention.getId()))
.thenReturn(intention);
VersementStatutResponse r = versementService.verifierStatutVersement(intention.getId());
assertThat(r.isConfirme()).isTrue();
assertThat(r.getMessage()).contains("confirmé");
}
@Test
@DisplayName("verifierStatutVersement — EXPIREE → confirme=false")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void verifierStatutVersement_expiree() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.EXPIREE);
when(intentionPaiementRepository.findById(intention.getId()))
.thenReturn(intention);
VersementStatutResponse r = versementService.verifierStatutVersement(intention.getId());
assertThat(r.isConfirme()).isFalse();
}
@Test
@DisplayName("verifierStatutVersement — ECHOUEE → confirme=false")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void verifierStatutVersement_echouee() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.ECHOUEE);
when(intentionPaiementRepository.findById(intention.getId()))
.thenReturn(intention);
VersementStatutResponse r = versementService.verifierStatutVersement(intention.getId());
assertThat(r.isConfirme()).isFalse();
}
@Test
@DisplayName("verifierStatutVersement — session expirée localement → EXPIREE")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void verifierStatutVersement_localExpiry() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.EN_COURS);
intention.setDateExpiration(LocalDateTime.now().minusMinutes(5));
when(intentionPaiementRepository.findById(intention.getId()))
.thenReturn(intention);
VersementStatutResponse r = versementService.verifierStatutVersement(intention.getId());
assertThat(r.getStatut()).isEqualTo("EXPIREE");
}
@Test
@DisplayName("verifierStatutVersement — EN_COURS sans session Wave → en attente")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void verifierStatutVersement_enCoursNoSession() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.EN_COURS);
intention.setDateExpiration(LocalDateTime.now().plusMinutes(25));
when(intentionPaiementRepository.findById(intention.getId()))
.thenReturn(intention);
VersementStatutResponse r = versementService.verifierStatutVersement(intention.getId());
assertThat(r.isConfirme()).isFalse();
assertThat(r.getMessage()).containsIgnoringCase("attente");
}
// ── confirmerVersementWave ────────────────────────────────────────────────
@Test
@DisplayName("confirmerVersementWave — déjà COMPLETEE → idempotent")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void confirmerVersementWave_alreadyCompletee_idempotent() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.COMPLETEE);
// Ne doit pas appeler persist
versementService.confirmerVersementWave(intention, null);
// Pas d'exception = idempotence OK
}
@Test
@DisplayName("confirmerVersementWave — objetsCibles null → passe sans erreur")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void confirmerVersementWave_nullObjetsCibles() {
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.EN_COURS);
intention.setObjetsCibles(null);
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
versementService.confirmerVersementWave(intention, "TCN-123");
assertThat(intention.getStatut()).isEqualTo(StatutIntentionPaiement.COMPLETEE);
}
@Test
@DisplayName("confirmerVersementWave — JSON cotisation → mise à jour cotisation")
@TestSecurity(user = "membre@test.com", roles = {"MEMBRE"})
void confirmerVersementWave_reconcilesCotisation() {
UUID cotisationId = testCotisation.getId();
IntentionPaiement intention = buildIntention(StatutIntentionPaiement.EN_COURS);
intention.setObjetsCibles(
"[{\"type\":\"COTISATION\",\"id\":\"" + cotisationId + "\",\"montant\":5000}]");
doAnswer(inv -> null).when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
when(mockEm.find(Cotisation.class, cotisationId)).thenReturn(testCotisation);
when(mockEm.merge(any())).thenReturn(testCotisation);
versementService.confirmerVersementWave(intention, "TCN-ABC");
assertThat(testCotisation.getStatut()).isEqualTo("PAYEE");
assertThat(testCotisation.getMontantPaye()).isEqualByComparingTo("5000");
}
// ── toE164 ────────────────────────────────────────────────────────────────
@Test
@DisplayName("toE164 — null → null")
void toE164_null() {
assertThat(VersementService.toE164(null)).isNull();
}
@Test
@DisplayName("toE164 — vide → null")
void toE164_blank() {
assertThat(VersementService.toE164(" ")).isNull();
}
@Test
@DisplayName("toE164 — 9 chiffres commençant par 7 → +221 prefix")
void toE164_9digits_7prefix() {
assertThat(VersementService.toE164("771234567")).isEqualTo("+221771234567");
}
@Test
@DisplayName("toE164 — 9 chiffres commençant par 0 → +221 + supprime 0")
void toE164_9digits_0prefix() {
assertThat(VersementService.toE164("071234567")).isEqualTo("+22171234567");
}
@Test
@DisplayName("toE164 — déjà avec indicatif 221 → ajoute +")
void toE164_with221prefix() {
assertThat(VersementService.toE164("221771234567")).isEqualTo("+221771234567");
}
@Test
@DisplayName("toE164 — déjà au format E.164 (+221...) → conservé")
void toE164_alreadyE164() {
assertThat(VersementService.toE164("+221771234567")).isEqualTo("+221771234567");
}
@Test
@DisplayName("toE164 — format non reconnu → + + chiffres")
void toE164_unknown() {
assertThat(VersementService.toE164("0033612345678")).startsWith("+");
}
// ── helpers ───────────────────────────────────────────────────────────────
private IntentionPaiement buildIntention(StatutIntentionPaiement statut) {
IntentionPaiement i = new IntentionPaiement();
i.setId(UUID.randomUUID());
i.setStatut(statut);
i.setMontantTotal(new BigDecimal("5000"));
i.setCodeDevise("XOF");
i.setUtilisateur(testMembre);
i.setDateExpiration(LocalDateTime.now().plusMinutes(30));
return i;
}
}