Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,67 +1,35 @@
package com.codenames.codenames.backend.chat;

import com.codenames.codenames.backend.lobby.services.LobbyService;
import com.codenames.codenames.backend.utility.ChatMessageType;
import com.codenames.codenames.backend.utility.Role;
import com.codenames.codenames.backend.utility.Team;
import com.codenames.codenames.backend.websocket.SessionRegistry;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

/**
* Controller for broadcasting client messages to the desired destination with STOMP.
*
* <p>The destination is based on the lobbyID or team parameters passed when the method is invoked.
* The parameters are appended to the destination and broadcasted to all subscribers.
*/

@Controller
public class ChatController {

private final ChatService chatService;
private final LobbyService lobbyService;
private final SessionRegistry sessionRegistry;

/**
* Constructor for the ChatController.
*
* @param chatService the service used to validate and persist chat history
*/
public ChatController(
ChatService chatService, LobbyService lobbyService, SessionRegistry sessionRegistry) {
ChatService chatService, LobbyService lobbyService) {
this.chatService = chatService;
this.lobbyService = lobbyService;
this.sessionRegistry = sessionRegistry;
}

/**
* Verifies the username of the sender and checks if they are sending the message to their own
* lobby before allowing them to send a message.
*
* @param headerAccessor the header accessor containing the session information of the sender
* @param targetLobbyId the actual lobby ID associated with the sender's session, used to verify
* that they are sending the message to their own lobby
* @return the username if sender is verified, otherwise throws an exception
* @throws IllegalStateException if the username is null or if the sender is trying to send a
* message to a lobby they are not in
*/
private String getVerifiedUsername(
SimpMessageHeaderAccessor headerAccessor, String targetLobbyId) {
String sessionId = headerAccessor.getSessionId();
String realUsername = sessionRegistry.getUser(sessionId);
String realLobbyId = sessionRegistry.getLobby(sessionId);

if (realUsername == null) {
throw new IllegalStateException("Null username.");
}
if (targetLobbyId == null) {
throw new IllegalStateException("Null lobby ID.");
}
if (!targetLobbyId.equals(realLobbyId)) {
throw new IllegalStateException("Wrong lobby, user not in lobby " + realLobbyId);
}
return realUsername;
}

/**
Expand All @@ -72,18 +40,19 @@ private String getVerifiedUsername(
*/
@MessageMapping("/chat/{lobbyId}")
public void sendLobbyMessage(
@DestinationVariable String lobbyId,
@Payload ChatDto chatDto,
SimpMessageHeaderAccessor headerAccessor) {
String realUsername = getVerifiedUsername(headerAccessor, lobbyId);

// Create a new ChatDto with the verified username to prevent passing false username
ChatDto verifiedChatDto = new ChatDto(realUsername, chatDto.content(), chatDto.type());
@DestinationVariable String lobbyId,
@Payload ChatDto chatDto) {

ChatDto verifiedChatDto = new ChatDto(
chatDto.senderUsername(),
chatDto.content(),
ChatMessageType.CHAT
);
chatService.processMessage(lobbyId, "LOBBY", "", verifiedChatDto);
}

/**
* Verifies the sender's name, lobbyID, and team before sending a message to the entire team.
* Verifies the sender's team and delegates message processing to {@link ChatService}.
*
* @param lobbyId the ID of the lobby the client is in
* @param team the team the client is in (RED, BLUE)
Expand All @@ -93,25 +62,26 @@ public void sendLobbyMessage(
public void sendTeamMessage(
@DestinationVariable String lobbyId,
@DestinationVariable Team team,
@Payload ChatDto chatDto,
SimpMessageHeaderAccessor headerAccessor) {
String realUsername = getVerifiedUsername(headerAccessor, lobbyId);
@Payload ChatDto chatDto) {

Team playerTeam = lobbyService.getPlayerTeam(realUsername, lobbyId);
Team playerTeam = lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId);
if (playerTeam != team) {
throw new IllegalStateException("You are not on team " + team.name());
}

ChatDto verifiedDto = new ChatDto(realUsername, chatDto.content(), chatDto.type());
ChatDto verifiedDto = new ChatDto(
chatDto.senderUsername(),
chatDto.content(),
ChatMessageType.CHAT
);

String roomKey = "TEAM_" + team.name();
String topicSuffix = "/" + team.name();
chatService.processMessage(lobbyId, roomKey, topicSuffix, verifiedDto);
}

/**
* Verifies the sender's name, lobbyID, team and role sending a message to the operatives on the
* respective team.
* Verifies the sender's team and role and delegates message processing to {@link ChatService}.
*
* @param lobbyId the ID of the lobby the client is in
* @param team the team the client is in (RED, BLUE)
Expand All @@ -121,19 +91,21 @@ public void sendTeamMessage(
public void sendTeamOperativeMessage(
@DestinationVariable String lobbyId,
@DestinationVariable Team team,
@Payload ChatDto chatDto,
SimpMessageHeaderAccessor headerAccessor) {
String realUsername = getVerifiedUsername(headerAccessor, lobbyId);
@Payload ChatDto chatDto) {

Team playerTeam = lobbyService.getPlayerTeam(realUsername, lobbyId);
Role playerRole = lobbyService.getPlayerRole(realUsername, lobbyId);
Team playerTeam = lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId);
Role playerRole = lobbyService.getPlayerRole(chatDto.senderUsername(), lobbyId);

if (playerTeam != team || playerRole != Role.OPERATIVE) {
throw new IllegalStateException(
"You are either not an operative or are sending to the wrong team.");
}

ChatDto verifiedDto = new ChatDto(realUsername, chatDto.content(), chatDto.type());
ChatDto verifiedDto = new ChatDto(
chatDto.senderUsername(),
chatDto.content(),
ChatMessageType.CHAT
);

String roomKey = "OPERATIVE_" + team.name();
String topicSuffix = "/" + team.name() + "/operative";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void setUp() {
lobbyService = mock(LobbyService.class);
headerAccessor = mock(SimpMessageHeaderAccessor.class);

chatController = new ChatController(chatService, lobbyService, sessionRegistry);
chatController = new ChatController(chatService, lobbyService);

chatDto = new ChatDto("TestName", "TestMessage", ChatMessageType.CHAT);

Expand All @@ -51,46 +51,19 @@ void setUp() {

@Test
void testSendLobbyMessage_valid() {
chatController.sendLobbyMessage(lobbyId, chatDto, headerAccessor);
chatController.sendLobbyMessage(lobbyId, chatDto);

ChatDto verifiedChatDto = new ChatDto(realUsername, chatDto.content(), chatDto.type());
ChatDto verifiedChatDto = new ChatDto(chatDto.senderUsername(), chatDto.content(), chatDto.type());
verify(chatService, times(1)).processMessage(lobbyId, "LOBBY", "", verifiedChatDto);
}

@Test
void testSendLobbyMessage_nullUsername() {
when(sessionRegistry.getUser(sessionId)).thenReturn(null);

assertThrows(
IllegalStateException.class,
() -> chatController.sendLobbyMessage(lobbyId, chatDto, headerAccessor));
}

@Test
void testSendLobbyMessage_wrongLobby() {
when(sessionRegistry.getLobby(sessionId)).thenReturn("differentLobby");

assertThrows(
IllegalStateException.class,
() -> chatController.sendLobbyMessage(lobbyId, chatDto, headerAccessor));
}

@Test
void testSendLobbyMessage_nullLobby() {
when(sessionRegistry.getLobby(sessionId)).thenReturn("differentLobby");

assertThrows(
IllegalStateException.class,
() -> chatController.sendLobbyMessage(null, chatDto, headerAccessor));
}

@Test
void testSendTeamMessage_valid() {
when(lobbyService.getPlayerTeam(realUsername, lobbyId)).thenReturn(redTeam);
when(lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId)).thenReturn(redTeam);

chatController.sendTeamMessage(lobbyId, redTeam, chatDto, headerAccessor);
chatController.sendTeamMessage(lobbyId, redTeam, chatDto);

ChatDto verifiedChatDto = new ChatDto(realUsername, chatDto.content(), chatDto.type());
ChatDto verifiedChatDto = new ChatDto(chatDto.senderUsername(), chatDto.content(), chatDto.type());
verify(chatService, times(1)).processMessage(lobbyId, "TEAM_RED", "/RED", verifiedChatDto);
}

Expand All @@ -100,38 +73,48 @@ void testSendTeamMessage_sendToDifferentTeam_throwException() {

assertThrows(
IllegalStateException.class,
() -> chatController.sendTeamMessage(lobbyId, redTeam, chatDto, headerAccessor));
() -> chatController.sendTeamMessage(lobbyId, redTeam, chatDto));
}

@Test
void testSendTeamOperativeMessage_valid() {
when(lobbyService.getPlayerTeam(realUsername, lobbyId)).thenReturn(blueTeam);
when(lobbyService.getPlayerRole(realUsername, lobbyId)).thenReturn(Role.OPERATIVE);
when(lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId)).thenReturn(blueTeam);
when(lobbyService.getPlayerRole(chatDto.senderUsername(), lobbyId)).thenReturn(Role.OPERATIVE);

chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto, headerAccessor);
chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto);

ChatDto verifiedChatDto = new ChatDto(realUsername, chatDto.content(), chatDto.type());
ChatDto verifiedChatDto = new ChatDto(chatDto.senderUsername(), chatDto.content(), chatDto.type());
verify(chatService, times(1))
.processMessage(lobbyId, "OPERATIVE_BLUE", "/BLUE/operative", verifiedChatDto);
}

@Test
void testSendTeamOperativeMessage_wrongRoleCorrectTeam() {
when(lobbyService.getPlayerTeam(realUsername, lobbyId)).thenReturn(blueTeam);
when(lobbyService.getPlayerRole(realUsername, lobbyId)).thenReturn(Role.SPYMASTER);
when(lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId)).thenReturn(blueTeam);
when(lobbyService.getPlayerRole(chatDto.senderUsername(), lobbyId)).thenReturn(Role.SPYMASTER);

assertThrows(
IllegalStateException.class,
() -> chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto, headerAccessor));
() -> chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto));
}

@Test
void testSendTeamOperativeMessage_wrongTeamCorrectRole() {
when(lobbyService.getPlayerTeam(realUsername, lobbyId)).thenReturn(redTeam);
when(lobbyService.getPlayerRole(realUsername, lobbyId)).thenReturn(Role.OPERATIVE);
when(lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId)).thenReturn(redTeam);
when(lobbyService.getPlayerRole(chatDto.senderUsername(), lobbyId)).thenReturn(Role.OPERATIVE);

assertThrows(
IllegalStateException.class,
() -> chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto));
}

@Test
void testSendTeamOperativeMessage_wrongRoleWrongTeam() {
when(lobbyService.getPlayerTeam(chatDto.senderUsername(), lobbyId)).thenReturn(redTeam);
when(lobbyService.getPlayerRole(chatDto.senderUsername(), lobbyId)).thenReturn(Role.SPYMASTER);

assertThrows(
IllegalStateException.class,
() -> chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto, headerAccessor));
() -> chatController.sendTeamOperativeMessage(lobbyId, blueTeam, chatDto));
}
}
Loading