diff --git a/src/main/java/coffeemeet/server/auth/exception/AuthErrorCode.java b/src/main/java/coffeemeet/server/auth/exception/AuthErrorCode.java index bc43665e..3eec1f64 100644 --- a/src/main/java/coffeemeet/server/auth/exception/AuthErrorCode.java +++ b/src/main/java/coffeemeet/server/auth/exception/AuthErrorCode.java @@ -10,6 +10,7 @@ public enum AuthErrorCode implements ErrorCode { INVALID_LOGIN_TYPE("A000", "지원하지 않는 로그인 타입입니다."), AUTHENTICATION_FAILED("A001", "인증이 실패했습니다."), AUTHORIZATION_FAILED("A003", "인가에 실패했습니다."), + HEADER_NOT_FOUND("A004", "헤더에 인증 코드가 없습니다."), ALREADY_REGISTERED("A009", "이미 가입된 사용자입니다."), DELETED_USER("A010", "탈퇴한 지 30일이 지난 사용자입니다. 다시 회원가입해 주세요."); diff --git a/src/main/java/coffeemeet/server/chatting/current/domain/ChattingMessage.java b/src/main/java/coffeemeet/server/chatting/current/domain/ChattingMessage.java index 85a7e562..5f3fb519 100644 --- a/src/main/java/coffeemeet/server/chatting/current/domain/ChattingMessage.java +++ b/src/main/java/coffeemeet/server/chatting/current/domain/ChattingMessage.java @@ -4,6 +4,7 @@ import coffeemeet.server.common.domain.BaseEntity; import coffeemeet.server.common.execption.InvalidInputException; +import coffeemeet.server.user.domain.User; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -38,10 +39,16 @@ public class ChattingMessage extends BaseEntity { @JoinColumn(name = "chatting_room_id", nullable = false) private ChattingRoom chattingRoom; - public ChattingMessage(@NonNull String message, @NonNull ChattingRoom chattingRoom) { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + public ChattingMessage(@NonNull String message, @NonNull ChattingRoom chattingRoom, + @NonNull User user) { validateMessage(message); this.message = message; this.chattingRoom = chattingRoom; + this.user = user; } private void validateMessage(String message) { diff --git a/src/main/java/coffeemeet/server/chatting/current/domain/ChattingRoom.java b/src/main/java/coffeemeet/server/chatting/current/domain/ChattingRoom.java index 2fbdff1b..c007c4f2 100644 --- a/src/main/java/coffeemeet/server/chatting/current/domain/ChattingRoom.java +++ b/src/main/java/coffeemeet/server/chatting/current/domain/ChattingRoom.java @@ -6,14 +6,13 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter @Table(name = "chatting_rooms") -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor public class ChattingRoom extends BaseEntity { @Id diff --git a/src/main/java/coffeemeet/server/chatting/current/implement/ChattingMessageCommand.java b/src/main/java/coffeemeet/server/chatting/current/implement/ChattingMessageCommand.java new file mode 100644 index 00000000..5c116e54 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/implement/ChattingMessageCommand.java @@ -0,0 +1,22 @@ +package coffeemeet.server.chatting.current.implement; + +import coffeemeet.server.chatting.current.domain.ChattingMessage; +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.infrastructure.ChattingMessageRepository; +import coffeemeet.server.user.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional +@RequiredArgsConstructor +public class ChattingMessageCommand { + + private final ChattingMessageRepository chattingMessageRepository; + + public ChattingMessage saveChattingMessage(String content, ChattingRoom chattingRoom, User user) { + return chattingMessageRepository.save(new ChattingMessage(content, chattingRoom, user)); + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/implement/ChattingRoomCommand.java b/src/main/java/coffeemeet/server/chatting/current/implement/ChattingRoomCommand.java new file mode 100644 index 00000000..fc870e60 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/implement/ChattingRoomCommand.java @@ -0,0 +1,20 @@ +package coffeemeet.server.chatting.current.implement; + +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.infrastructure.ChattingRoomRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional +@RequiredArgsConstructor +public class ChattingRoomCommand { + + private final ChattingRoomRepository chattingRoomRepository; + + public ChattingRoom saveChattingRoom() { + return chattingRoomRepository.save(new ChattingRoom()); + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/implement/ChattingRoomQuery.java b/src/main/java/coffeemeet/server/chatting/current/implement/ChattingRoomQuery.java new file mode 100644 index 00000000..05702b43 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/implement/ChattingRoomQuery.java @@ -0,0 +1,29 @@ +package coffeemeet.server.chatting.current.implement; + +import static coffeemeet.server.chatting.exception.ChattingErrorCode.CHATTING_ROOM_NOT_FOUND; + +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.infrastructure.ChattingRoomRepository; +import coffeemeet.server.common.execption.InvalidInputException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ChattingRoomQuery { + + private static final String CHATTING_ROOM_NOT_FOUND_MESSAGE = "(%s)번 채팅방을 찾을 수 없습니다."; + + private final ChattingRoomRepository chattingRoomRepository; + + public ChattingRoom getChattingRoomById(Long roomId) { + return chattingRoomRepository.findById(roomId) + .orElseThrow(() -> new InvalidInputException( + CHATTING_ROOM_NOT_FOUND, + String.format(CHATTING_ROOM_NOT_FOUND_MESSAGE, roomId) + )); + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/infrastructure/ChattingMessageRepository.java b/src/main/java/coffeemeet/server/chatting/current/infrastructure/ChattingMessageRepository.java new file mode 100644 index 00000000..2445cbbb --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/infrastructure/ChattingMessageRepository.java @@ -0,0 +1,8 @@ +package coffeemeet.server.chatting.current.infrastructure; + +import coffeemeet.server.chatting.current.domain.ChattingMessage; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChattingMessageRepository extends JpaRepository { + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/infrastructure/ChattingRoomRepository.java b/src/main/java/coffeemeet/server/chatting/current/infrastructure/ChattingRoomRepository.java new file mode 100644 index 00000000..15e52016 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/infrastructure/ChattingRoomRepository.java @@ -0,0 +1,8 @@ +package coffeemeet.server.chatting.current.infrastructure; + +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChattingRoomRepository extends JpaRepository { + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/presentation/ChattingMessageController.java b/src/main/java/coffeemeet/server/chatting/current/presentation/ChattingMessageController.java new file mode 100644 index 00000000..562f65c5 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/presentation/ChattingMessageController.java @@ -0,0 +1,46 @@ +package coffeemeet.server.chatting.current.presentation; + +import coffeemeet.server.chatting.current.presentation.dto.ChatStomp; +import coffeemeet.server.chatting.current.service.ChattingMessageService; +import jakarta.validation.Valid; +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.socket.messaging.SessionConnectEvent; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +@RestController +@RequiredArgsConstructor +public class ChattingMessageController { + + private final SimpMessageSendingOperations simpMessageSendingOperations; + private final ChattingMessageService chattingMessageService; + private final Map sessions = new HashMap<>(); + + @EventListener(SessionConnectEvent.class) + public void onConnect(SessionConnectEvent event) { + String sessionId = String.valueOf(event.getMessage().getHeaders().get("simpSessionId")); + String userId = String.valueOf(event.getMessage().getHeaders().get("nativeHeaders")) + .split("userId=\\[")[1].split("]")[0]; + sessions.put(sessionId, Long.valueOf(userId)); + } + + @EventListener(SessionDisconnectEvent.class) + public void onDisconnect(SessionDisconnectEvent event) { + sessions.remove(event.getSessionId()); + } + + @MessageMapping("/chatting/messages") + public void message(@Valid ChatStomp.Request request, SimpMessageHeaderAccessor accessor) { + Long userId = sessions.get(accessor.getSessionId()); + chattingMessageService.createChattingMessage(request.roomId(), request.content(), userId); + simpMessageSendingOperations.convertAndSend("/sub/chatting/room/" + request.roomId(), + request.content()); + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/presentation/dto/ChatStomp.java b/src/main/java/coffeemeet/server/chatting/current/presentation/dto/ChatStomp.java new file mode 100644 index 00000000..6e432dce --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/presentation/dto/ChatStomp.java @@ -0,0 +1,17 @@ +package coffeemeet.server.chatting.current.presentation.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public sealed interface ChatStomp permits ChatStomp.Request { + + record Request( + @NotNull + Long roomId, + @NotBlank + String content + ) implements ChatStomp { + + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/service/ChattingMessageService.java b/src/main/java/coffeemeet/server/chatting/current/service/ChattingMessageService.java new file mode 100644 index 00000000..7f4de1d9 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/service/ChattingMessageService.java @@ -0,0 +1,25 @@ +package coffeemeet.server.chatting.current.service; + +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.implement.ChattingMessageCommand; +import coffeemeet.server.chatting.current.implement.ChattingRoomQuery; +import coffeemeet.server.user.domain.User; +import coffeemeet.server.user.implement.UserQuery; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChattingMessageService { + + private final ChattingMessageCommand chattingMessageCommand; + private final ChattingRoomQuery chattingRoomQuery; + private final UserQuery userQuery; + + public void createChattingMessage(Long roomId, String content, Long userId) { + User user = userQuery.getUserById(userId); + ChattingRoom chattingRoom = chattingRoomQuery.getChattingRoomById(roomId); + chattingMessageCommand.saveChattingMessage(content, chattingRoom, user); + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/current/service/ChattingRoomService.java b/src/main/java/coffeemeet/server/chatting/current/service/ChattingRoomService.java new file mode 100644 index 00000000..91cc7fa7 --- /dev/null +++ b/src/main/java/coffeemeet/server/chatting/current/service/ChattingRoomService.java @@ -0,0 +1,17 @@ +package coffeemeet.server.chatting.current.service; + +import coffeemeet.server.chatting.current.implement.ChattingRoomCommand; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChattingRoomService { + + private final ChattingRoomCommand chattingRoomCommand; + + public void createChattingRoom() { + chattingRoomCommand.saveChattingRoom(); + } + +} diff --git a/src/main/java/coffeemeet/server/chatting/exception/ChattingErrorCode.java b/src/main/java/coffeemeet/server/chatting/exception/ChattingErrorCode.java index c5ed5ca1..2b1e1f74 100644 --- a/src/main/java/coffeemeet/server/chatting/exception/ChattingErrorCode.java +++ b/src/main/java/coffeemeet/server/chatting/exception/ChattingErrorCode.java @@ -8,7 +8,7 @@ @RequiredArgsConstructor public enum ChattingErrorCode implements ErrorCode { INVALID_MESSAGE("CM000", "유효하지 않은 메세지 형식입니다."), - ; + CHATTING_ROOM_NOT_FOUND("CR004", "채팅방을 찾을 수 없습니다."); private final String errorCode; private final String message; diff --git a/src/main/java/coffeemeet/server/common/config/ChattingConfig.java b/src/main/java/coffeemeet/server/common/config/ChattingConfig.java new file mode 100644 index 00000000..e5257b09 --- /dev/null +++ b/src/main/java/coffeemeet/server/common/config/ChattingConfig.java @@ -0,0 +1,38 @@ +package coffeemeet.server.common.config; + +import coffeemeet.server.auth.domain.JwtTokenProvider; +import coffeemeet.server.auth.implement.RefreshTokenQuery; +import coffeemeet.server.common.presentation.interceptor.StompInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +@RequiredArgsConstructor +public class ChattingConfig implements WebSocketMessageBrokerConfigurer { + + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenQuery refreshTokenQuery; + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/stomp").setAllowedOriginPatterns("*"); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/sub"); + registry.setApplicationDestinationPrefixes("/pub"); + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(new StompInterceptor(jwtTokenProvider, refreshTokenQuery)); + } + +} diff --git a/src/main/java/coffeemeet/server/common/execption/GlobalErrorCode.java b/src/main/java/coffeemeet/server/common/execption/GlobalErrorCode.java index bb2f0b0f..66c1b186 100644 --- a/src/main/java/coffeemeet/server/common/execption/GlobalErrorCode.java +++ b/src/main/java/coffeemeet/server/common/execption/GlobalErrorCode.java @@ -9,6 +9,7 @@ public enum GlobalErrorCode implements ErrorCode { VALIDATION_ERROR("G000", "유효하지 않은 입력입니다."), INVALID_S3_URL("G004", "유효하지 않은 s3 url 입니다."), + STOMP_ACCESSOR_NOT_FOUND("G004", "웹소켓 연결을 할 수 없습니다."), INTERNAL_SERVER_ERROR("G050", "예상치 못한 오류입니다."); private final String code; diff --git a/src/main/java/coffeemeet/server/common/presentation/advice/GlobalExceptionHandler.java b/src/main/java/coffeemeet/server/common/presentation/advice/GlobalExceptionHandler.java index a9af6077..68988ce5 100644 --- a/src/main/java/coffeemeet/server/common/presentation/advice/GlobalExceptionHandler.java +++ b/src/main/java/coffeemeet/server/common/presentation/advice/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import coffeemeet.server.common.execption.DataLengthExceededException; import coffeemeet.server.common.execption.GlobalErrorCode; import coffeemeet.server.common.execption.InvalidAuthException; +import coffeemeet.server.common.execption.InvalidInputException; import coffeemeet.server.common.execption.MissMatchException; import coffeemeet.server.common.execption.NotFoundException; import lombok.RequiredArgsConstructor; @@ -42,6 +43,13 @@ public ResponseEntity handleException(InvalidAuthException except .body(ErrorResponse.of(exception.getErrorCode())); } + @ExceptionHandler(InvalidInputException.class) + public ResponseEntity handleException(InvalidInputException exception) { + log.info(exception.getMessage(), exception); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ErrorResponse.of(exception.getErrorCode())); + } + @ExceptionHandler(MissMatchException.class) public ResponseEntity handleException(MissMatchException exception) { log.info(exception.getMessage(), exception); diff --git a/src/main/java/coffeemeet/server/common/presentation/interceptor/StompInterceptor.java b/src/main/java/coffeemeet/server/common/presentation/interceptor/StompInterceptor.java new file mode 100644 index 00000000..e76b0bc7 --- /dev/null +++ b/src/main/java/coffeemeet/server/common/presentation/interceptor/StompInterceptor.java @@ -0,0 +1,54 @@ +package coffeemeet.server.common.presentation.interceptor; + +import static coffeemeet.server.auth.exception.AuthErrorCode.AUTHENTICATION_FAILED; +import static coffeemeet.server.common.execption.GlobalErrorCode.STOMP_ACCESSOR_NOT_FOUND; + +import coffeemeet.server.auth.domain.JwtTokenProvider; +import coffeemeet.server.auth.implement.RefreshTokenQuery; +import coffeemeet.server.common.execption.InvalidAuthException; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; + +@RequiredArgsConstructor +public class StompInterceptor implements ChannelInterceptor { + + private static final String STOMP_ACCESSOR_NOT_FOUND_MESSAGE = "stomp header accessor를 찾을 수 없습니다."; + private static final String HEADER_AUTHENTICATION_FAILED_MESSAGE = "(%s)는 잘못된 권한 헤더입니다."; + + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenQuery refreshTokenQuery; + + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, + StompHeaderAccessor.class); + if (headerAccessor == null) { + throw new InvalidAuthException( + STOMP_ACCESSOR_NOT_FOUND, + STOMP_ACCESSOR_NOT_FOUND_MESSAGE + ); + } + + if (headerAccessor.getCommand() == StompCommand.CONNECT) { + String authHeader = String.valueOf(headerAccessor.getNativeHeader("Authorization")); + if (authHeader != null && authHeader.startsWith("Bearer ", 1)) { + String token = authHeader.substring(7, authHeader.length() - 1); + Long userId = jwtTokenProvider.extractUserId(token); + refreshTokenQuery.getRefreshToken(userId); + headerAccessor.addNativeHeader("userId", String.valueOf(userId)); + } else { + throw new InvalidAuthException( + AUTHENTICATION_FAILED, + String.format(HEADER_AUTHENTICATION_FAILED_MESSAGE, authHeader) + ); + } + } + return message; + } + +} diff --git a/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java b/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java index 27188479..20521185 100644 --- a/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java +++ b/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java @@ -7,7 +7,7 @@ import coffeemeet.server.auth.implement.RefreshTokenQuery; import coffeemeet.server.common.annotation.Login; import coffeemeet.server.common.domain.AuthInfo; -import coffeemeet.server.common.execption.InvalidInputException; +import coffeemeet.server.common.execption.InvalidAuthException; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; @@ -44,7 +44,7 @@ public AuthInfo resolveArgument(MethodParameter parameter, ModelAndViewContainer RefreshToken refreshToken = refreshTokenQuery.getRefreshToken(userId); return new AuthInfo(userId, refreshToken.getValue()); } - throw new InvalidInputException( + throw new InvalidAuthException( AUTHENTICATION_FAILED, String.format(HEADER_AUTHENTICATION_FAILED_MESSAGE, authHeader) ); diff --git a/src/test/java/coffeemeet/server/chatting/current/implement/ChattingMessageCommandTest.java b/src/test/java/coffeemeet/server/chatting/current/implement/ChattingMessageCommandTest.java new file mode 100644 index 00000000..7f0a63b9 --- /dev/null +++ b/src/test/java/coffeemeet/server/chatting/current/implement/ChattingMessageCommandTest.java @@ -0,0 +1,44 @@ +package coffeemeet.server.chatting.current.implement; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.only; + +import coffeemeet.server.chatting.current.domain.ChattingMessage; +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.infrastructure.ChattingMessageRepository; +import coffeemeet.server.common.fixture.entity.ChattingFixture; +import coffeemeet.server.common.fixture.entity.UserFixture; +import coffeemeet.server.user.domain.User; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChattingMessageCommandTest { + + @InjectMocks + private ChattingMessageCommand chattingMessageCommand; + + @Mock + private ChattingMessageRepository chattingMessageRepository; + + @DisplayName("새로운 채팅 메세지를 저장할 수 있다.") + @Test + void saveChattingMessageTest() { + // given + String content = "content"; + ChattingRoom chattingRoom = ChattingFixture.chattingRoom(); + User user = UserFixture.user(); + + // when + chattingMessageCommand.saveChattingMessage(content, chattingRoom, user); + + // then + then(chattingMessageRepository).should(only()).save(any(ChattingMessage.class)); + } + +} diff --git a/src/test/java/coffeemeet/server/chatting/current/implement/ChattingRoomCommandTest.java b/src/test/java/coffeemeet/server/chatting/current/implement/ChattingRoomCommandTest.java new file mode 100644 index 00000000..ac2ac0f1 --- /dev/null +++ b/src/test/java/coffeemeet/server/chatting/current/implement/ChattingRoomCommandTest.java @@ -0,0 +1,35 @@ +package coffeemeet.server.chatting.current.implement; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.only; + +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.infrastructure.ChattingRoomRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChattingRoomCommandTest { + + @InjectMocks + private ChattingRoomCommand chattingRoomCommand; + + @Mock + private ChattingRoomRepository chattingRoomRepository; + + @DisplayName("채팅방을 생성할 수 있다.") + @Test + void saveChattingRoomTest() { + // given, when + chattingRoomCommand.saveChattingRoom(); + + // then + then(chattingRoomRepository).should(only()).save(any(ChattingRoom.class)); + } + +} diff --git a/src/test/java/coffeemeet/server/chatting/current/implement/ChattingRoomQueryTest.java b/src/test/java/coffeemeet/server/chatting/current/implement/ChattingRoomQueryTest.java new file mode 100644 index 00000000..76b2fa97 --- /dev/null +++ b/src/test/java/coffeemeet/server/chatting/current/implement/ChattingRoomQueryTest.java @@ -0,0 +1,42 @@ +package coffeemeet.server.chatting.current.implement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.infrastructure.ChattingRoomRepository; +import coffeemeet.server.common.fixture.entity.ChattingFixture; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChattingRoomQueryTest { + + @InjectMocks + private ChattingRoomQuery chattingRoomQuery; + + @Mock + private ChattingRoomRepository chattingRoomRepository; + + + @DisplayName("채팅방을 조회할 수 있다.") + @Test + void getChattingRoomByIdTest() { + // given + ChattingRoom chattingRoom = ChattingFixture.chattingRoom(); + Long chattingRoomId = chattingRoom.getId(); + given(chattingRoomRepository.findById(chattingRoomId)).willReturn(Optional.of(chattingRoom)); + + // when + ChattingRoom foundChattingRoom = chattingRoomQuery.getChattingRoomById(chattingRoomId); + + // then + assertThat(foundChattingRoom).isEqualTo(chattingRoom); + } + +} diff --git a/src/test/java/coffeemeet/server/chatting/current/service/ChattingMessageServiceTest.java b/src/test/java/coffeemeet/server/chatting/current/service/ChattingMessageServiceTest.java new file mode 100644 index 00000000..96d12f12 --- /dev/null +++ b/src/test/java/coffeemeet/server/chatting/current/service/ChattingMessageServiceTest.java @@ -0,0 +1,49 @@ +package coffeemeet.server.chatting.current.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.only; + +import coffeemeet.server.chatting.current.implement.ChattingMessageCommand; +import coffeemeet.server.chatting.current.implement.ChattingRoomQuery; +import coffeemeet.server.user.implement.UserQuery; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChattingMessageServiceTest { + + @InjectMocks + private ChattingMessageService chattingMessageService; + + @Mock + private ChattingMessageCommand chattingMessageCommand; + + @Mock + private ChattingRoomQuery chattingRoomQuery; + + @Mock + private UserQuery userQuery; + + @DisplayName("채팅 메세지를 만들 수 있다.") + @Test + void createChattingMessageTest() { + // given + Long roomId = 1L; + String content = "내용"; + Long userId = 1L; + + // when + chattingMessageService.createChattingMessage(roomId, content, userId); + + // then + then(userQuery).should(only()).getUserById(userId); + then(chattingRoomQuery).should(only()).getChattingRoomById(roomId); + then(chattingMessageCommand).should(only()).saveChattingMessage(any(), any(), any()); + } + +} diff --git a/src/test/java/coffeemeet/server/chatting/current/service/ChattingRoomServiceTest.java b/src/test/java/coffeemeet/server/chatting/current/service/ChattingRoomServiceTest.java new file mode 100644 index 00000000..445644bd --- /dev/null +++ b/src/test/java/coffeemeet/server/chatting/current/service/ChattingRoomServiceTest.java @@ -0,0 +1,33 @@ +package coffeemeet.server.chatting.current.service; + +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.only; + +import coffeemeet.server.chatting.current.implement.ChattingRoomCommand; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChattingRoomServiceTest { + + @InjectMocks + private ChattingRoomService chattingRoomService; + + @Mock + private ChattingRoomCommand chattingRoomCommand; + + @DisplayName("채팅방을 만들 수 있다.") + @Test + void createChattingRoomTest() { + // given, when + chattingRoomService.createChattingRoom(); + + // then + then(chattingRoomCommand).should(only()).saveChattingRoom(); + } + +} diff --git a/src/test/java/coffeemeet/server/common/fixture/entity/ChattingFixture.java b/src/test/java/coffeemeet/server/common/fixture/entity/ChattingFixture.java new file mode 100644 index 00000000..47ddba1d --- /dev/null +++ b/src/test/java/coffeemeet/server/common/fixture/entity/ChattingFixture.java @@ -0,0 +1,25 @@ +package coffeemeet.server.common.fixture.entity; + +import coffeemeet.server.chatting.current.domain.ChattingMessage; +import coffeemeet.server.chatting.current.domain.ChattingRoom; +import coffeemeet.server.chatting.current.presentation.dto.ChatStomp; +import org.instancio.Instancio; + +public class ChattingFixture { + + public static ChattingMessage chattingMessage() { + return Instancio.of(ChattingMessage.class) + .create(); + } + + public static ChattingRoom chattingRoom() { + return Instancio.of(ChattingRoom.class) + .create(); + } + + public static ChatStomp.Request chatStompRequest() { + return Instancio.of(ChatStomp.Request.class) + .create(); + } + +}