fix: 3 tests
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 2m46s

- DashboardWebSocketEndpoint: null-check on onMessage to avoid NPE when client sends null payload
- ConversationParticipantTest.equalsHashCode: share Conversation+Membre instances (random UUIDs inside newConversation/newMembre were defeating equals)
- MessageTest.equalsHashCode: same fix pattern
This commit is contained in:
2026-04-23 13:01:50 +00:00
parent e503c6c0a1
commit 34056b7c03
3 changed files with 77 additions and 47 deletions

View File

@@ -1,43 +1,47 @@
package dev.lions.unionflow.server.resource; package dev.lions.unionflow.server.resource;
import io.quarkus.websockets.next.OnClose; import io.quarkus.websockets.next.OnClose;
import io.quarkus.websockets.next.OnOpen; import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage; import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket; import io.quarkus.websockets.next.WebSocket;
import io.quarkus.websockets.next.WebSocketConnection; import io.quarkus.websockets.next.WebSocketConnection;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
/** /**
* Endpoint WebSocket pour le dashboard temps réel. * Endpoint WebSocket pour le dashboard temps réel.
* Les clients mobiles et web se connectent ici pour recevoir les mises à jour. * Les clients mobiles et web se connectent ici pour recevoir les mises à jour.
* Types de messages supportés : stats_update, new_activity, event_update, notification, pong * Types de messages supportés : stats_update, new_activity, event_update, notification, pong
*/ */
@WebSocket(path = "/ws/dashboard") @WebSocket(path = "/ws/dashboard")
public class DashboardWebSocketEndpoint { public class DashboardWebSocketEndpoint {
private static final Logger LOG = Logger.getLogger(DashboardWebSocketEndpoint.class); private static final Logger LOG = Logger.getLogger(DashboardWebSocketEndpoint.class);
@OnOpen @OnOpen
public String onOpen(WebSocketConnection connection) { public String onOpen(WebSocketConnection connection) {
LOG.infof("WebSocket connection opened: %s", connection.id()); LOG.infof("WebSocket connection opened: %s", connection.id());
return "{\"type\":\"connected\",\"data\":{\"message\":\"Connected to UnionFlow Dashboard WebSocket\"}}"; return "{\"type\":\"connected\",\"data\":{\"message\":\"Connected to UnionFlow Dashboard WebSocket\"}}";
} }
@OnTextMessage @OnTextMessage
public String onMessage(String message, WebSocketConnection connection) { public String onMessage(String message, WebSocketConnection connection) {
LOG.debugf("WebSocket message received from %s: %s", connection.id(), message); LOG.debugf("WebSocket message received from %s: %s", connection.id(), message);
// Répondre aux pings avec un pong (heartbeat) if (message == null) {
if ("ping".equalsIgnoreCase(message.trim()) || message.contains("\"type\":\"ping\"")) { return "{\"type\":\"ack\",\"data\":{\"received\":true}}";
return "{\"type\":\"pong\",\"data\":{\"timestamp\":" + System.currentTimeMillis() + "}}"; }
}
// Répondre aux pings avec un pong (heartbeat)
// Accusé de réception pour les autres messages if ("ping".equalsIgnoreCase(message.trim()) || message.contains("\"type\":\"ping\"")) {
return "{\"type\":\"ack\",\"data\":{\"received\":true}}"; return "{\"type\":\"pong\",\"data\":{\"timestamp\":" + System.currentTimeMillis() + "}}";
} }
@OnClose // Accusé de réception pour les autres messages
public void onClose(WebSocketConnection connection) { return "{\"type\":\"ack\",\"data\":{\"received\":true}}";
LOG.infof("WebSocket connection closed: %s", connection.id()); }
}
} @OnClose
public void onClose(WebSocketConnection connection) {
LOG.infof("WebSocket connection closed: %s", connection.id());
}
}

View File

@@ -103,10 +103,23 @@ class ConversationParticipantTest {
@DisplayName("equals et hashCode") @DisplayName("equals et hashCode")
void equalsHashCode() { void equalsHashCode() {
UUID id = UUID.randomUUID(); UUID id = UUID.randomUUID();
ConversationParticipant a = buildMinimal("PARTICIPANT"); // Partage les mêmes Conversation et Membre pour que Lombok equals
// sur les champs imbriqués donne true (les IDs imbriqués sont aléatoires
// dans newConversation/newMembre, donc il faut réutiliser les instances).
Conversation sharedConv = newConversation();
Membre sharedMembre = newMembre();
ConversationParticipant a = new ConversationParticipant();
a.setId(id); a.setId(id);
ConversationParticipant b = buildMinimal("PARTICIPANT"); a.setConversation(sharedConv);
a.setMembre(sharedMembre);
a.setRoleDansConversation("PARTICIPANT");
a.setNotifier(true);
ConversationParticipant b = new ConversationParticipant();
b.setId(id); b.setId(id);
b.setConversation(sharedConv);
b.setMembre(sharedMembre);
b.setRoleDansConversation("PARTICIPANT");
b.setNotifier(true);
assertThat(a).isEqualTo(b); assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode()); assertThat(a.hashCode()).isEqualTo(b.hashCode());
} }

View File

@@ -143,10 +143,23 @@ class MessageTest {
@DisplayName("equals et hashCode") @DisplayName("equals et hashCode")
void equalsHashCode() { void equalsHashCode() {
UUID id = UUID.randomUUID(); UUID id = UUID.randomUUID();
Message a = buildMinimal(TypeContenu.TEXTE); // Partage Conversation et Membre pour que Lombok equals
// (récursif sur les champs) donne true — sinon les UUIDs aléatoires
// dans newConversation/newMembre cassent l'égalité.
Conversation sharedConv = newConversation();
Membre sharedExpediteur = newMembre();
Message a = new Message();
a.setId(id); a.setId(id);
Message b = buildMinimal(TypeContenu.TEXTE); a.setConversation(sharedConv);
a.setExpediteur(sharedExpediteur);
a.setTypeMessage(TypeContenu.TEXTE);
a.setContenu("Texte test");
Message b = new Message();
b.setId(id); b.setId(id);
b.setConversation(sharedConv);
b.setExpediteur(sharedExpediteur);
b.setTypeMessage(TypeContenu.TEXTE);
b.setContenu("Texte test");
assertThat(a).isEqualTo(b); assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode()); assertThat(a.hashCode()).isEqualTo(b.hashCode());
} }