Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

테스트용 임시 PR #777

Open
wants to merge 12 commits into
base: develop-be
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

import com.ddang.ddang.chat.application.MessageService;
import com.ddang.ddang.chat.application.dto.CreateMessageDto;
import com.ddang.ddang.chat.application.dto.ReadMessageDto;
import com.ddang.ddang.chat.application.event.MessageNotificationEvent;
import com.ddang.ddang.chat.application.event.UpdateReadMessageLogEvent;
import com.ddang.ddang.chat.domain.Message;
import com.ddang.ddang.chat.domain.WebSocketChatSessions;
import com.ddang.ddang.chat.handler.dto.ChatMessageDataDto;
import com.ddang.ddang.chat.handler.dto.ChatPingDataDto;
import com.ddang.ddang.chat.handler.dto.HandleMessageResponse;
import com.ddang.ddang.chat.handler.dto.MessageDto;
import com.ddang.ddang.chat.handler.dto.SendMessageStatus;
import com.ddang.ddang.chat.presentation.dto.request.CreateMessageRequest;
import com.ddang.ddang.chat.presentation.dto.request.ReadMessageRequest;
import com.ddang.ddang.websocket.handler.WebSocketHandleTextMessageProvider;
import com.ddang.ddang.websocket.handler.dto.ChattingType;
import com.ddang.ddang.websocket.handler.dto.SendMessageDto;
import com.ddang.ddang.websocket.handler.dto.SessionAttributeDto;
import com.ddang.ddang.websocket.handler.dto.TextMessageType;
Expand All @@ -30,6 +36,9 @@
@RequiredArgsConstructor
public class ChatWebSocketHandleTextMessageProvider implements WebSocketHandleTextMessageProvider {

private static final String CHATROOM_ID_KEY = "chatRoomId";
private static final String CHATTTING_TYPE_KEY = "type";

private final WebSocketChatSessions sessions;
private final ObjectMapper objectMapper;
private final MessageService messageService;
Expand All @@ -47,15 +56,14 @@ public List<SendMessageDto> handleCreateSendMessage(
final Map<String, String> data
) throws JsonProcessingException {
final SessionAttributeDto sessionAttribute = getSessionAttributes(session);
final ChatMessageDataDto messageData = objectMapper.convertValue(data, ChatMessageDataDto.class);
sessions.add(session, messageData.chatRoomId());
final long chatRoomId = getChatRoomId(data);
sessions.add(session, chatRoomId);

final Long writerId = sessionAttribute.userId();
final CreateMessageDto createMessageDto = createMessageDto(messageData, writerId);
final Message message = messageService.create(createMessageDto);
sendNotificationIfReceiverNotInSession(message, sessionAttribute);

return createSendMessages(message, writerId, createMessageDto.chatRoomId());
final ChattingType type = ChattingType.findValue(data.get(CHATTTING_TYPE_KEY));
if (ChattingType.PING == type) {
return createPingResponse(sessionAttribute, data, session);
}
return createSendMessageResponse(data, sessionAttribute);
}

private SessionAttributeDto getSessionAttributes(final WebSocketSession session) {
Expand All @@ -64,11 +72,63 @@ private SessionAttributeDto getSessionAttributes(final WebSocketSession session)
return objectMapper.convertValue(attributes, SessionAttributeDto.class);
}

private CreateMessageDto createMessageDto(final ChatMessageDataDto messageData, final Long userId) {
final CreateMessageRequest request = new CreateMessageRequest(
messageData.receiverId(),
messageData.contents()
private long getChatRoomId(final Map<String, String> data) {
return Long.parseLong(data.get(CHATROOM_ID_KEY));
}

private List<SendMessageDto> createPingResponse(
final SessionAttributeDto sessionAttribute,
final Map<String, String> data,
final WebSocketSession userSession
) throws JsonProcessingException {
final ChatPingDataDto pingData = objectMapper.convertValue(data, ChatPingDataDto.class);
final ReadMessageRequest readMessageRequest = new ReadMessageRequest(
sessionAttribute.userId(),
pingData.chatRoomId(),
pingData.lastMessageId()
);
final List<ReadMessageDto> readMessageDtos = messageService.readAllByLastMessageId(readMessageRequest);

final List<MessageDto> messageDtos = convertToMessageDto(readMessageDtos, userSession);
final HandleMessageResponse handleMessageResponse = new HandleMessageResponse(
SendMessageStatus.SUCCESS,
messageDtos
);
return List.of(new SendMessageDto(
userSession,
new TextMessage(objectMapper.writeValueAsString(handleMessageResponse))
));
}

private List<MessageDto> convertToMessageDto(
final List<ReadMessageDto> readMessageDtos,
final WebSocketSession session
) {
return readMessageDtos.stream()
.map(readMessageDto -> MessageDto.of(readMessageDto,
isMyMessage(session, readMessageDto.writerId())
))
.toList();
}

private List<SendMessageDto> createSendMessageResponse(
final Map<String, String> data,
final SessionAttributeDto sessionAttribute
) throws JsonProcessingException {
final Long writerId = sessionAttribute.userId();
final ChatMessageDataDto messageData = objectMapper.convertValue(data, ChatMessageDataDto.class);
final CreateMessageDto createMessageDto = createMessageDto(messageData, writerId);
final Message message = messageService.create(createMessageDto);
sendNotificationIfReceiverNotInSession(message, sessionAttribute);

return createSendMessages(message, writerId, createMessageDto.chatRoomId());
}

private CreateMessageDto createMessageDto(
final ChatMessageDataDto messageData,
final Long userId
) {
final CreateMessageRequest request = new CreateMessageRequest(messageData.receiverId(), messageData.contents());

return CreateMessageDto.of(userId, messageData.chatRoomId(), request);
}
Expand All @@ -81,8 +141,8 @@ private void sendNotificationIfReceiverNotInSession(
final String profileImageAbsoluteUrl = String.valueOf(sessionAttribute.baseUrl());
messageNotificationEventPublisher.publishEvent(new MessageNotificationEvent(
message,
profileImageAbsoluteUrl
));
profileImageAbsoluteUrl)
);
}
}

