diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofController.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofController.kt index 87b1640..1ba96af 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofController.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofController.kt @@ -42,7 +42,7 @@ class GoalProofController( } @Operation(summary = "Retrieving GoalProofs API", description = "모든 다짐 인증을 조회합니다") - @GetMapping("goal/{goalId}/goal-proof") + @GetMapping("/goal/{goalId}/goal-proof") fun retrieveAll( @GetAuth userInfo: UserInfo, @PathVariable goalId: Long @@ -51,7 +51,7 @@ class GoalProofController( } @Operation(summary = "Retrieving GoalProofs' result API", description = "해당 다짐이 성공했는지 여부를 알려줍니다") - @GetMapping("goal/{goalId}/goal-proof/result") + @GetMapping("/goal/{goalId}/goal-proof/result") fun isGoalSuccess( @GetAuth userInfo: UserInfo, @PathVariable goalId: Long @@ -63,7 +63,7 @@ class GoalProofController( @PutMapping("/goal-proof/{goalProofId}") fun update( @PathVariable goalProofId: Long, - @RequestBody request: GoalProofUpdateRequest, + @Valid @RequestBody request: GoalProofUpdateRequest, @GetAuth userInfo: UserInfo ): Response { return Response.success( diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofDto.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofDto.kt index 735ec79..54a5c2a 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofDto.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofDto.kt @@ -5,6 +5,7 @@ import com.whatever.raisedragon.applicationservice.goalproof.dto.GoalProofUpdate import com.whatever.raisedragon.domain.gifticon.URL import com.whatever.raisedragon.domain.goalproof.Comment import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank @Schema(description = "[Request] 다짐 인증 생성") data class GoalProofCreateRequest( @@ -12,9 +13,11 @@ data class GoalProofCreateRequest( val goalId: Long, @Schema(description = "다짐 인증에 사용한 이미지 url") + @field:NotBlank(message = "URL은 공백이어서는 안됩니다.") val url: String, @Schema(description = "다짐 인증에 대한 부연설명") + @field:NotBlank(message = "Comment는 공백이어서는 안됩니다.") val comment: String ) @@ -30,9 +33,11 @@ fun GoalProofCreateRequest.toServiceRequest( @Schema(description = "[Request] 다짐 인증 수정") data class GoalProofUpdateRequest( @Schema(description = "다짐 인증에 사용한 이미지 url") + @field:NotBlank(message = "URL은 공백이어서는 안됩니다.") val url: String? = null, @Schema(description = "다짐 인증에 대한 부연설명") + @field:NotBlank(message = "Comment는 공백이어서는 안됩니다.") val comment: String? = null ) diff --git a/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/ControllerTestSupport.kt b/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/ControllerTestSupport.kt index c2f967b..50ee7ca 100644 --- a/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/ControllerTestSupport.kt +++ b/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/ControllerTestSupport.kt @@ -3,9 +3,10 @@ package com.whatever.raisedragon import com.fasterxml.jackson.databind.ObjectMapper import com.whatever.raisedragon.applicationservice.betting.BettingApplicationService import com.whatever.raisedragon.applicationservice.goalgifticon.GoalGifticonApplicationService +import com.whatever.raisedragon.applicationservice.goalproof.GoalProofApplicationService import com.whatever.raisedragon.controller.betting.BettingController import com.whatever.raisedragon.controller.goalgifticon.GoalGifticonController -import org.mockito.Mockito.* +import com.whatever.raisedragon.controller.goalproof.GoalProofController import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.mock.mockito.MockBean @@ -19,6 +20,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde controllers = [ BettingController::class, GoalGifticonController::class, + GoalProofController::class ] ) @ActiveProfiles("test") @@ -36,6 +38,9 @@ abstract class ControllerTestSupport { @MockBean protected lateinit var goalGifticonApplicationService: GoalGifticonApplicationService + @MockBean + protected lateinit var goalProofApplicationService: GoalProofApplicationService + protected fun MockHttpServletRequestBuilder.withCsrf(): MockHttpServletRequestBuilder { return with(SecurityMockMvcRequestPostProcessors.csrf()) } diff --git a/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofControllerTest.kt b/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofControllerTest.kt new file mode 100644 index 0000000..20ba8e6 --- /dev/null +++ b/raisedragon-api/src/test/kotlin/com/whatever/raisedragon/controller/goalproof/GoalProofControllerTest.kt @@ -0,0 +1,204 @@ +package com.whatever.raisedragon.controller.goalproof + +import com.whatever.raisedragon.ControllerTestSupport +import com.whatever.raisedragon.applicationservice.goalproof.dto.GoalProofListRetrieveResponse +import com.whatever.raisedragon.applicationservice.goalproof.dto.GoalProofRetrieveResponse +import com.whatever.raisedragon.security.WithCustomUser +import org.hamcrest.core.IsNull.nullValue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.mockito.Mockito.`when` +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@WithCustomUser(id = 1L, nickname = "User") +class GoalProofControllerTest : ControllerTestSupport() { + + @DisplayName("GoalProof를 생성한다.") + @Test + fun create() { + // given + val request = GoalProofCreateRequest(goalId = 1L, url = "www.sample.com", comment = "Sample Comment") + + // when // then + mockMvc + .perform( + post("/v1/goal-proof") + .withCsrf() + .writeRequestAsContent(request) + .contentTypeAsJson() + ) + .andDo(::print) + .andExpect(status().isCreated) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.errorResponse").value(nullValue())) + } + + @DisplayName("GoalProof를 생성할 때 URL은 공백이 아니다.") + @Test + fun createWithBlankUrl() { + // given + val request = GoalProofCreateRequest(goalId = 1L, url = "", comment = "Sample Comment") + + // when // then + mockMvc + .perform( + post("/v1/goal-proof") + .withCsrf() + .writeRequestAsContent(request) + .contentTypeAsJson() + ) + .andDo(::print) + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.errorResponse.code").value("400")) + .andExpect(jsonPath("$.errorResponse.detailMessage").value("URL은 공백이어서는 안됩니다.")) + .andExpect(jsonPath("$.data").isEmpty()) + } + + @DisplayName("GoalProof를 생성할 때 Comment는 공백이 아니다.") + @Test + fun createWithBlankComment() { + // given + val request = GoalProofCreateRequest(goalId = 1L, url = "www.sample.com", comment = "") + + // when // then + mockMvc + .perform( + post("/v1/goal-proof") + .withCsrf() + .writeRequestAsContent(request) + .contentTypeAsJson() + ) + .andDo(::print) + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.errorResponse.code").value("400")) + .andExpect(jsonPath("$.errorResponse.detailMessage").value("Comment는 공백이어서는 안됩니다.")) + .andExpect(jsonPath("$.data").isEmpty()) + } + + @DisplayName("GoalProof를 조회한다.") + @Test + fun retrieve() { + // given + val goalProofId = 1L + + // when // then + mockMvc + .perform( + get("/v1/goal-proof/$goalProofId") + ) + .andDo(::print) + .andExpect(status().isOk) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.errorResponse").value(nullValue())) + } + + @DisplayName("모든 GoalProof를 조회한다.") + @Test + fun retrieveAll() { + // given + val goalId = 1L + val mockUserId = 1L + val goalProofs = listOf() + val progressDays = listOf() + + `when`(goalProofApplicationService.retrieveAll(goalId, mockUserId)).thenReturn( + GoalProofListRetrieveResponse(goalProofs, progressDays) + ) + // when // then + mockMvc + .perform( + get("/v1/goal/$goalId/goal-proof") + ) + .andDo(::print) + .andExpect(status().isOk) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.errorResponse").value(nullValue())) + .andExpect(jsonPath("$.data.goalProofs").isArray) + .andExpect(jsonPath("$.data.progressDays").isArray) + } + + @DisplayName("해당 다짐이 성공했는지 여부를 조회한다.") + @Test + fun isGoalSuccess() { + // given + val goalId = 1L + + // when // then + mockMvc + .perform( + get("/v1/goal/$goalId/goal-proof/result") + ) + .andDo(::print) + .andExpect(status().isOk) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.errorResponse").value(nullValue())) + .andExpect(jsonPath("$.data").isBoolean) + } + + @DisplayName("GoalProof를 수정한다.") + @Test + fun update() { + // given + val request = GoalProofUpdateRequest("www.sample.com", "Sample Comment") + val goalProofId = 1L + + // when // then + mockMvc + .perform( + put("/v1/goal-proof/$goalProofId") + .withCsrf() + .writeRequestAsContent(request) + .contentTypeAsJson() + ) + .andDo(::print) + .andExpect(status().isOk) + .andExpect(jsonPath("$.errorResponse").value(nullValue())) + } + + @DisplayName("GoalProof를 수정할 때 URL은 공백이 아니다.") + @Test + fun updateWithBlankUrl() { + // given + val request = GoalProofUpdateRequest(url = "", comment = "Sample Comment") + val goalProofId = 1L + + // when // then + mockMvc + .perform( + put("/v1/goal-proof/$goalProofId") + + .withCsrf() + .writeRequestAsContent(request) + .contentTypeAsJson() + ) + .andDo(::print) + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.errorResponse.code").value("400")) + .andExpect(jsonPath("$.errorResponse.detailMessage").value("URL은 공백이어서는 안됩니다.")) + .andExpect(jsonPath("$.data").isEmpty()) + } + + @DisplayName("GoalProof를 수정할 때 Comment는 공백이 아니다.") + @Test + fun updateWithBlankComment() { + // given + val request = GoalProofUpdateRequest(url = "www.sample.com", comment = "") + val goalProofId = 1L + + // when // then + mockMvc + .perform( + put("/v1/goal-proof/$goalProofId") + .withCsrf() + .writeRequestAsContent(request) + .contentTypeAsJson() + ) + .andDo(::print) + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.errorResponse.code").value("400")) + .andExpect(jsonPath("$.errorResponse.detailMessage").value("Comment는 공백이어서는 안됩니다.")) + .andExpect(jsonPath("$.data").isEmpty()) + } +} \ No newline at end of file diff --git a/raisedragon-common/src/main/kotlin/com/whatever/raisedragon/common/exception/ExceptionCode.kt b/raisedragon-common/src/main/kotlin/com/whatever/raisedragon/common/exception/ExceptionCode.kt index be82755..60cf783 100644 --- a/raisedragon-common/src/main/kotlin/com/whatever/raisedragon/common/exception/ExceptionCode.kt +++ b/raisedragon-common/src/main/kotlin/com/whatever/raisedragon/common/exception/ExceptionCode.kt @@ -8,28 +8,28 @@ enum class ExceptionCode( val message: String, ) { - E400_BAD_REQUEST(HttpStatus.BAD_REQUEST, "000", "필수 파라미터 값이 없거나 잘못된 값으로 요청을 보낸 경우 발생"), + E400_BAD_REQUEST(HttpStatus.BAD_REQUEST, "400", "필수 파라미터 값이 없거나 잘못된 값으로 요청을 보낸 경우 발생"), // ------------------------------ 401 ------------------------------ - E401_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "000", "유효하지 않은 인증 토큰을 사용한 경우 발생"), + E401_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "401", "유효하지 않은 인증 토큰을 사용한 경우 발생"), // ------------------------------ 403 ------------------------------ - E403_FORBIDDEN(HttpStatus.FORBIDDEN, "000", "사용 권한이 없는 경우 발생"), + E403_FORBIDDEN(HttpStatus.FORBIDDEN, "403", "사용 권한이 없는 경우 발생"), // ------------------------------ 404 ------------------------------ - E404_NOT_FOUND(HttpStatus.NOT_FOUND, "000", "요청한 리소스가 존재하지 않는 경우 발생"), + E404_NOT_FOUND(HttpStatus.NOT_FOUND, "404", "요청한 리소스가 존재하지 않는 경우 발생"), // ------------------------------ 405 ------------------------------ - E405_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "000", "HTTP Method가 잘못된 경우"), + E405_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "405", "HTTP Method가 잘못된 경우"), // ------------------------------ 409 ------------------------------ - E409_CONFLICT(HttpStatus.CONFLICT, "000", "요청한 리소스가 중복된 경우 발생"), + E409_CONFLICT(HttpStatus.CONFLICT, "409", "요청한 리소스가 중복된 경우 발생"), // ------------------------------ 500 ------------------------------ - E500_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "000", "서버 내부에 문제 발생"), + E500_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500", "서버 내부에 문제 발생"), // ------------------------------ 501 ------------------------------ - E501_NOT_IMPLEMENTED(HttpStatus.NOT_IMPLEMENTED, "000", "지원하지 않는 타입의 요청"), + E501_NOT_IMPLEMENTED(HttpStatus.NOT_IMPLEMENTED, "501", "지원하지 않는 타입의 요청"), } fun ExceptionCode.throwAsException() {