From 4dbf59b76a27ed90431af655d6ed6ab07142dc04 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Fri, 29 Nov 2024 01:05:13 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EA=B8=B0=EA=B4=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기관 프로필 조회 엔드포인트를 위한 컨트롤러 구현 - center의 PK값에 columnDefinition = "BINARY(16)" 옵션을 넣어 타입을 더 명시적으로 나타냄 - 테스트 코드 작성및 검증완료 --- .../controller/CenterQueryApiController.java | 28 ++++++ .../com/somemore/center/domain/Center.java | 2 +- .../com/somemore/ControllerTestSupport.java | 21 +++++ .../CenterQueryApiControllerTest.java | 93 +++++++++++++++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/somemore/center/controller/CenterQueryApiController.java create mode 100644 src/test/java/com/somemore/ControllerTestSupport.java create mode 100644 src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java diff --git a/src/main/java/com/somemore/center/controller/CenterQueryApiController.java b/src/main/java/com/somemore/center/controller/CenterQueryApiController.java new file mode 100644 index 000000000..84a125955 --- /dev/null +++ b/src/main/java/com/somemore/center/controller/CenterQueryApiController.java @@ -0,0 +1,28 @@ +package com.somemore.center.controller; + +import com.somemore.center.dto.response.CenterProfileResponseDto; +import com.somemore.center.usecase.query.CenterQueryUseCase; +import com.somemore.global.common.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/center") +public class CenterQueryApiController { + + private final CenterQueryUseCase centerQueryUseCase; + + @GetMapping("/profile/{centerId}") + public ApiResponse getCenterProfile(@PathVariable UUID centerId) { + + CenterProfileResponseDto responseDto = centerQueryUseCase.getCenterProfileByCenterId(centerId); + + return ApiResponse.ok(200, responseDto, "기관 프로필 조회 성공"); + } +} diff --git a/src/main/java/com/somemore/center/domain/Center.java b/src/main/java/com/somemore/center/domain/Center.java index a13f964f2..a957772fd 100644 --- a/src/main/java/com/somemore/center/domain/Center.java +++ b/src/main/java/com/somemore/center/domain/Center.java @@ -14,7 +14,7 @@ public class Center extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) - @Column(nullable = false, length = 16) + @Column(nullable = false, columnDefinition = "BINARY(16)") private UUID id; @Column(name = "name", nullable = false) diff --git a/src/test/java/com/somemore/ControllerTestSupport.java b/src/test/java/com/somemore/ControllerTestSupport.java new file mode 100644 index 000000000..27537fc4a --- /dev/null +++ b/src/test/java/com/somemore/ControllerTestSupport.java @@ -0,0 +1,21 @@ +package com.somemore; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +public abstract class ControllerTestSupport { + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; +} diff --git a/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java new file mode 100644 index 000000000..7cc9e26fa --- /dev/null +++ b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java @@ -0,0 +1,93 @@ +package com.somemore.center.controller; + +import com.somemore.ControllerTestSupport; +import com.somemore.center.dto.response.CenterProfileResponseDto; +import com.somemore.center.usecase.query.CenterQueryUseCase; +import com.somemore.global.exception.BadRequestException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +class CenterQueryApiControllerTest extends ControllerTestSupport { + + @MockBean + protected CenterQueryUseCase centerQueryUseCase; + + private UUID centerId; + private CenterProfileResponseDto responseDto; + + @BeforeEach + void setUp() { + centerId = UUID.randomUUID(); + responseDto = CenterProfileResponseDto.builder() + .centerId(centerId) + .name("Test Center") + .contactNumber("010-1234-5678") + .imgUrl("http://example.com/image.jpg") + .introduce("This is a test center.") + .homepageLink("http://example.com") + .preferItems(List.of()) + .build(); + } + + @DisplayName("기관 ID로 기관 프로필을 조회할 수 있다. (controller)") + @Test + void getCenterProfile() throws Exception { + // given + when(centerQueryUseCase.getCenterProfileByCenterId(centerId)).thenReturn(responseDto); + + // when // then + mockMvc.perform( + get("/api/center/profile/{centerId}", centerId) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("기관 프로필 조회 성공")) + .andExpect(jsonPath("$.data.center_id").value(centerId.toString())) // center_id로 수정 + .andExpect(jsonPath("$.data.name").value("Test Center")) + .andExpect(jsonPath("$.data.contact_number").value("010-1234-5678")) // contact_number로 수정 + .andExpect(jsonPath("$.data.img_url").value("http://example.com/image.jpg")) // img_url로 수정 + .andExpect(jsonPath("$.data.introduce").value("This is a test center.")) + .andExpect(jsonPath("$.data.homepage_link").value("http://example.com")) // homepage_link로 수정 + .andExpect(jsonPath("$.data.prefer_items").isArray()); // prefer_items로 수정 + + verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(centerId); + } + + @DisplayName("존재하지 않는 기관 ID로 조회 시 예외가 발생한다. (controller)") + @Test + void getCenterProfile_NotFound() throws Exception { + // given + UUID nonExistentCenterId = UUID.randomUUID(); + when(centerQueryUseCase.getCenterProfileByCenterId(nonExistentCenterId)) + .thenThrow(new BadRequestException(NOT_EXISTS_CENTER.getMessage())); + + // when // then + mockMvc.perform( + get("/api/center/profile/{centerId}", nonExistentCenterId) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value("400")) + .andExpect(jsonPath("$.detail").value("존재하지 않는 기관 ID 입니다.")); + + verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(nonExistentCenterId); + } + +} + From cc00129dcbfa5afd7b54eba5db90593cc411fcc1 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Fri, 29 Nov 2024 01:05:13 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EA=B8=B0=EA=B4=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기관 프로필 조회 엔드포인트를 위한 컨트롤러 구현 - center의 PK값에 columnDefinition = "BINARY(16)" 옵션을 넣어 타입을 더 명시적으로 나타냄 - 테스트 코드 작성및 검증완료 --- .../controller/CenterQueryApiController.java | 28 ++++++ .../com/somemore/center/domain/Center.java | 2 +- .../com/somemore/ControllerTestSupport.java | 21 +++++ .../CenterQueryApiControllerTest.java | 93 +++++++++++++++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/somemore/center/controller/CenterQueryApiController.java create mode 100644 src/test/java/com/somemore/ControllerTestSupport.java create mode 100644 src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java diff --git a/src/main/java/com/somemore/center/controller/CenterQueryApiController.java b/src/main/java/com/somemore/center/controller/CenterQueryApiController.java new file mode 100644 index 000000000..84a125955 --- /dev/null +++ b/src/main/java/com/somemore/center/controller/CenterQueryApiController.java @@ -0,0 +1,28 @@ +package com.somemore.center.controller; + +import com.somemore.center.dto.response.CenterProfileResponseDto; +import com.somemore.center.usecase.query.CenterQueryUseCase; +import com.somemore.global.common.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/center") +public class CenterQueryApiController { + + private final CenterQueryUseCase centerQueryUseCase; + + @GetMapping("/profile/{centerId}") + public ApiResponse getCenterProfile(@PathVariable UUID centerId) { + + CenterProfileResponseDto responseDto = centerQueryUseCase.getCenterProfileByCenterId(centerId); + + return ApiResponse.ok(200, responseDto, "기관 프로필 조회 성공"); + } +} diff --git a/src/main/java/com/somemore/center/domain/Center.java b/src/main/java/com/somemore/center/domain/Center.java index a13f964f2..a957772fd 100644 --- a/src/main/java/com/somemore/center/domain/Center.java +++ b/src/main/java/com/somemore/center/domain/Center.java @@ -14,7 +14,7 @@ public class Center extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) - @Column(nullable = false, length = 16) + @Column(nullable = false, columnDefinition = "BINARY(16)") private UUID id; @Column(name = "name", nullable = false) diff --git a/src/test/java/com/somemore/ControllerTestSupport.java b/src/test/java/com/somemore/ControllerTestSupport.java new file mode 100644 index 000000000..27537fc4a --- /dev/null +++ b/src/test/java/com/somemore/ControllerTestSupport.java @@ -0,0 +1,21 @@ +package com.somemore; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +public abstract class ControllerTestSupport { + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; +} diff --git a/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java new file mode 100644 index 000000000..7cc9e26fa --- /dev/null +++ b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java @@ -0,0 +1,93 @@ +package com.somemore.center.controller; + +import com.somemore.ControllerTestSupport; +import com.somemore.center.dto.response.CenterProfileResponseDto; +import com.somemore.center.usecase.query.CenterQueryUseCase; +import com.somemore.global.exception.BadRequestException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +class CenterQueryApiControllerTest extends ControllerTestSupport { + + @MockBean + protected CenterQueryUseCase centerQueryUseCase; + + private UUID centerId; + private CenterProfileResponseDto responseDto; + + @BeforeEach + void setUp() { + centerId = UUID.randomUUID(); + responseDto = CenterProfileResponseDto.builder() + .centerId(centerId) + .name("Test Center") + .contactNumber("010-1234-5678") + .imgUrl("http://example.com/image.jpg") + .introduce("This is a test center.") + .homepageLink("http://example.com") + .preferItems(List.of()) + .build(); + } + + @DisplayName("기관 ID로 기관 프로필을 조회할 수 있다. (controller)") + @Test + void getCenterProfile() throws Exception { + // given + when(centerQueryUseCase.getCenterProfileByCenterId(centerId)).thenReturn(responseDto); + + // when // then + mockMvc.perform( + get("/api/center/profile/{centerId}", centerId) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("기관 프로필 조회 성공")) + .andExpect(jsonPath("$.data.center_id").value(centerId.toString())) // center_id로 수정 + .andExpect(jsonPath("$.data.name").value("Test Center")) + .andExpect(jsonPath("$.data.contact_number").value("010-1234-5678")) // contact_number로 수정 + .andExpect(jsonPath("$.data.img_url").value("http://example.com/image.jpg")) // img_url로 수정 + .andExpect(jsonPath("$.data.introduce").value("This is a test center.")) + .andExpect(jsonPath("$.data.homepage_link").value("http://example.com")) // homepage_link로 수정 + .andExpect(jsonPath("$.data.prefer_items").isArray()); // prefer_items로 수정 + + verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(centerId); + } + + @DisplayName("존재하지 않는 기관 ID로 조회 시 예외가 발생한다. (controller)") + @Test + void getCenterProfile_NotFound() throws Exception { + // given + UUID nonExistentCenterId = UUID.randomUUID(); + when(centerQueryUseCase.getCenterProfileByCenterId(nonExistentCenterId)) + .thenThrow(new BadRequestException(NOT_EXISTS_CENTER.getMessage())); + + // when // then + mockMvc.perform( + get("/api/center/profile/{centerId}", nonExistentCenterId) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value("400")) + .andExpect(jsonPath("$.detail").value("존재하지 않는 기관 ID 입니다.")); + + verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(nonExistentCenterId); + } + +} + From 17f9a12709403d0add41187b3f299c8483b1a651 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:29:45 +0900 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 예외 메세지 변경 - 컨트롤러와 API 엔드포인트에 스웨거 어노테이션 추가 - 응답 DTO에 스웨거 스키마 추 --- .../controller/CenterQueryApiController.java | 4 ++++ .../dto/response/CenterProfileResponseDto.java | 14 ++++++++++++++ .../global/exception/ExceptionMessage.java | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/somemore/center/controller/CenterQueryApiController.java b/src/main/java/com/somemore/center/controller/CenterQueryApiController.java index 84a125955..ef62e9d50 100644 --- a/src/main/java/com/somemore/center/controller/CenterQueryApiController.java +++ b/src/main/java/com/somemore/center/controller/CenterQueryApiController.java @@ -3,6 +3,8 @@ import com.somemore.center.dto.response.CenterProfileResponseDto; import com.somemore.center.usecase.query.CenterQueryUseCase; import com.somemore.global.common.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -14,10 +16,12 @@ @RequiredArgsConstructor @RestController @RequestMapping("/api/center") +@Tag(name = "Center Query API", description = "기관 관련 조회 API를 제공합니다.") public class CenterQueryApiController { private final CenterQueryUseCase centerQueryUseCase; + @Operation(summary = "기관 프로필 조회 API") @GetMapping("/profile/{centerId}") public ApiResponse getCenterProfile(@PathVariable UUID centerId) { diff --git a/src/main/java/com/somemore/center/dto/response/CenterProfileResponseDto.java b/src/main/java/com/somemore/center/dto/response/CenterProfileResponseDto.java index 7ccf73d63..5acb4713a 100644 --- a/src/main/java/com/somemore/center/dto/response/CenterProfileResponseDto.java +++ b/src/main/java/com/somemore/center/dto/response/CenterProfileResponseDto.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.somemore.center.domain.Center; import com.somemore.center.domain.PreferItem; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.util.List; @@ -12,12 +13,25 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @Builder public record CenterProfileResponseDto( + @Schema(description = "센터 ID", example = "123e4567-e89b-12d3-a456-426614174000") UUID centerId, + + @Schema(description = "센터 이름", example = "서울 도서관") String name, + + @Schema(description = "연락처", example = "010-1234-5678") String contactNumber, + + @Schema(description = "센터 이미지 URL", example = "https://example.com/images/center.jpg") String imgUrl, + + @Schema(description = "센터 소개", example = "저희 도서관은 유명해요") String introduce, + + @Schema(description = "센터 홈페이지 링크", example = "https://fitnesscenter.com") String homepageLink, + + @Schema(description = "선호 물품 리스트") List preferItems ) { public static CenterProfileResponseDto of(Center center, List preferItemDtos) { diff --git a/src/main/java/com/somemore/global/exception/ExceptionMessage.java b/src/main/java/com/somemore/global/exception/ExceptionMessage.java index 9a2f41794..01176f829 100644 --- a/src/main/java/com/somemore/global/exception/ExceptionMessage.java +++ b/src/main/java/com/somemore/global/exception/ExceptionMessage.java @@ -8,7 +8,7 @@ @Getter public enum ExceptionMessage { - NOT_EXISTS_CENTER("존재하지 않는 기관 ID 입니다."), + NOT_EXISTS_CENTER("존재하지 않는 기관 입니다."), NOT_EXISTS_COMMUNITY_BOARD("존재하지 않는 게시글 입니다."), UNAUTHORIZED_COMMUNITY_BOARD("해당 게시글에 권한이 없습니다."), NOT_EXISTS_COMMUNITY_COMMENT("존재하지 않는 댓글 입니다."), From b787d023de4222bf37889b7aed94c3fe89acbad1 Mon Sep 17 00:00:00 2001 From: seojin Yoon <90759319+7zrv@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:38:25 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CenterQueryApiControllerTest 예외 테스트의 예외 메세지를 수정 --- .../center/controller/CenterQueryApiControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java index 7cc9e26fa..904b231ea 100644 --- a/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java +++ b/src/test/java/com/somemore/center/controller/CenterQueryApiControllerTest.java @@ -84,7 +84,7 @@ void getCenterProfile_NotFound() throws Exception { .andDo(print()) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.status").value("400")) - .andExpect(jsonPath("$.detail").value("존재하지 않는 기관 ID 입니다.")); + .andExpect(jsonPath("$.detail").value("존재하지 않는 기관 입니다.")); verify(centerQueryUseCase, times(1)).getCenterProfileByCenterId(nonExistentCenterId); }