diff --git a/src/main/java/com/example/RealMatch/chat/application/service/ChatService.java b/src/main/java/com/example/RealMatch/chat/application/service/ChatService.java new file mode 100644 index 00000000..962defb4 --- /dev/null +++ b/src/main/java/com/example/RealMatch/chat/application/service/ChatService.java @@ -0,0 +1,54 @@ +package com.example.RealMatch.chat.application.service; + +import org.springframework.web.multipart.MultipartFile; + +import com.example.RealMatch.chat.presentation.conversion.MessageCursor; +import com.example.RealMatch.chat.presentation.conversion.RoomCursor; +import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomFilterStatus; +import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomSort; +import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomTab; +import com.example.RealMatch.chat.presentation.dto.enums.ChatSystemMessageKind; +import com.example.RealMatch.chat.presentation.dto.request.ChatAttachmentUploadRequest; +import com.example.RealMatch.chat.presentation.dto.request.ChatRoomCreateRequest; +import com.example.RealMatch.chat.presentation.dto.response.ChatAttachmentUploadResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatMessageListResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatMessageResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatRoomCreateResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatRoomDetailResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatRoomListResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessagePayload; +import com.example.RealMatch.global.config.jwt.CustomUserDetails; + +public interface ChatService { + ChatRoomCreateResponse createOrGetRoom(CustomUserDetails user, ChatRoomCreateRequest request); + + ChatRoomListResponse getRoomList( + CustomUserDetails user, + ChatRoomTab tab, + ChatRoomFilterStatus filterStatus, + ChatRoomSort sort, + RoomCursor roomCursor, + int size + ); + + ChatRoomDetailResponse getRoomDetail(CustomUserDetails user, Long roomId); + + ChatMessageListResponse getMessages( + CustomUserDetails user, + Long roomId, + MessageCursor messageCursor, + int size + ); + + ChatAttachmentUploadResponse uploadAttachment( + CustomUserDetails user, + ChatAttachmentUploadRequest request, + MultipartFile file + ); + + ChatMessageResponse createSystemMessage( + Long roomId, + ChatSystemMessageKind kind, + ChatSystemMessagePayload payload + ); +} diff --git a/src/main/java/com/example/RealMatch/chat/application/service/ChatServiceImpl.java b/src/main/java/com/example/RealMatch/chat/application/service/ChatServiceImpl.java new file mode 100644 index 00000000..f88b5ce9 --- /dev/null +++ b/src/main/java/com/example/RealMatch/chat/application/service/ChatServiceImpl.java @@ -0,0 +1,76 @@ +package com.example.RealMatch.chat.application.service; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.example.RealMatch.chat.presentation.controller.fixture.ChatFixtureFactory; +import com.example.RealMatch.chat.presentation.conversion.MessageCursor; +import com.example.RealMatch.chat.presentation.conversion.RoomCursor; +import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomFilterStatus; +import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomSort; +import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomTab; +import com.example.RealMatch.chat.presentation.dto.enums.ChatSystemMessageKind; +import com.example.RealMatch.chat.presentation.dto.request.ChatAttachmentUploadRequest; +import com.example.RealMatch.chat.presentation.dto.request.ChatRoomCreateRequest; +import com.example.RealMatch.chat.presentation.dto.response.ChatAttachmentUploadResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatMessageListResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatMessageResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatRoomCreateResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatRoomDetailResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatRoomListResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessagePayload; +import com.example.RealMatch.global.config.jwt.CustomUserDetails; + +@Service +public class ChatServiceImpl implements ChatService { + + @Override + public ChatRoomCreateResponse createOrGetRoom(CustomUserDetails user, ChatRoomCreateRequest request) { + return ChatFixtureFactory.sampleRoomCreateResponse(); + } + + @Override + public ChatRoomListResponse getRoomList( + CustomUserDetails user, + ChatRoomTab tab, + ChatRoomFilterStatus filterStatus, + ChatRoomSort sort, + RoomCursor roomCursor, + int size + ) { + return ChatFixtureFactory.sampleRoomListResponse(); + } + + @Override + public ChatRoomDetailResponse getRoomDetail(CustomUserDetails user, Long roomId) { + return ChatFixtureFactory.sampleRoomDetailResponse(roomId); + } + + @Override + public ChatMessageListResponse getMessages( + CustomUserDetails user, + Long roomId, + MessageCursor messageCursor, + int size + ) { + return ChatFixtureFactory.sampleMessageListResponse(roomId); + } + + @Override + public ChatAttachmentUploadResponse uploadAttachment( + CustomUserDetails user, + ChatAttachmentUploadRequest request, + MultipartFile file + ) { + return ChatFixtureFactory.sampleAttachmentUploadResponse(request, file); + } + + @Override + public ChatMessageResponse createSystemMessage( + Long roomId, + ChatSystemMessageKind kind, + ChatSystemMessagePayload payload + ) { + return ChatFixtureFactory.sampleSystemMessageResponse(roomId, kind, payload); + } +} diff --git a/src/main/java/com/example/RealMatch/chat/application/service/ChatSocketService.java b/src/main/java/com/example/RealMatch/chat/application/service/ChatSocketService.java new file mode 100644 index 00000000..d628c268 --- /dev/null +++ b/src/main/java/com/example/RealMatch/chat/application/service/ChatSocketService.java @@ -0,0 +1,22 @@ +package com.example.RealMatch.chat.application.service; + +import org.springframework.lang.NonNull; + +import com.example.RealMatch.chat.presentation.dto.enums.ChatSystemMessageKind; +import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessagePayload; +import com.example.RealMatch.chat.presentation.dto.websocket.ChatMessageCreatedEvent; +import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageAck; +import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageCommand; + +public interface ChatSocketService { + @NonNull + ChatMessageCreatedEvent createMessageEvent(ChatSendMessageCommand command); + + ChatSendMessageAck createAck(ChatSendMessageCommand command, Long messageId); + + ChatMessageCreatedEvent createSystemMessageEvent( + Long roomId, + ChatSystemMessageKind kind, + ChatSystemMessagePayload payload + ); +} diff --git a/src/main/java/com/example/RealMatch/chat/application/service/ChatSocketServiceImpl.java b/src/main/java/com/example/RealMatch/chat/application/service/ChatSocketServiceImpl.java new file mode 100644 index 00000000..8d27ed77 --- /dev/null +++ b/src/main/java/com/example/RealMatch/chat/application/service/ChatSocketServiceImpl.java @@ -0,0 +1,40 @@ +package com.example.RealMatch.chat.application.service; + +import java.util.Objects; + +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; + +import com.example.RealMatch.chat.presentation.controller.fixture.ChatSocketFixtureFactory; +import com.example.RealMatch.chat.presentation.dto.enums.ChatSystemMessageKind; +import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessagePayload; +import com.example.RealMatch.chat.presentation.dto.websocket.ChatMessageCreatedEvent; +import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageAck; +import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageCommand; + +@Service +public class ChatSocketServiceImpl implements ChatSocketService { + + @Override + @NonNull + public ChatMessageCreatedEvent createMessageEvent(ChatSendMessageCommand command) { + return Objects.requireNonNull( + ChatSocketFixtureFactory.sampleMessageCreatedEvent(command), + "ChatMessageCreatedEvent must not be null." + ); + } + + @Override + public ChatSendMessageAck createAck(ChatSendMessageCommand command, Long messageId) { + return ChatSocketFixtureFactory.sampleAck(command, messageId); + } + + @Override + public ChatMessageCreatedEvent createSystemMessageEvent( + Long roomId, + ChatSystemMessageKind kind, + ChatSystemMessagePayload payload + ) { + throw new UnsupportedOperationException("Not implemented yet."); + } +} diff --git a/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatController.java b/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatController.java index abe47b3f..d42698c0 100644 --- a/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatController.java +++ b/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import com.example.RealMatch.chat.presentation.controller.fixture.ChatFixtureFactory; +import com.example.RealMatch.chat.application.service.ChatService; import com.example.RealMatch.chat.presentation.conversion.MessageCursor; import com.example.RealMatch.chat.presentation.conversion.RoomCursor; import com.example.RealMatch.chat.presentation.dto.enums.ChatRoomFilterStatus; @@ -34,12 +34,18 @@ @RequestMapping("/api/chat") public class ChatController implements ChatSwagger { + private final ChatService chatService; + + public ChatController(ChatService chatService) { + this.chatService = chatService; + } + @PostMapping("/rooms") public CustomResponse createOrGetRoom( @AuthenticationPrincipal CustomUserDetails user, @Valid @RequestBody ChatRoomCreateRequest request ) { - return CustomResponse.ok(ChatFixtureFactory.sampleRoomCreateResponse()); + return CustomResponse.ok(chatService.createOrGetRoom(user, request)); } @GetMapping("/rooms") @@ -51,7 +57,7 @@ public CustomResponse getRoomList( @RequestParam(name = "cursor", required = false) RoomCursor roomCursor, @RequestParam(defaultValue = "20") int size ) { - return CustomResponse.ok(ChatFixtureFactory.sampleRoomListResponse()); + return CustomResponse.ok(chatService.getRoomList(user, tab, filterStatus, sort, roomCursor, size)); } @GetMapping("/rooms/{roomId}") @@ -59,7 +65,7 @@ public CustomResponse getRoomDetail( @AuthenticationPrincipal CustomUserDetails user, @PathVariable Long roomId ) { - return CustomResponse.ok(ChatFixtureFactory.sampleRoomDetailResponse(roomId)); + return CustomResponse.ok(chatService.getRoomDetail(user, roomId)); } @GetMapping("/rooms/{roomId}/messages") @@ -69,7 +75,7 @@ public CustomResponse getMessages( @RequestParam(name = "cursor", required = false) MessageCursor messageCursor, @RequestParam(defaultValue = "20") int size ) { - return CustomResponse.ok(ChatFixtureFactory.sampleMessageListResponse(roomId)); + return CustomResponse.ok(chatService.getMessages(user, roomId, messageCursor, size)); } @PostMapping("/attachments") @@ -78,6 +84,6 @@ public CustomResponse uploadAttachment( @Valid @RequestPart("request") ChatAttachmentUploadRequest request, @RequestPart("file") MultipartFile file ) { - return CustomResponse.ok(ChatFixtureFactory.sampleAttachmentUploadResponse(request, file)); + return CustomResponse.ok(chatService.uploadAttachment(user, request, file)); } } diff --git a/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatSocketController.java b/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatSocketController.java index 370b78c6..d5d4c853 100644 --- a/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatSocketController.java +++ b/src/main/java/com/example/RealMatch/chat/presentation/controller/ChatSocketController.java @@ -1,14 +1,12 @@ package com.example.RealMatch.chat.presentation.controller; -import java.util.Objects; - import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.annotation.SendToUser; import org.springframework.stereotype.Controller; -import com.example.RealMatch.chat.presentation.controller.fixture.ChatSocketFixtureFactory; +import com.example.RealMatch.chat.application.service.ChatSocketService; import com.example.RealMatch.chat.presentation.dto.websocket.ChatMessageCreatedEvent; import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageAck; import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageCommand; @@ -19,16 +17,18 @@ public class ChatSocketController { private final SimpMessagingTemplate messagingTemplate; + private final ChatSocketService chatSocketService; - public ChatSocketController(SimpMessagingTemplate messagingTemplate) { + public ChatSocketController(SimpMessagingTemplate messagingTemplate, ChatSocketService chatSocketService) { this.messagingTemplate = messagingTemplate; + this.chatSocketService = chatSocketService; } @MessageMapping("/chat.send") @SendToUser("/queue/chat.ack") public ChatSendMessageAck sendMessage(@Valid @Payload ChatSendMessageCommand command) { - ChatMessageCreatedEvent event = ChatSocketFixtureFactory.sampleMessageCreatedEvent(command); - messagingTemplate.convertAndSend("/topic/rooms/" + command.roomId(), Objects.requireNonNull(event)); - return ChatSocketFixtureFactory.sampleAck(command, event.message().messageId()); + ChatMessageCreatedEvent event = chatSocketService.createMessageEvent(command); + messagingTemplate.convertAndSend("/topic/rooms/" + command.roomId(), event); + return chatSocketService.createAck(command, event.message().messageId()); } } diff --git a/src/main/java/com/example/RealMatch/chat/presentation/controller/fixture/ChatFixtureFactory.java b/src/main/java/com/example/RealMatch/chat/presentation/controller/fixture/ChatFixtureFactory.java index 2b09281c..be8f85ca 100644 --- a/src/main/java/com/example/RealMatch/chat/presentation/controller/fixture/ChatFixtureFactory.java +++ b/src/main/java/com/example/RealMatch/chat/presentation/controller/fixture/ChatFixtureFactory.java @@ -30,10 +30,11 @@ import com.example.RealMatch.chat.presentation.dto.response.ChatRoomCreateResponse; import com.example.RealMatch.chat.presentation.dto.response.ChatRoomDetailResponse; import com.example.RealMatch.chat.presentation.dto.response.ChatRoomListResponse; +import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessagePayload; import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessageResponse; /** - * 채팅 API 테스트를 위한 Fixture 응답 DTO를 생성하는 팩토리 (서비스 로직 완성하면 삭제할 것임) + * 채팅 API 테스트를 위한 Fixture 응답 DTO를 생성하는 팩토리 (서비스 로직 완성하면 운영 코드에서 제거하고 테스트 전용으로 남겨둠) */ public final class ChatFixtureFactory { @@ -99,6 +100,30 @@ public static ChatMessageListResponse sampleMessageListResponse(Long roomId) { ); } + public static ChatMessageResponse sampleSystemMessageResponse( + Long roomId, + ChatSystemMessageKind kind, + ChatSystemMessagePayload payload + ) { + if (kind == null) { + throw new IllegalArgumentException("System message kind is required."); + } + ChatSystemMessagePayload resolvedPayload = payload != null ? payload : defaultSystemMessagePayload(kind); + ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse(1, kind, resolvedPayload); + return new ChatMessageResponse( + systemMessageMessageId(kind), + roomId, + null, + ChatSenderType.SYSTEM, + ChatMessageType.SYSTEM, + null, + null, + systemMessage, + systemMessageCreatedAt(kind), + null + ); + } + private static ChatMessageResponse sampleTextMessage(Long roomId) { return new ChatMessageResponse( 7001L, @@ -163,92 +188,68 @@ private static ChatMessageResponse sampleFileMessage(Long roomId) { } private static ChatMessageResponse sampleSystemMessage(Long roomId) { - ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse( - 1, - ChatSystemMessageKind.PROPOSAL_CARD, - new ChatProposalCardPayloadResponse( - 5001L, - 4001L, - "캠페인 A", - "캠페인 요약 문구", - ChatProposalDecisionStatus.PENDING, - ChatProposalDirection.BRAND_TO_CREATOR, - new ChatProposalActionButtonsResponse( - new ChatProposalActionButtonResponse( - "제안 수락하기", - true - ), - new ChatProposalActionButtonResponse( - "거절하기", - true - ) - ), - null - ) - ); - return new ChatMessageResponse( - 7004L, - roomId, - null, - ChatSenderType.SYSTEM, - ChatMessageType.SYSTEM, - null, - null, - systemMessage, - LocalDateTime.of(2025, 1, 1, 10, 5), - null - ); + return sampleSystemMessageResponse(roomId, ChatSystemMessageKind.PROPOSAL_CARD, null); } private static ChatMessageResponse sampleProposalStatusNoticeMessage(Long roomId) { - ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse( - 1, - ChatSystemMessageKind.PROPOSAL_STATUS_NOTICE, - new ChatProposalStatusNoticePayloadResponse( - 5001L, - 202L, - LocalDateTime.of(2025, 1, 1, 10, 6) - ) - ); - return new ChatMessageResponse( - 7005L, - roomId, - null, - ChatSenderType.SYSTEM, - ChatMessageType.SYSTEM, - null, - null, - systemMessage, - LocalDateTime.of(2025, 1, 1, 10, 6), - null - ); + return sampleSystemMessageResponse(roomId, ChatSystemMessageKind.PROPOSAL_STATUS_NOTICE, null); } private static ChatMessageResponse sampleMatchedCampaignMessage(Long roomId) { - ChatSystemMessageResponse systemMessage = new ChatSystemMessageResponse( - 1, - ChatSystemMessageKind.MATCHED_CAMPAIGN_CARD, - new ChatMatchedCampaignPayloadResponse( - 4001L, - "캠페인 A", - 150000L, - "KRW", - "ORDER-20250101-0001", - "캠페인이 매칭되었습니다. 협업을 시작해 주세요." - ) - ); - return new ChatMessageResponse( - 7006L, - roomId, - null, - ChatSenderType.SYSTEM, - ChatMessageType.SYSTEM, - null, - null, - systemMessage, - LocalDateTime.of(2025, 1, 1, 10, 7), - null - ); + return sampleSystemMessageResponse(roomId, ChatSystemMessageKind.MATCHED_CAMPAIGN_CARD, null); + } + + private static ChatSystemMessagePayload defaultSystemMessagePayload(ChatSystemMessageKind kind) { + return switch (kind) { + case PROPOSAL_CARD -> new ChatProposalCardPayloadResponse( + 5001L, + 4001L, + "캠페인 A", + "캠페인 요약 문구", + ChatProposalDecisionStatus.PENDING, + ChatProposalDirection.BRAND_TO_CREATOR, + new ChatProposalActionButtonsResponse( + new ChatProposalActionButtonResponse( + "제안 수락하기", + true + ), + new ChatProposalActionButtonResponse( + "거절하기", + true + ) + ), + null + ); + case PROPOSAL_STATUS_NOTICE -> new ChatProposalStatusNoticePayloadResponse( + 5001L, + 202L, + LocalDateTime.of(2025, 1, 1, 10, 6) + ); + case MATCHED_CAMPAIGN_CARD -> new ChatMatchedCampaignPayloadResponse( + 4001L, + "캠페인 A", + 150000L, + "KRW", + "ORDER-20250101-0001", + "캠페인이 매칭되었습니다. 협업을 시작해 주세요." + ); + }; + } + + private static long systemMessageMessageId(ChatSystemMessageKind kind) { + return switch (kind) { + case PROPOSAL_CARD -> 7004L; + case PROPOSAL_STATUS_NOTICE -> 7005L; + case MATCHED_CAMPAIGN_CARD -> 7006L; + }; + } + + private static LocalDateTime systemMessageCreatedAt(ChatSystemMessageKind kind) { + return switch (kind) { + case PROPOSAL_CARD -> LocalDateTime.of(2025, 1, 1, 10, 5); + case PROPOSAL_STATUS_NOTICE -> LocalDateTime.of(2025, 1, 1, 10, 6); + case MATCHED_CAMPAIGN_CARD -> LocalDateTime.of(2025, 1, 1, 10, 7); + }; } public static ChatAttachmentUploadResponse sampleAttachmentUploadResponse( diff --git a/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatControllerTest.java b/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatControllerTest.java index f24e5e47..e879c51c 100644 --- a/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatControllerTest.java +++ b/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatControllerTest.java @@ -2,6 +2,10 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -19,12 +23,15 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; +import com.example.RealMatch.chat.application.service.ChatService; import com.example.RealMatch.chat.presentation.config.ChatCursorConverterConfig; +import com.example.RealMatch.chat.presentation.controller.fixture.ChatFixtureFactory; import com.example.RealMatch.chat.presentation.dto.enums.ChatAttachmentType; import com.example.RealMatch.chat.presentation.dto.request.ChatAttachmentUploadRequest; import com.example.RealMatch.chat.presentation.dto.request.ChatRoomCreateRequest; import com.example.RealMatch.global.config.jwt.JwtProvider; import com.fasterxml.jackson.databind.ObjectMapper; + @SuppressWarnings("null") @WebMvcTest(ChatController.class) @AutoConfigureMockMvc(addFilters = false) @@ -40,11 +47,17 @@ class ChatControllerTest { @MockitoBean private JwtProvider jwtProvider; + @MockitoBean + private ChatService chatService; + @Test @DisplayName("채팅방 생성/조회: 200 + 공통 응답 포맷") void createOrGetRoom_returnsOk() throws Exception { ChatRoomCreateRequest request = new ChatRoomCreateRequest(101L, 202L); + given(chatService.createOrGetRoom(any(), any())) + .willReturn(ChatFixtureFactory.sampleRoomCreateResponse()); + mockMvc.perform(post("/api/chat/rooms") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) @@ -61,6 +74,9 @@ void createOrGetRoom_returnsOk() throws Exception { @Test @DisplayName("채팅방 목록 조회: 200 + 공통 응답 포맷") void getRoomList_returnsOk() throws Exception { + given(chatService.getRoomList(any(), any(), any(), any(), any(), anyInt())) + .willReturn(ChatFixtureFactory.sampleRoomListResponse()); + mockMvc.perform(get("/api/chat/rooms") .param("tab", "SENT") .param("status", "ALL") @@ -89,6 +105,9 @@ void getRoomList_returnsOk() throws Exception { @Test @DisplayName("채팅방 상세 조회: 200 + 공통 응답 포맷") void getRoomDetail_returnsOk() throws Exception { + given(chatService.getRoomDetail(any(), anyLong())) + .willReturn(ChatFixtureFactory.sampleRoomDetailResponse(3001L)); + mockMvc.perform(get("/api/chat/rooms/{roomId}", 3001L)) .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess").value(true)) @@ -105,6 +124,9 @@ void getRoomDetail_returnsOk() throws Exception { @Test @DisplayName("채팅 메시지 조회: 200 + 공통 응답 포맷") void getMessages_returnsOk() throws Exception { + given(chatService.getMessages(any(), anyLong(), any(), anyInt())) + .willReturn(ChatFixtureFactory.sampleMessageListResponse(3001L)); + mockMvc.perform(get("/api/chat/rooms/{roomId}/messages", 3001L) .param("cursor", "7001") .param("size", "20")) @@ -166,6 +188,12 @@ void uploadAttachment_returnsOk() throws Exception { "dummy".getBytes() ); + given(chatService.uploadAttachment(any(), any(), any())) + .willAnswer(invocation -> ChatFixtureFactory.sampleAttachmentUploadResponse( + invocation.getArgument(1), + invocation.getArgument(2) + )); + mockMvc.perform(multipart("/api/chat/attachments") .file(jsonPart) .file(filePart)) diff --git a/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatSocketControllerTest.java b/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatSocketControllerTest.java index 9f4d944d..8b0a0f6d 100644 --- a/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatSocketControllerTest.java +++ b/src/test/java/com/example/RealMatch/chat/presentation/controller/ChatSocketControllerTest.java @@ -16,6 +16,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.http.server.ServerHttpRequest; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; @@ -30,8 +31,12 @@ import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; +import com.example.RealMatch.chat.application.service.ChatSocketService; +import com.example.RealMatch.chat.presentation.controller.fixture.ChatSocketFixtureFactory; import com.example.RealMatch.chat.presentation.dto.enums.ChatMessageType; import com.example.RealMatch.chat.presentation.dto.enums.ChatSendMessageAckStatus; +import com.example.RealMatch.chat.presentation.dto.enums.ChatSystemMessageKind; +import com.example.RealMatch.chat.presentation.dto.response.ChatSystemMessagePayload; import com.example.RealMatch.chat.presentation.dto.websocket.ChatMessageCreatedEvent; import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageAck; import com.example.RealMatch.chat.presentation.dto.websocket.ChatSendMessageCommand; @@ -208,6 +213,34 @@ private void waitForSubscriptions() throws InterruptedException { @TestConfiguration static class WebSocketTestConfig { + @Bean + @Primary + ChatSocketService chatSocketService() { + return new ChatSocketService() { + @Override + public @NonNull ChatMessageCreatedEvent createMessageEvent(ChatSendMessageCommand command) { + return Objects.requireNonNull( + ChatSocketFixtureFactory.sampleMessageCreatedEvent(command), + "ChatMessageCreatedEvent must not be null." + ); + } + + @Override + public ChatSendMessageAck createAck(ChatSendMessageCommand command, Long messageId) { + return ChatSocketFixtureFactory.sampleAck(command, messageId); + } + + @Override + public ChatMessageCreatedEvent createSystemMessageEvent( + Long roomId, + ChatSystemMessageKind kind, + ChatSystemMessagePayload payload + ) { + throw new UnsupportedOperationException("Not implemented yet."); + } + }; + } + @Bean HandshakeHandler handshakeHandler() { return new DefaultHandshakeHandler() {