From 3100ac21c3081da9d97fa31461a33989a74d87d2 Mon Sep 17 00:00:00 2001 From: akastijn Date: Sat, 1 Aug 2026 18:26:30 +0200 Subject: [PATCH] Add ChatService test suite to verify message filtering and delivery to appropriate users. --- .../services/chat/ChatService.java | 64 +++++-- .../services/chat/ChatServiceTest.java | 157 ++++++++++++++++++ 2 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java diff --git a/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java b/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java index a1ff43e..51a397b 100644 --- a/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java +++ b/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java @@ -7,6 +7,7 @@ import com.alttd.altitudeweb.database.Databases; import com.alttd.altitudeweb.database.chat.ChatLogMapper; import com.alttd.altitudeweb.model.PermissionClaimDto; import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher; +import com.alttd.altitudeweb.services.chat.event_publisher.EventUser; import com.alttd.altitudeweb.setup.Connection; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -83,23 +84,56 @@ public class ChatService { } private void sendMessagesToListeners(List chatMessageList) { - String jsonMessageListIncludingBlocked = chatMessageList.stream() - .map(ChatMessageMapper::toJson) - .collect(Collectors.joining(",", "[", "]")); - String jsonMessageList = chatMessageList.stream() - .filter(ChatMessage::isNotBlocked) - .filter(chatMessage -> Arrays.asList(allowedServers).contains(chatMessage.getServer())) - //TODO [Stijn] [2026-07-19]: Handle different channel types - .filter(chatMessage -> chatMessage.getType() == ChatMessageType.PUBLIC) - .map(ChatMessageMapper::toJson) - .collect(Collectors.joining(",", "[", "]")); + Map jsonCache = new HashMap<>(); + for (ChatMessage message : chatMessageList) { + jsonCache.put(message, ChatMessageMapper.toJson(message)); + } - eventPublisher.sendToUsers("chat", (eventUser) -> { - if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) { - return jsonMessageListIncludingBlocked; + eventPublisher.sendToUsers("chat", (eventUser) -> + chatMessageList.stream() + .filter(chatMessage -> shouldReceive(eventUser, chatMessage)) + .map(jsonCache::get) + .collect(Collectors.joining(",", "[", "]")) + ); + } + + private boolean shouldReceive(EventUser eventUser, ChatMessage chatMessage) { + if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) { + return true; + } + + if (!Arrays.asList(allowedServers).contains(chatMessage.getServer())) { + return false; + } + + if (!chatMessage.isNotBlocked()) { + return false; + } + + return switch (chatMessage.getType()) { + case PUBLIC, GLOBAL -> true; + case PARTY -> { + // TODO [Stijn] [2026-07-19]: Check if user is in party + // if (isInParty(eventUser, chatMessage.getChannel())) return true; + yield false; } - return jsonMessageList; - }); + case MSG -> { + if (eventUser.uuid().equals(chatMessage.getUuid())) { + yield true; + } + if (chatMessage.getReceiver() != null) { + try { + UUID receiverUuid = UUID.fromString(chatMessage.getReceiver()); + yield eventUser.uuid().equals(receiverUuid); + } catch (IllegalArgumentException e) { + yield false; + } + } + yield false; + } + case GAC -> eventUser.hasPermission(PermissionClaimDto.MOD); + case CUSTOM -> false; + }; } public List getMessagesSince(Instant instant) { diff --git a/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java b/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java new file mode 100644 index 0000000..35eb5d5 --- /dev/null +++ b/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java @@ -0,0 +1,157 @@ +package com.alttd.altitudeweb.services.chat; + +import com.alttd.altitudeweb.controllers.chat.ChatMessage; +import com.alttd.altitudeweb.controllers.chat.ChatMessageType; +import com.alttd.altitudeweb.model.PermissionClaimDto; +import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher; +import com.alttd.altitudeweb.services.chat.event_publisher.EventUser; +import com.alttd.altitudeweb.services.chat.event_publisher.MessageForUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class ChatServiceTest { + + private ChatService chatService; + private EventPublisher eventPublisher; + + @BeforeEach + void setUp() { + eventPublisher = mock(EventPublisher.class); + chatService = new ChatService(eventPublisher); + ReflectionTestUtils.setField(chatService, "allowedServers", new String[]{"server1"}); + } + + @Test + void testPublicMessageSentToEveryone() { + ChatMessage message = ChatMessage.builder() + .uuid(UUID.randomUUID()) + .type(ChatMessageType.PUBLIC) + .server("server1") + .messageJson("hello") + .notBlocked(true) + .timestamp(Instant.now()) + .build(); + + chatService.addChatMessage(List.of(message)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageForUser.class); + verify(eventPublisher).sendToUsers(anyString(), captor.capture()); + + MessageForUser messageForUser = captor.getValue(); + EventUser user = new EventUser(UUID.randomUUID(), List.of()); + String result = messageForUser.get(user); + + assertTrue(result.contains("hello")); + } + + @Test + void testGacMessageSentOnlyToStaff() { + ChatMessage message = ChatMessage.builder() + .uuid(UUID.randomUUID()) + .type(ChatMessageType.GAC) + .server("server1") + .messageJson("staff chat") + .notBlocked(true) + .timestamp(Instant.now()) + .build(); + + chatService.addChatMessage(List.of(message)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageForUser.class); + verify(eventPublisher).sendToUsers(anyString(), captor.capture()); + + MessageForUser messageForUser = captor.getValue(); + + EventUser regularUser = new EventUser(UUID.randomUUID(), List.of()); + assertFalse(messageForUser.get(regularUser).contains("staff chat"), "Regular user should not see GAC message"); + + EventUser modUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.MOD.getValue())); + assertTrue(messageForUser.get(modUser).contains("staff chat"), "MOD should see GAC message"); + } + + @Test + void testMsgMessageSentToSenderAndReceiver() { + UUID senderUuid = UUID.randomUUID(); + UUID receiverUuid = UUID.randomUUID(); + ChatMessage message = ChatMessage.builder() + .uuid(senderUuid) + .type(ChatMessageType.MSG) + .server("server1") + .receiver(receiverUuid.toString()) + .messageJson("private message") + .notBlocked(true) + .timestamp(Instant.now()) + .build(); + + chatService.addChatMessage(List.of(message)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageForUser.class); + verify(eventPublisher).sendToUsers(anyString(), captor.capture()); + + MessageForUser messageForUser = captor.getValue(); + + EventUser sender = new EventUser(senderUuid, List.of()); + EventUser receiver = new EventUser(receiverUuid, List.of()); + EventUser other = new EventUser(UUID.randomUUID(), List.of()); + + assertTrue(messageForUser.get(sender).contains("private message"), "Sender should see their own message"); + assertTrue(messageForUser.get(receiver).contains("private message"), "Receiver should see the message"); + assertFalse(messageForUser.get(other).contains("private message"), "Others should not see private message"); + } + + @Test + void testGlobalMessageSentToEveryone() { + ChatMessage message = ChatMessage.builder() + .uuid(UUID.randomUUID()) + .type(ChatMessageType.GLOBAL) + .server("server1") + .messageJson("global hello") + .notBlocked(true) + .timestamp(Instant.now()) + .build(); + + chatService.addChatMessage(List.of(message)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageForUser.class); + verify(eventPublisher).sendToUsers(anyString(), captor.capture()); + + MessageForUser messageForUser = captor.getValue(); + EventUser user = new EventUser(UUID.randomUUID(), List.of()); + assertTrue(messageForUser.get(user).contains("global hello")); + } + + @Test + void testBlockedMessageOnlySentToHeadMod() { + ChatMessage message = ChatMessage.builder() + .uuid(UUID.randomUUID()) + .type(ChatMessageType.PUBLIC) + .server("server1") + .messageJson("blocked message") + .notBlocked(false) + .timestamp(Instant.now()) + .build(); + + chatService.addChatMessage(List.of(message)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageForUser.class); + verify(eventPublisher).sendToUsers(anyString(), captor.capture()); + + MessageForUser messageForUser = captor.getValue(); + EventUser regularUser = new EventUser(UUID.randomUUID(), List.of()); + EventUser headModUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.HEAD_MOD.getValue())); + + assertFalse(messageForUser.get(regularUser).contains("blocked message")); + assertTrue(messageForUser.get(headModUser).contains("blocked message")); + } +}