feat(messaging): module messagerie unifié avec contact policies + member blocks

Refactor complet : fusion de Conversation + Message en un module Messaging unique
avec ContactPolicy (règles qui-peut-parler-à-qui) et MemberBlock (blocages utilisateur).

- Migration V28 : tables conversations/conversation_participants/messages/
  contact_policies/member_blocks
- Nouvelles entités : ContactPolicy, ConversationParticipant, MemberBlock
  (Conversation/Message mises à jour avec relations)
- Nouvelles repositories : ContactPolicyRepository, ConversationParticipantRepository,
  MemberBlockRepository
- MessagingResource (nouveau) remplace ConversationResource + MessageResource
- MessagingService (nouveau) remplace ConversationService + MessageService
  avec vérifications appartenance org + policies + blocages avant envoi
- Anciens fichiers Conversation/Message Resource/Service/Tests supprimés
This commit is contained in:
dahoud
2026-04-15 20:23:04 +00:00
parent a650b372f1
commit 719d45e1fe
25 changed files with 2120 additions and 3298 deletions

View File

@@ -1,95 +1,166 @@
package dev.lions.unionflow.server.entity;
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
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.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@DisplayName("Conversation")
class ConversationTest {
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 Organisation newOrg() {
Organisation org = new Organisation();
org.setId(UUID.randomUUID());
org.setNom("Tontine Test");
return org;
}
@Test
@DisplayName("getters/setters de base")
@DisplayName("getters/setters — tous les champs")
void gettersSetters() {
Conversation c = new Conversation();
c.setName("Groupe Test");
c.setDescription("Description groupe");
c.setType(ConversationType.GROUP);
c.setIsMuted(false);
c.setIsPinned(true);
c.setIsArchived(false);
Conversation conv = new Conversation();
Organisation org = newOrg();
conv.setOrganisation(org);
conv.setTypeConversation(TypeConversation.DIRECTE);
conv.setStatut(StatutConversation.ACTIVE);
conv.setTitre("Discussion");
conv.setNombreMessages(5);
assertThat(c.getName()).isEqualTo("Groupe Test");
assertThat(c.getDescription()).isEqualTo("Description groupe");
assertThat(c.getType()).isEqualTo(ConversationType.GROUP);
assertThat(c.getIsMuted()).isFalse();
assertThat(c.getIsPinned()).isTrue();
assertThat(c.getIsArchived()).isFalse();
assertThat(conv.getOrganisation()).isEqualTo(org);
assertThat(conv.getTypeConversation()).isEqualTo(TypeConversation.DIRECTE);
assertThat(conv.getStatut()).isEqualTo(StatutConversation.ACTIVE);
assertThat(conv.getTitre()).isEqualTo("Discussion");
assertThat(conv.getNombreMessages()).isEqualTo(5);
}
@Test
@DisplayName("onUpdate (PreUpdate) - met à jour updatedAt via réflexion")
void onUpdate_setsUpdatedAt() throws Exception {
Conversation c = new Conversation();
assertThat(c.getUpdatedAt()).isNull();
Method onUpdate = Conversation.class.getDeclaredMethod("onUpdate");
onUpdate.setAccessible(true);
LocalDateTime before = LocalDateTime.now().minusSeconds(1);
onUpdate.invoke(c);
LocalDateTime after = LocalDateTime.now().plusSeconds(1);
assertThat(c.getUpdatedAt()).isNotNull();
assertThat(c.getUpdatedAt()).isAfter(before);
assertThat(c.getUpdatedAt()).isBefore(after);
@DisplayName("estActive — ACTIVE → true")
void estActive_active() {
Conversation conv = buildMinimal();
conv.setStatut(StatutConversation.ACTIVE);
assertThat(conv.estActive()).isTrue();
}
@Test
@DisplayName("onUpdate appelé deux fois met à jour updatedAt à chaque fois")
void onUpdate_calledTwice_updatesEachTime() throws Exception {
Conversation c = new Conversation();
Method onUpdate = Conversation.class.getDeclaredMethod("onUpdate");
onUpdate.setAccessible(true);
onUpdate.invoke(c);
LocalDateTime first = c.getUpdatedAt();
// petit délai pour différencier les timestamps
Thread.sleep(5);
onUpdate.invoke(c);
LocalDateTime second = c.getUpdatedAt();
assertThat(second).isAfterOrEqualTo(first);
@DisplayName("estActive — ARCHIVEE → false")
void estActive_archivee() {
Conversation conv = buildMinimal();
conv.setStatut(StatutConversation.ARCHIVEE);
assertThat(conv.estActive()).isFalse();
}
@Test
@DisplayName("participants initialisé à liste vide")
@DisplayName("archiver — passe le statut à ARCHIVEE")
void archiver() {
Conversation conv = buildMinimal();
conv.setStatut(StatutConversation.ACTIVE);
conv.archiver();
assertThat(conv.getStatut()).isEqualTo(StatutConversation.ARCHIVEE);
assertThat(conv.estActive()).isFalse();
}
@Test
@DisplayName("enregistrerNouveauMessage — incrémente le compteur et met à jour la date")
void enregistrerNouveauMessage() {
Conversation conv = buildMinimal();
conv.setNombreMessages(3);
conv.enregistrerNouveauMessage();
assertThat(conv.getNombreMessages()).isEqualTo(4);
assertThat(conv.getDernierMessageAt()).isNotNull();
}
@Test
@DisplayName("enregistrerNouveauMessage — part de null")
void enregistrerNouveauMessage_partDeNull() {
Conversation conv = buildMinimal();
conv.setNombreMessages(null);
conv.enregistrerNouveauMessage();
assertThat(conv.getNombreMessages()).isEqualTo(1);
}
@Test
@DisplayName("onCreate — initialise statut et nombreMessages si null")
void onCreate_initDefaults() {
Conversation conv = new Conversation();
conv.setOrganisation(newOrg());
conv.setTypeConversation(TypeConversation.DIRECTE);
conv.onCreate();
assertThat(conv.getStatut()).isEqualTo(StatutConversation.ACTIVE);
assertThat(conv.getNombreMessages()).isEqualTo(0);
assertThat(conv.getDateCreation()).isNotNull();
assertThat(conv.getActif()).isTrue();
}
@Test
@DisplayName("ROLE_CANAL — roleCible renseigné")
void roleCanalType() {
Conversation conv = buildMinimal();
conv.setTypeConversation(TypeConversation.ROLE_CANAL);
conv.setRoleCible("TRESORIER");
conv.setTitre("Trésorier");
assertThat(conv.getTypeConversation()).isEqualTo(TypeConversation.ROLE_CANAL);
assertThat(conv.getRoleCible()).isEqualTo("TRESORIER");
}
@Test
@DisplayName("participants initialisé à liste vide (builder default)")
void participants_initializedEmpty() {
Conversation c = new Conversation();
assertThat(c.getParticipants()).isNotNull().isEmpty();
Conversation conv = Conversation.builder()
.organisation(newOrg())
.typeConversation(TypeConversation.DIRECTE)
.build();
assertThat(conv.getParticipants()).isNotNull().isEmpty();
assertThat(conv.getMessages()).isNotNull().isEmpty();
}
@Test
@DisplayName("messages initialisé à liste vide")
void messages_initializedEmpty() {
Conversation c = new Conversation();
assertThat(c.getMessages()).isNotNull().isEmpty();
@DisplayName("equals et hashCode")
void equalsHashCode() {
UUID id = UUID.randomUUID();
Organisation org = newOrg();
Conversation a = buildMinimal();
a.setId(id);
a.setOrganisation(org);
Conversation b = buildMinimal();
b.setId(id);
b.setOrganisation(org);
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
@DisplayName("isMuted et isPinned et isArchived défaut false")
void defaultFlags_areFalse() {
Conversation c = new Conversation();
assertThat(c.getIsMuted()).isFalse();
assertThat(c.getIsPinned()).isFalse();
assertThat(c.getIsArchived()).isFalse();
@DisplayName("toString non null")
void toString_nonNull() {
assertThat(buildMinimal().toString()).isNotNull().isNotEmpty();
}
private Conversation buildMinimal() {
Conversation conv = new Conversation();
conv.setOrganisation(newOrg());
conv.setTypeConversation(TypeConversation.DIRECTE);
conv.setStatut(StatutConversation.ACTIVE);
conv.setNombreMessages(0);
return conv;
}
}