Expand All @@ -95,29 +155,31 @@ private List<SendMessageDto> createSendMessages(

final List<SendMessageDto> sendMessageDtos = new ArrayList<>();
for (final WebSocketSession currentSession : groupSessions) {
final TextMessage textMessage = createTextMessage(message, writerId, currentSession);
final MessageDto messageDto = MessageDto.of(message, isMyMessage(currentSession, writerId));
final TextMessage textMessage = createTextMessage(messageDto);
sendMessageDtos.add(new SendMessageDto(currentSession, textMessage));
updateReadMessageLog(currentSession, chatRoomId, message);
}

return sendMessageDtos;
}

private TextMessage createTextMessage(
final Message message,
final Long writerId,
final WebSocketSession session
) throws JsonProcessingException {
final boolean isMyMessage = isMyMessage(session, writerId);
final MessageDto messageDto = MessageDto.of(message, isMyMessage);
private boolean isMyMessage(
final WebSocketSession session,
final Long writerId
) {
final long userId = Long.parseLong(String.valueOf(session.getAttributes().get("userId")));

return new TextMessage(objectMapper.writeValueAsString(messageDto));
return writerId.equals(userId);
}

private boolean isMyMessage(final WebSocketSession session, final Long writerId) {
final long userId = Long.parseLong(String.valueOf(session.getAttributes().get("userId")));
private TextMessage createTextMessage(final MessageDto messageDto) throws JsonProcessingException {
final HandleMessageResponse handleMessageResponse = new HandleMessageResponse(
SendMessageStatus.SUCCESS,
List.of(messageDto)
);

return writerId.equals(userId);
return new TextMessage(objectMapper.writeValueAsString(handleMessageResponse));
}

private void updateReadMessageLog(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.ddang.ddang.chat.handler.dto;

public record ChatPingDataDto(Long chatRoomId, Long lastMessageId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.ddang.ddang.chat.handler.dto;

import java.util.List;

public record HandleMessageResponse(SendMessageStatus status, List<MessageDto> messages) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ddang.ddang.chat.handler.dto;

import com.ddang.ddang.chat.application.dto.ReadMessageDto;
import com.ddang.ddang.chat.domain.Message;
import com.fasterxml.jackson.annotation.JsonFormat;

Expand All @@ -24,4 +25,13 @@ public static MessageDto of(final Message message, final boolean isMyMessage) {
message.getContents()
);
}

public static MessageDto of(final ReadMessageDto readMessageDto, final boolean isMyMessage) {
return new MessageDto(
readMessageDto.id(),
readMessageDto.createdTime(),
isMyMessage,
readMessageDto.contents()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ddang.ddang.chat.handler.dto;

public enum SendMessageStatus {

SUCCESS,
DISCONNECTED,
FORBIDDEN
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import com.ddang.ddang.review.application.exception.ReviewNotFoundException;
import com.ddang.ddang.user.application.exception.AlreadyExistsNameException;
import com.ddang.ddang.user.application.exception.UserNotFoundException;
import com.ddang.ddang.websocket.handler.exception.UnsupportedChattingTypeException;
import com.ddang.ddang.websocket.handler.exception.UnsupportedTextMessageTypeException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
Expand Down Expand Up @@ -436,6 +438,26 @@ public ResponseEntity<ExceptionResponse> handleWithdrawalNotAllowedException(fin
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(UnsupportedTextMessageTypeException.class)
public ResponseEntity<ExceptionResponse> handleUnsupportedTextMessageTypeException(
final UnsupportedTextMessageTypeException ex
) {
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
}

@ExceptionHandler(UnsupportedChattingTypeException.class)
public ResponseEntity<ExceptionResponse> handleUnsupportedChattingTypeException(
final UnsupportedChattingTypeException ex
) {
logger.warn(String.format(LOG_MESSAGE_FORMAT, ex.getClass().getSimpleName(), ex.getMessage()));

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ExceptionResponse(ex.getMessage()));
}

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
final MethodArgumentNotValidException ex,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ddang.ddang.websocket.handler.dto;

import com.ddang.ddang.websocket.handler.exception.UnsupportedChattingTypeException;

import java.util.Arrays;

public enum ChattingType {

MESSAGE,
PING;

public static ChattingType findValue(final String value) {
return Arrays.stream(ChattingType.values())
.filter(chattingType -> chattingType.name().equalsIgnoreCase(value))
.findFirst()
.orElseThrow(() -> new UnsupportedChattingTypeException("잘못된 채팅 타입입니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
package com.ddang.ddang.websocket.handler.dto;

import com.ddang.ddang.websocket.handler.exception.UnsupportedTextMessageTypeException;
import com.fasterxml.jackson.annotation.JsonCreator;

import java.util.Arrays;

public enum TextMessageType {

CHATTINGS,
BIDS
BIDS;

@JsonCreator
public static TextMessageType fromString(final String value) {
return Arrays.stream(TextMessageType.values())
.filter(messageType -> messageType.name().equalsIgnoreCase(value))
.findFirst()
.orElseThrow(() -> new UnsupportedTextMessageTypeException("잘못된 메시지 타입입니다."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ddang.ddang.websocket.handler.exception;

public class UnsupportedChattingTypeException extends IllegalStateException {
public UnsupportedChattingTypeException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ddang.ddang.chat.handler;

import com.ddang.ddang.chat.application.event.CreateReadMessageLogEvent;
import com.ddang.ddang.chat.application.event.MessageNotificationEvent;
import com.ddang.ddang.chat.application.event.UpdateReadMessageLogEvent;
import com.ddang.ddang.chat.domain.WebSocketChatSessions;
Expand Down Expand Up @@ -28,6 +29,7 @@
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
Expand All @@ -39,6 +41,9 @@
@SuppressWarnings("NonAsciiCharacters")
class ChatWebSocketHandleTextMessageProviderTest extends ChatWebSocketHandleTextMessageProviderTestFixture {

@Autowired
ApplicationEvents applicationEvents;

@Autowired
ChatWebSocketHandleTextMessageProvider provider;

Expand All @@ -62,6 +67,8 @@ class ChatWebSocketHandleTextMessageProviderTest extends ChatWebSocketHandleText

@Autowired
ObjectMapper objectMapper;
@Autowired
private ChatWebSocketHandleTextMessageProvider chatWebSocketHandleTextMessageProvider;

@Test
void 지원하는_웹소켓_핸들링_타입을_반환한다() {
Expand Down Expand Up @@ -173,6 +180,51 @@ class ChatWebSocketHandleTextMessageProviderTest extends ChatWebSocketHandleText
assertThat(actual).hasSize(1);
}

@Test
void 메시지_전송시_알림_전송_이벤트를_호출한다() throws Exception {
// given
given(writerSession.getAttributes()).willReturn(발신자_세션_attribute_정보);
willDoNothing().given(sessions).add(writerSession, 채팅방.getId());
willReturn(false).given(sessions).containsByUserId(채팅방.getId(), 수신자.getId());
willReturn(Set.of(writerSession)).given(sessions).getSessionsByChatRoomId(채팅방.getId());

// when
provider.handleCreateSendMessage(writerSession, 메시지_전송_데이터);
final long actual = events.stream(MessageNotificationEvent.class).count();

// then
assertThat(actual).isEqualTo(1);
}

@Test
void 메시지_전송시_메시지_로그_업데이트_이벤트를_호출한다() throws Exception {
// given
given(writerSession.getAttributes()).willReturn(발신자_세션_attribute_정보);
willDoNothing().given(sessions).add(writerSession, 채팅방.getId());
willReturn(false).given(sessions).containsByUserId(채팅방.getId(), 수신자.getId());
willReturn(Set.of(writerSession)).given(sessions).getSessionsByChatRoomId(채팅방.getId());

// when
provider.handleCreateSendMessage(writerSession, 메시지_전송_데이터);
final long actual = events.stream(UpdateReadMessageLogEvent.class).count();

// then
assertThat(actual).isEqualTo(1);
}

@Test
void 잘못된_데이터_타입_전달시_예외가_발생한다() throws JsonProcessingException {
// given
given(writerSession.getAttributes()).willReturn(발신자_세션_attribute_정보);
willDoNothing().given(sessions).add(writerSession, 채팅방.getId());
willReturn(false).given(sessions).containsByUserId(채팅방.getId(), 수신자.getId());
willReturn(Set.of(writerSession)).given(sessions).getSessionsByChatRoomId(채팅방.getId());

// when
assertThatThrownBy(() -> provider.handleCreateSendMessage(writerSession, 잘못된_메시지_전송_데이터))
.isInstanceOf(IllegalArgumentException.class);
}

@Test
void 세션을_삭제한다() {
// given
Expand Down
Loading
Loading