diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/api/ChatMemberApi.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/api/ChatMemberApi.java index a7fa2e66e..45c5552ef 100644 --- a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/api/ChatMemberApi.java +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/api/ChatMemberApi.java @@ -4,15 +4,19 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.SchemaProperty; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotEmpty; import kr.co.pennyway.api.apis.chat.dto.ChatMemberReq; +import kr.co.pennyway.api.apis.chat.dto.ChatMemberRes; import kr.co.pennyway.api.apis.chat.dto.ChatRoomRes; import kr.co.pennyway.api.common.annotation.ApiExceptionExplanation; import kr.co.pennyway.api.common.annotation.ApiResponseExplanations; +import kr.co.pennyway.api.common.exception.ApiErrorCode; import kr.co.pennyway.api.common.security.authentication.SecurityUserDetails; import kr.co.pennyway.domain.domains.chatroom.exception.ChatRoomErrorCode; import kr.co.pennyway.domain.domains.member.exception.ChatMemberErrorCode; @@ -21,6 +25,9 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Set; @Tag(name = "[채팅방 멤버 API]") public interface ChatMemberApi { @@ -42,4 +49,17 @@ ResponseEntity joinChatRoom( @Validated @RequestBody ChatMemberReq.Join payload, @AuthenticationPrincipal SecurityUserDetails user ); + + @Operation(summary = "채팅방 멤버 조회", method = "GET", description = "채팅방 멤버 목록을 조회한다. 오로지 요청자의 채팅방 접근 권한만을 검사하며, 요청 아이디의 채팅방 포함 여부에 대한 검사 및 응답은 포함하지 않는다.") + @Parameters({ + @Parameter(name = "chatRoomId", description = "채팅방 ID", required = true, in = ParameterIn.PATH), + @Parameter(name = "ids", description = """ + 멤버 ID 목록. 중복을 허용하며, 순서가 일관되지 않아도 된다. 단, 최대 50개까지 조회 가능하며, null을 허용하지 않는다. 값은 `[채팅방 API] 채팅방 조회`의 응답으로 얻은 `otherParticipantIds`의 값을 사용하면 된다. (주의, userId가 아님!)""" + , required = true, in = ParameterIn.QUERY, array = @ArraySchema(schema = @Schema(type = "integer"))) + }) + @ApiResponseExplanations(errors = { + @ApiExceptionExplanation(value = ApiErrorCode.class, constant = "OVERFLOW_QUERY_PARAMETER", summary = "쿼리 파라미터 오버플로우", description = "쿼리 파라미터가 최대 개수를 초과하여 채팅방 멤버 조회에 실패했습니다.") + }) + @ApiResponse(responseCode = "200", description = "채팅방 멤버 조회 성공", content = @Content(schemaProperties = @SchemaProperty(name = "chatMembers", array = @ArraySchema(schema = @Schema(implementation = ChatMemberRes.Detail.class))))) + ResponseEntity readChatMembers(@PathVariable("chatRoomId") Long chatRoomId, @Validated @NotEmpty @RequestParam("ids") Set ids); } diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberController.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberController.java index 460138cd7..4648eab5a 100644 --- a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberController.java +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberController.java @@ -1,9 +1,12 @@ package kr.co.pennyway.api.apis.chat.controller; +import jakarta.validation.constraints.NotEmpty; import kr.co.pennyway.api.apis.chat.api.ChatMemberApi; import kr.co.pennyway.api.apis.chat.dto.ChatMemberReq; import kr.co.pennyway.api.apis.chat.dto.ChatRoomRes; import kr.co.pennyway.api.apis.chat.usecase.ChatMemberUseCase; +import kr.co.pennyway.api.common.exception.ApiErrorCode; +import kr.co.pennyway.api.common.exception.ApiErrorException; import kr.co.pennyway.api.common.response.SuccessResponse; import kr.co.pennyway.api.common.security.authentication.SecurityUserDetails; import lombok.RequiredArgsConstructor; @@ -14,12 +17,16 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.Set; + @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/v2/chat-rooms/{chatRoomId}/chat-members") public class ChatMemberController implements ChatMemberApi { private static final String CHAT_ROOM = "chatRoom"; + private static final String CHAT_MEMBERS = "chatMembers"; + private final ChatMemberUseCase chatMemberUseCase; @Override @@ -34,4 +41,15 @@ public ResponseEntity joinChatRoom( return ResponseEntity.ok(SuccessResponse.from(CHAT_ROOM, detail)); } + + @Override + @GetMapping("") + @PreAuthorize("isAuthenticated() and @chatRoomManager.hasPermission(principal.userId, #chatRoomId)") + public ResponseEntity readChatMembers(@PathVariable("chatRoomId") Long chatRoomId, @Validated @NotEmpty @RequestParam("ids") Set chatMemberIds) { + if (chatMemberIds.size() > 50) { + throw new ApiErrorException(ApiErrorCode.OVERFLOW_QUERY_PARAMETER); + } + + return ResponseEntity.ok(SuccessResponse.from(CHAT_MEMBERS, chatMemberUseCase.readChatMembers(chatRoomId, chatMemberIds))); + } } diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/mapper/ChatMemberMapper.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/mapper/ChatMemberMapper.java new file mode 100644 index 000000000..517a6aea3 --- /dev/null +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/mapper/ChatMemberMapper.java @@ -0,0 +1,16 @@ +package kr.co.pennyway.api.apis.chat.mapper; + +import kr.co.pennyway.api.apis.chat.dto.ChatMemberRes; +import kr.co.pennyway.common.annotation.Mapper; +import kr.co.pennyway.domain.domains.member.domain.ChatMember; + +import java.util.List; + +@Mapper +public final class ChatMemberMapper { + public static List toChatMemberResDetail(List chatMembers) { + return chatMembers.stream() + .map(chatMember -> ChatMemberRes.Detail.from(chatMember, false)) + .toList(); + } +} diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatMemberSearchService.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatMemberSearchService.java index ce80afa17..e06b0cf3c 100644 --- a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatMemberSearchService.java +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatMemberSearchService.java @@ -1,10 +1,12 @@ package kr.co.pennyway.api.apis.chat.service; +import kr.co.pennyway.domain.domains.member.domain.ChatMember; import kr.co.pennyway.domain.domains.member.service.ChatMemberService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Set; @Slf4j @@ -16,4 +18,8 @@ public class ChatMemberSearchService { public Set readJoinedChatRoomIds(Long userId) { return chatMemberService.readChatRoomIdsByUserId(userId); } + + public List readChatMembers(Long chatRoomId, Set chatMemberIds) { + return chatMemberService.readChatMembersByIdIn(chatRoomId, chatMemberIds); + } } diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchService.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchService.java index 77107e426..e604a67e4 100644 --- a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchService.java +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchService.java @@ -38,10 +38,10 @@ public ChatRoomRes.RoomWithParticipants execute(Long userId, Long chatRoomId) { .filter(sender -> !sender.equals(userId)) .collect(Collectors.toSet()); - List recentParticipants = chatMemberService.readChatMembersByMemberIdIn(chatRoomId, recentParticipantIds); + List recentParticipants = chatMemberService.readChatMembersByUserIdIn(chatRoomId, recentParticipantIds); recentParticipantIds.add(userId); - List otherMemberIds = chatMemberService.readChatMemberIdsByMemberIdNotIn(chatRoomId, recentParticipantIds); + List otherMemberIds = chatMemberService.readChatMemberIdsByUserIdNotIn(chatRoomId, recentParticipantIds); return ChatRoomMapper.toChatRoomResRoomWithParticipants(myInfo, recentParticipants, otherMemberIds, chatMessages); } diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/usecase/ChatMemberUseCase.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/usecase/ChatMemberUseCase.java index cfe55fc26..c1b23d6e1 100644 --- a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/usecase/ChatMemberUseCase.java +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/apis/chat/usecase/ChatMemberUseCase.java @@ -1,23 +1,37 @@ package kr.co.pennyway.api.apis.chat.usecase; +import kr.co.pennyway.api.apis.chat.dto.ChatMemberRes; import kr.co.pennyway.api.apis.chat.dto.ChatRoomRes; +import kr.co.pennyway.api.apis.chat.mapper.ChatMemberMapper; import kr.co.pennyway.api.apis.chat.mapper.ChatRoomMapper; import kr.co.pennyway.api.apis.chat.service.ChatMemberJoinService; +import kr.co.pennyway.api.apis.chat.service.ChatMemberSearchService; import kr.co.pennyway.common.annotation.UseCase; import kr.co.pennyway.domain.domains.chatroom.domain.ChatRoom; +import kr.co.pennyway.domain.domains.member.domain.ChatMember; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; +import java.util.List; +import java.util.Set; + @Slf4j @UseCase @RequiredArgsConstructor public class ChatMemberUseCase { private final ChatMemberJoinService chatMemberJoinService; + private final ChatMemberSearchService chatMemberSearchService; public ChatRoomRes.Detail joinChatRoom(Long userId, Long chatRoomId, Integer password) { Pair chatRoom = chatMemberJoinService.execute(userId, chatRoomId, password); return ChatRoomMapper.toChatRoomResDetail(chatRoom.getLeft(), false, chatRoom.getRight()); } + + public List readChatMembers(Long chatRoomId, Set chatMemberIds) { + List chatMembers = chatMemberSearchService.readChatMembers(chatRoomId, chatMemberIds); + + return ChatMemberMapper.toChatMemberResDetail(chatMembers); + } } diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/common/exception/ApiErrorCode.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/common/exception/ApiErrorCode.java new file mode 100644 index 000000000..5c552e1a1 --- /dev/null +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/common/exception/ApiErrorCode.java @@ -0,0 +1,30 @@ +package kr.co.pennyway.api.common.exception; + +import kr.co.pennyway.common.exception.BaseErrorCode; +import kr.co.pennyway.common.exception.CausedBy; +import kr.co.pennyway.common.exception.ReasonCode; +import kr.co.pennyway.common.exception.StatusCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ApiErrorCode implements BaseErrorCode { + // 400 Bad Request + OVERFLOW_QUERY_PARAMETER(StatusCode.BAD_REQUEST, ReasonCode.INVALID_REQUEST, "쿼리 파라미터가 너무 많습니다."), + ; + + private final StatusCode statusCode; + private final ReasonCode reasonCode; + private final String message; + + @Override + public CausedBy causedBy() { + return CausedBy.of(statusCode, reasonCode); + } + + @Override + public String getExplainError() throws NoSuchFieldError { + return message; + } +} diff --git a/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/common/exception/ApiErrorException.java b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/common/exception/ApiErrorException.java new file mode 100644 index 000000000..1f37cae42 --- /dev/null +++ b/pennyway-app-external-api/src/main/java/kr/co/pennyway/api/common/exception/ApiErrorException.java @@ -0,0 +1,22 @@ +package kr.co.pennyway.api.common.exception; + +import kr.co.pennyway.common.exception.CausedBy; +import kr.co.pennyway.common.exception.GlobalErrorException; + +public class ApiErrorException extends GlobalErrorException { + private final ApiErrorCode errorCode; + + public ApiErrorException(ApiErrorCode errorCode) { + super(errorCode); + this.errorCode = errorCode; + } + + @Override + public CausedBy causedBy() { + return errorCode.causedBy(); + } + + public String getExplainError() { + return errorCode.getExplainError(); + } +} diff --git a/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberBathGetControllerTest.java b/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberBathGetControllerTest.java new file mode 100644 index 000000000..2172cd62c --- /dev/null +++ b/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/controller/ChatMemberBathGetControllerTest.java @@ -0,0 +1,126 @@ +package kr.co.pennyway.api.apis.chat.controller; + +import kr.co.pennyway.api.apis.chat.dto.ChatMemberRes; +import kr.co.pennyway.api.apis.chat.usecase.ChatMemberUseCase; +import kr.co.pennyway.api.config.supporter.WithSecurityMockUser; +import kr.co.pennyway.domain.domains.member.type.ChatMemberRole; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +import static org.mockito.BDDMockito.given; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = ChatMemberController.class) +@ActiveProfiles("test") +public class ChatMemberBathGetControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private ChatMemberUseCase chatMemberUseCase; + + @BeforeEach + void setUp(WebApplicationContext webApplicationContext) { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .defaultRequest(MockMvcRequestBuilders.get("/**").with(csrf())) + .build(); + } + + @Test + @DisplayName("채팅방 멤버 조회에 성공한다") + @WithSecurityMockUser + void successReadChatMembers() throws Exception { + // given + Long chatRoomId = 1L; + Set memberIds = Set.of(1L, 2L, 3L); + List expectedResponse = createMockMemberDetails(); + + given(chatMemberUseCase.readChatMembers(chatRoomId, memberIds)).willReturn(expectedResponse); + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/v2/chat-rooms/{chatRoomId}/chat-members", chatRoomId) + .param("ids", "1,2,3") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.chatMembers").isArray()) + .andExpect(jsonPath("$.data.chatMembers.length()").value(3)) + .andDo(print()); + } + + @Test + @DisplayName("50개를 초과하는 멤버 ID 요청 시 실패한다") + @WithSecurityMockUser + void failReadChatMembersWhenExceedLimit() throws Exception { + // given + Long chatRoomId = 1L; + Set memberIds = LongStream.rangeClosed(1, 51) + .boxed() + .collect(Collectors.toSet()); + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/v2/chat-rooms/{chatRoomId}/chat-members", chatRoomId) + .param("ids", memberIds.stream() + .map(String::valueOf) + .collect(Collectors.joining(","))) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @Test + @DisplayName("ids가 null인 경우 실패한다 <400 Bad Request>") + @WithSecurityMockUser + void failReadChatMembersWhenIdsIsNull() throws Exception { + // given + Long chatRoomId = 1L; + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/v2/chat-rooms/{chatRoomId}/chat-members", chatRoomId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @Test + @DisplayName("ids가 빈 값일 경우 실패한다") + @WithSecurityMockUser + void failReadChatMembersWhenIdsIsEmpty() throws Exception { + // given + Long chatRoomId = 1L; + + // when & then + mockMvc.perform(MockMvcRequestBuilders.get("/v2/chat-rooms/{chatRoomId}/chat-members", chatRoomId) + .param("ids", "") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + private List createMockMemberDetails() { + return List.of( + new ChatMemberRes.Detail(1L, "User1", ChatMemberRole.MEMBER, null, LocalDateTime.now()), + new ChatMemberRes.Detail(2L, "User2", ChatMemberRole.MEMBER, null, LocalDateTime.now()), + new ChatMemberRes.Detail(3L, "User3", ChatMemberRole.MEMBER, null, LocalDateTime.now()) + ); + } +} diff --git a/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/integration/ChatMemberBatchGetIntegrationTest.java b/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/integration/ChatMemberBatchGetIntegrationTest.java new file mode 100644 index 000000000..3ce1ad4d5 --- /dev/null +++ b/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/integration/ChatMemberBatchGetIntegrationTest.java @@ -0,0 +1,109 @@ +package kr.co.pennyway.api.apis.chat.integration; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.pennyway.api.apis.chat.dto.ChatMemberRes; +import kr.co.pennyway.api.common.response.SuccessResponse; +import kr.co.pennyway.api.common.util.ApiTestHelper; +import kr.co.pennyway.api.config.ExternalApiDBTestConfig; +import kr.co.pennyway.api.config.ExternalApiIntegrationTest; +import kr.co.pennyway.api.config.fixture.ChatRoomFixture; +import kr.co.pennyway.api.config.fixture.UserFixture; +import kr.co.pennyway.domain.domains.chatroom.domain.ChatRoom; +import kr.co.pennyway.domain.domains.chatroom.service.ChatRoomService; +import kr.co.pennyway.domain.domains.member.domain.ChatMember; +import kr.co.pennyway.domain.domains.member.service.ChatMemberService; +import kr.co.pennyway.domain.domains.user.domain.User; +import kr.co.pennyway.domain.domains.user.service.UserService; +import kr.co.pennyway.infra.common.jwt.JwtProvider; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Slf4j +@ExternalApiIntegrationTest +public class ChatMemberBatchGetIntegrationTest extends ExternalApiDBTestConfig { + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private ChatRoomService chatRoomService; + + @Autowired + private ChatMemberService chatMemberService; + + @Autowired + private UserService userService; + + @Autowired + private JwtProvider accessTokenProvider; + + @LocalServerPort + private int port; + + private ApiTestHelper apiTestHelper; + + private User owner; + private ChatRoom chatRoom; + private ChatMember ownerMember; + + @BeforeEach + void setUp() { + apiTestHelper = new ApiTestHelper(restTemplate, objectMapper, accessTokenProvider); + + owner = userService.createUser(UserFixture.GENERAL_USER.toUser()); + chatRoom = chatRoomService.create(ChatRoomFixture.PUBLIC_CHAT_ROOM.toEntity(1L)); + ownerMember = chatMemberService.createAdmin(owner, chatRoom); + } + + @Test + @DisplayName("채팅방 멤버 조회를 성공한다") + void successReadChatMembers() { + // given + List members = createTestMembers(10); + List memberIds = members.stream().map(ChatMember::getId).toList(); + + // when + ResponseEntity response = apiTestHelper.callApi( + "http://localhost:" + port + "/v2/chat-rooms/{chatRoomId}/chat-members?ids={ids}", + HttpMethod.GET, + owner, + null, + new TypeReference>>>() { + }, + chatRoom.getId(), + String.join(",", memberIds.stream().map(String::valueOf).toList()) + ); + SuccessResponse>> body = (SuccessResponse>>) response.getBody(); + List payload = body.getData().get("chatMembers"); + + // then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(memberIds.size(), payload.size()); + } + + private List createTestMembers(int count) { + List createdMembers = new ArrayList<>(); + for (int i = 0; i < count; i++) { + User member = userService.createUser(UserFixture.GENERAL_USER.toUser()); + createdMembers.add(chatMemberService.createMember(member, chatRoom)); + } + return createdMembers; + } +} diff --git a/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchServiceTest.java b/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchServiceTest.java index f105e3465..ceb2c7f1c 100644 --- a/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchServiceTest.java +++ b/pennyway-app-external-api/src/test/java/kr/co/pennyway/api/apis/chat/service/ChatRoomWithParticipantsSearchServiceTest.java @@ -64,8 +64,8 @@ public void successToRetrieveChatRoomWithParticipantsAndRecentMessages() { given(chatMemberService.readChatMember(userId, chatRoom.getId())).willReturn(Optional.of(myInfo)); given(chatMessageService.readRecentMessages(eq(chatRoom.getId()), anyInt())).willReturn(recentMessages); - given(chatMemberService.readChatMembersByMemberIdIn(eq(chatRoom.getId()), anySet())).willReturn(recentParticipants); - given(chatMemberService.readChatMemberIdsByMemberIdNotIn(eq(chatRoom.getId()), anySet())).willReturn(otherMemberIds); + given(chatMemberService.readChatMembersByUserIdIn(eq(chatRoom.getId()), anySet())).willReturn(recentParticipants); + given(chatMemberService.readChatMemberIdsByUserIdNotIn(eq(chatRoom.getId()), anySet())).willReturn(otherMemberIds); // when ChatRoomRes.RoomWithParticipants result = service.execute(userId, chatRoom.getId()); @@ -83,8 +83,8 @@ public void successToRetrieveChatRoomWithParticipantsAndRecentMessages() { // verify verify(chatMemberService).readChatMember(userId, chatRoom.getId()); verify(chatMessageService).readRecentMessages(eq(chatRoom.getId()), anyInt()); - verify(chatMemberService).readChatMembersByMemberIdIn(eq(chatRoom.getId()), anySet()); - verify(chatMemberService).readChatMemberIdsByMemberIdNotIn(eq(chatRoom.getId()), anySet()); + verify(chatMemberService).readChatMembersByUserIdIn(eq(chatRoom.getId()), anySet()); + verify(chatMemberService).readChatMemberIdsByUserIdNotIn(eq(chatRoom.getId()), anySet()); } @Test diff --git a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/repository/ChatMemberRepository.java b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/repository/ChatMemberRepository.java index bdcefe904..68b6e4bf0 100644 --- a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/repository/ChatMemberRepository.java +++ b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/repository/ChatMemberRepository.java @@ -13,6 +13,10 @@ public interface ChatMemberRepository extends ExtendedRepository findByChatRoom_IdAndUser_Id(Long chatRoomId, Long userId); + @Transactional(readOnly = true) + @Query("SELECT cm FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.id IN :chatMemberIds") + List findByChatRoom_IdAndIdIn(Long chatRoomId, Set chatMemberIds); + @Transactional(readOnly = true) @Query("SELECT cm FROM ChatMember cm WHERE cm.chatRoom.id = :chatRoomId AND cm.user.id = :userId AND cm.deletedAt IS NULL") Optional findActiveChatMember(Long chatRoomId, Long userId); diff --git a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/service/ChatMemberService.java b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/service/ChatMemberService.java index 5bb0c4bee..d5cae851c 100644 --- a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/service/ChatMemberService.java +++ b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/member/service/ChatMemberService.java @@ -54,13 +54,18 @@ public Optional readChatMember(Long userId, Long chatRoomId) { } @Transactional(readOnly = true) - public List readChatMembersByMemberIdIn(Long chatRoomId, Set memberIds) { - return chatMemberRepository.findByChatRoom_IdAndUser_IdIn(chatRoomId, memberIds); + public List readChatMembersByIdIn(Long chatRoomId, Set chatMemberIds) { + return chatMemberRepository.findByChatRoom_IdAndIdIn(chatRoomId, chatMemberIds); } @Transactional(readOnly = true) - public List readChatMemberIdsByMemberIdNotIn(Long chatRoomId, Set memberIds) { - return chatMemberRepository.findByChatRoom_IdAndUser_IdNotIn(chatRoomId, memberIds); + public List readChatMembersByUserIdIn(Long chatRoomId, Set userIds) { + return chatMemberRepository.findByChatRoom_IdAndUser_IdIn(chatRoomId, userIds); + } + + @Transactional(readOnly = true) + public List readChatMemberIdsByUserIdNotIn(Long chatRoomId, Set userIds) { + return chatMemberRepository.findByChatRoom_IdAndUser_IdNotIn(chatRoomId, userIds); } @Transactional(readOnly = true)