diff --git a/.gitignore b/.gitignore
index 7786d55..d6e09dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -120,3 +120,5 @@ uploads/
# Claude Code agent worktrees
.claude/
+du.exe.stackdump
+du.exe.stackdump
diff --git a/pom.xml b/pom.xml
index a6296cb..dc8f443 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
dev.lions.unionflow
unionflow-parent
- 1.0.4
+ 1.0.5
../unionflow-server-api/parent-pom.xml
@@ -47,7 +47,7 @@
dev.lions.unionflow
unionflow-server-api
- 1.0.4
+ 1.0.5
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 7212b0f..be32539 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -9,6 +9,19 @@ quarkus.application.version=1.0.0
# Backup configuration
unionflow.backup.directory=${BACKUP_DIR:/tmp/unionflow-backups}
+# Keycloak Admin API (pour la déconnexion globale des sessions)
+keycloak.admin.url=${KEYCLOAK_URL:http://localhost:8180}
+keycloak.admin.username=${KEYCLOAK_ADMIN_USERNAME:admin}
+keycloak.admin.password=${KEYCLOAK_ADMIN_PASSWORD:admin}
+keycloak.admin.realm=${KEYCLOAK_REALM:unionflow}
+
+# Vérification des mises à jour disponibles (propriété optionnelle — Optional côté Java)
+# Absent par défaut : le check distant est désactivé, la version courante est retournée honnêtement.
+# Pour activer : définir UNIONFLOW_UPDATES_CHECK_URL (convention MicroProfile env var) ou
+# ajouter la ligne suivante dans application-prod.properties :
+# unionflow.updates.check-url=https://releases.lions.dev/unionflow/latest.json
+# L'endpoint doit retourner un JSON {"version": "x.y.z"}
+
# Jackson — sérialisation des dates en ISO string (pas en tableau [year, month, day])
quarkus.jackson.write-dates-as-timestamps=false
quarkus.jackson.serialization-inclusion=non_null
@@ -172,3 +185,15 @@ mp.messaging.incoming.contributions-events-in.topic=unionflow.contributions.even
mp.messaging.incoming.contributions-events-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.contributions-events-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
mp.messaging.incoming.contributions-events-in.group.id=unionflow-websocket-server
+
+# Chat Messages — Messagerie instantanée
+mp.messaging.outgoing.chat-messages-out.connector=smallrye-kafka
+mp.messaging.outgoing.chat-messages-out.topic=unionflow.chat.messages
+mp.messaging.outgoing.chat-messages-out.value.serializer=org.apache.kafka.common.serialization.StringSerializer
+mp.messaging.outgoing.chat-messages-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
+
+mp.messaging.incoming.chat-messages-in.connector=smallrye-kafka
+mp.messaging.incoming.chat-messages-in.topic=unionflow.chat.messages
+mp.messaging.incoming.chat-messages-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
+mp.messaging.incoming.chat-messages-in.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
+mp.messaging.incoming.chat-messages-in.group.id=unionflow-websocket-server
diff --git a/src/test/java/dev/lions/unionflow/server/entity/EntityCoverageTest.java b/src/test/java/dev/lions/unionflow/server/entity/EntityCoverageTest.java
index 8f54e72..4472ab1 100644
--- a/src/test/java/dev/lions/unionflow/server/entity/EntityCoverageTest.java
+++ b/src/test/java/dev/lions/unionflow/server/entity/EntityCoverageTest.java
@@ -4,7 +4,8 @@ import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
-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.Nested;
import org.junit.jupiter.api.Test;
@@ -134,38 +135,85 @@ class EntityCoverageTest {
}
}
- // ─── Conversation — onUpdate ─────────────────────────────────────────────
+ // ─── Conversation v4 ────────────────────────────────────────────────────
@Nested
- @DisplayName("Conversation — onUpdate() non couvert")
+ @DisplayName("Conversation v4 — méthodes métier")
class ConversationCoverage {
@Test
- @DisplayName("Conversation getters/setters de base")
+ @DisplayName("Conversation getters/setters v4")
void gettersSetters() {
Conversation c = new Conversation();
- c.setName("Chat Test");
- c.setDescription("Description");
- c.setType(ConversationType.GROUP);
- c.setIsMuted(false);
- c.setIsPinned(true);
- c.setIsArchived(false);
- c.setUpdatedAt(LocalDateTime.now());
+ c.setTypeConversation(TypeConversation.DIRECTE);
+ c.setRoleCible("TRESORIER");
+ c.setTitre("Canal Trésorier");
+ c.setStatut(StatutConversation.ACTIVE);
+ c.setDernierMessageAt(LocalDateTime.now());
+ c.setNombreMessages(3);
- assertThat(c.getName()).isEqualTo("Chat Test");
- assertThat(c.getType()).isEqualTo(ConversationType.GROUP);
- assertThat(c.getIsPinned()).isTrue();
+ assertThat(c.getTypeConversation()).isEqualTo(TypeConversation.DIRECTE);
+ assertThat(c.getRoleCible()).isEqualTo("TRESORIER");
+ assertThat(c.getTitre()).isEqualTo("Canal Trésorier");
+ assertThat(c.getStatut()).isEqualTo(StatutConversation.ACTIVE);
+ assertThat(c.getNombreMessages()).isEqualTo(3);
}
@Test
- @DisplayName("onUpdate() met à jour updatedAt")
- void onUpdate_setsUpdatedAt() {
+ @DisplayName("estActive() — ACTIVE → true, ARCHIVEE → false")
+ void estActive() {
+ Conversation active = new Conversation();
+ active.setStatut(StatutConversation.ACTIVE);
+ assertThat(active.estActive()).isTrue();
+
+ Conversation archivee = new Conversation();
+ archivee.setStatut(StatutConversation.ARCHIVEE);
+ assertThat(archivee.estActive()).isFalse();
+ }
+
+ @Test
+ @DisplayName("archiver() — passe à ARCHIVEE")
+ void archiver() {
Conversation c = new Conversation();
- c.setName("Chat");
- c.setType(ConversationType.INDIVIDUAL);
- assertThat(c.getUpdatedAt()).isNull();
- c.onUpdate();
- assertThat(c.getUpdatedAt()).isNotNull();
+ c.setStatut(StatutConversation.ACTIVE);
+ c.archiver();
+ assertThat(c.getStatut()).isEqualTo(StatutConversation.ARCHIVEE);
+ }
+
+ @Test
+ @DisplayName("enregistrerNouveauMessage() — incrémente compteur et met à jour horodatage")
+ void enregistrerNouveauMessage() {
+ Conversation c = new Conversation();
+ c.setNombreMessages(2);
+ LocalDateTime avant = LocalDateTime.now().minusSeconds(1);
+
+ c.enregistrerNouveauMessage();
+
+ assertThat(c.getNombreMessages()).isEqualTo(3);
+ assertThat(c.getDernierMessageAt()).isNotNull().isAfter(avant);
+ }
+
+ @Test
+ @DisplayName("enregistrerNouveauMessage() — nombreMessages null → passe à 1")
+ void enregistrerNouveauMessage_nullCount() {
+ Conversation c = new Conversation();
+ c.setNombreMessages(null);
+ c.enregistrerNouveauMessage();
+ assertThat(c.getNombreMessages()).isEqualTo(1);
+ }
+
+ @Test
+ @DisplayName("onCreate() — statut null → initialisé à ACTIVE")
+ void onCreate_statut() {
+ Conversation c = new Conversation();
+ c.setStatut(null);
+ c.setTypeConversation(TypeConversation.ROLE_CANAL);
+ c.setId(java.util.UUID.randomUUID());
+ c.setDateCreation(LocalDateTime.now());
+ c.setActif(true);
+ c.setVersion(0L);
+ c.onCreate();
+ assertThat(c.getStatut()).isEqualTo(StatutConversation.ACTIVE);
}
}
diff --git a/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java b/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java
index 3ba7e12..c1d0221 100644
--- a/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java
+++ b/src/test/java/dev/lions/unionflow/server/service/MembreServiceTest.java
@@ -674,14 +674,19 @@ class MembreServiceTest {
doReturn(100L).when(membreRepository).count();
doReturn(80L).when(membreRepository).countActifs();
doReturn(10L).when(membreRepository).countNouveauxMembres(any());
+ doReturn(5L).when(organisationService).rechercherOrganisationsCount("");
Map stats = membreService.obtenirStatistiquesAvancees();
assertThat(stats).containsKey("totalMembres");
+ assertThat(stats).containsKey("total"); // alias mobile
+ assertThat(stats).containsKey("totalOrganisations");
assertThat(stats).containsKey("membresActifs");
assertThat(stats).containsKey("membresInactifs");
assertThat(stats).containsKey("tauxActivite");
assertThat(stats.get("totalMembres")).isEqualTo(100L);
+ assertThat(stats.get("total")).isEqualTo(100L);
+ assertThat(stats.get("totalOrganisations")).isEqualTo(5L);
assertThat(stats.get("membresActifs")).isEqualTo(80L);
assertThat(stats.get("membresInactifs")).isEqualTo(20L);
assertThat((Double) stats.get("tauxActivite")).isEqualTo(80.0);
@@ -693,10 +698,12 @@ class MembreServiceTest {
doReturn(0L).when(membreRepository).count();
doReturn(0L).when(membreRepository).countActifs();
doReturn(0L).when(membreRepository).countNouveauxMembres(any());
+ doReturn(0L).when(organisationService).rechercherOrganisationsCount("");
Map stats = membreService.obtenirStatistiquesAvancees();
assertThat((Double) stats.get("tauxActivite")).isEqualTo(0.0);
+ assertThat(stats.get("totalOrganisations")).isEqualTo(0L);
}
// =========================================================================