- 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:
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user