From 610014533b274e708adc0a22eb898971c74d693c Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Mon, 29 Jan 2024 15:25:09 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20CartProduct=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/petqua/domain/cart/CartProduct.kt | 32 +++++++++++++++++++ .../domain/cart/CartProductRepository.kt | 5 +++ .../com/petqua/domain/cart/DeliveryMethod.kt | 10 ++++++ 3 files changed, 47 insertions(+) create mode 100644 src/main/kotlin/com/petqua/domain/cart/CartProduct.kt create mode 100644 src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt create mode 100644 src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt b/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt new file mode 100644 index 00000000..3a99947d --- /dev/null +++ b/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt @@ -0,0 +1,32 @@ +package com.petqua.domain.cart + +import com.petqua.common.domain.BaseEntity +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id + +@Entity +class CartProduct( + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L, + + @Column(nullable = false) + val memberId: Long, + + @Column(nullable = false) + val productId: Long, + + @Column(nullable = false) + val quantity: Int = 1, + + @Column(nullable = false) + val isMale: Boolean, + + @Enumerated(value = EnumType.STRING) + @Column(nullable = false) + val deliveryMethod: DeliveryMethod, +) : BaseEntity() diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt new file mode 100644 index 00000000..aa2c5cbb --- /dev/null +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt @@ -0,0 +1,5 @@ +package com.petqua.domain.cart + +import org.springframework.data.jpa.repository.JpaRepository + +interface CartProductRepository : JpaRepository diff --git a/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt b/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt new file mode 100644 index 00000000..289454b5 --- /dev/null +++ b/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt @@ -0,0 +1,10 @@ +package com.petqua.domain.cart + +enum class DeliveryMethod( + val description: String, +) { + + COMMON("일반 운송"), + SAFETY("안전 운송"), + PICK_UP("직접 방문"), +} From 042708bab1c302c1b1e5aea0e4c358a99a74b578 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Mon, 29 Jan 2024 16:26:20 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20Entity=20=EC=A1=B4=EC=9E=AC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20=ED=99=95=EC=9E=A5=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/petqua/common/domain/Repository.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/com/petqua/common/domain/Repository.kt b/src/main/kotlin/com/petqua/common/domain/Repository.kt index be73ec19..64c5b9e1 100644 --- a/src/main/kotlin/com/petqua/common/domain/Repository.kt +++ b/src/main/kotlin/com/petqua/common/domain/Repository.kt @@ -6,3 +6,9 @@ import org.springframework.data.repository.findByIdOrNull inline fun CrudRepository.findByIdOrThrow( id: ID, e: Exception = IllegalArgumentException("${T::class.java.name} entity 를 찾을 수 없습니다. id=$id") ): T = findByIdOrNull(id) ?: throw e + + +inline fun CrudRepository.existByIdOrThrow( + id: ID, + e: Exception = IllegalArgumentException("${T::class.java.name} entity 를 찾을 수 없습니다. id=$id") +): Unit = if (!existsById(id!!)) throw e else Unit From 08c516831f5e7bbb8776decc7c2e963aa2a498af Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Mon, 29 Jan 2024 16:26:48 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20CartProduct=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/cart/CartProductService.kt | 24 ++++++++++ .../application/cart/dto/CartProductDtos.kt | 22 ++++++++++ .../cart/CartProductServiceTest.kt | 44 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 src/main/kotlin/com/petqua/application/cart/CartProductService.kt create mode 100644 src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt create mode 100644 src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt diff --git a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt new file mode 100644 index 00000000..585445b6 --- /dev/null +++ b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt @@ -0,0 +1,24 @@ +package com.petqua.application.cart + +import com.petqua.application.cart.dto.SaveCartProductCommand +import com.petqua.common.domain.existByIdOrThrow +import com.petqua.domain.cart.CartProductRepository +import com.petqua.domain.product.ProductRepository +import com.petqua.exception.product.ProductException +import com.petqua.exception.product.ProductExceptionType.NOT_FOUND_PRODUCT +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Transactional +@Service +class CartProductService( + private val cartProductRepository: CartProductRepository, + private val productRepository: ProductRepository, +) { + + fun save(command: SaveCartProductCommand) { +// TODO memberRepository.existByIdOrThrow(command.memberId, MemberException(NOT_FOUND_MEMBER)) + productRepository.existByIdOrThrow(command.productId, ProductException(NOT_FOUND_PRODUCT)) + cartProductRepository.save(command.toCartProduct()) + } +} diff --git a/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt b/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt new file mode 100644 index 00000000..f70d8560 --- /dev/null +++ b/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt @@ -0,0 +1,22 @@ +package com.petqua.application.cart.dto + +import com.petqua.domain.cart.CartProduct +import com.petqua.domain.cart.DeliveryMethod + +data class SaveCartProductCommand( + val memberId: Long, + val productId: Long, + val quantity: Int, + val isMale: Boolean, + val deliveryMethod: DeliveryMethod, +) { + fun toCartProduct(): CartProduct { + return CartProduct( + memberId = memberId, + productId = productId, + quantity = quantity, + isMale = isMale, + deliveryMethod = deliveryMethod, + ) + } +} diff --git a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt new file mode 100644 index 00000000..a385b324 --- /dev/null +++ b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt @@ -0,0 +1,44 @@ +package com.petqua.application.cart + +import com.petqua.application.cart.dto.SaveCartProductCommand +import com.petqua.domain.cart.CartProductRepository +import com.petqua.domain.cart.DeliveryMethod +import com.petqua.domain.product.ProductRepository +import com.petqua.test.DataCleaner +import com.petqua.test.fixture.product +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class CartProductServiceTest( + private val cartProductService: CartProductService, + private val cartProductRepository: CartProductRepository, + private val productRepository: ProductRepository, + private val dataCleaner: DataCleaner, +) : BehaviorSpec({ + + Given("봉달 상품 저장 명령으로") { + val productId = productRepository.save(product(id = 1L)).id + val memberId = 1L + val command = SaveCartProductCommand( + memberId = memberId, + productId = productId, + quantity = 1, + isMale = true, + deliveryMethod = DeliveryMethod.COMMON, + ) + + When("봉달 상품을") { + cartProductService.save(command) + + Then("저장할 수 있다") { + cartProductRepository.findAll().size shouldBe 1 + } + } + } + + afterContainer { + dataCleaner.clean() + } +}) From 28bf424803ab4df5168ca42f80d5b4171e25de0d Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Mon, 29 Jan 2024 16:27:33 +0900 Subject: [PATCH 04/21] =?UTF-8?q?refactor:=20ProductController=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../petqua/presentation/{ => product}/ProductController.kt | 2 +- .../domain/{ => product}/ProductCustomRepositoryImplTest.kt | 5 ++--- .../kotlin/com/petqua/domain/{ => product}/ProductTest.kt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename src/main/kotlin/com/petqua/presentation/{ => product}/ProductController.kt (96%) rename src/test/kotlin/com/petqua/domain/{ => product}/ProductCustomRepositoryImplTest.kt (98%) rename src/test/kotlin/com/petqua/domain/{ => product}/ProductTest.kt (94%) diff --git a/src/main/kotlin/com/petqua/presentation/ProductController.kt b/src/main/kotlin/com/petqua/presentation/product/ProductController.kt similarity index 96% rename from src/main/kotlin/com/petqua/presentation/ProductController.kt rename to src/main/kotlin/com/petqua/presentation/product/ProductController.kt index 5d7f11f0..2c8aedf7 100644 --- a/src/main/kotlin/com/petqua/presentation/ProductController.kt +++ b/src/main/kotlin/com/petqua/presentation/product/ProductController.kt @@ -1,4 +1,4 @@ -package com.petqua.presentation +package com.petqua.presentation.product import com.petqua.application.product.ProductService import com.petqua.application.product.dto.ProductDetailResponse diff --git a/src/test/kotlin/com/petqua/domain/ProductCustomRepositoryImplTest.kt b/src/test/kotlin/com/petqua/domain/product/ProductCustomRepositoryImplTest.kt similarity index 98% rename from src/test/kotlin/com/petqua/domain/ProductCustomRepositoryImplTest.kt rename to src/test/kotlin/com/petqua/domain/product/ProductCustomRepositoryImplTest.kt index 7cafd5f8..8fdf3ae7 100644 --- a/src/test/kotlin/com/petqua/domain/ProductCustomRepositoryImplTest.kt +++ b/src/test/kotlin/com/petqua/domain/product/ProductCustomRepositoryImplTest.kt @@ -1,6 +1,5 @@ -package com.petqua.domain +package com.petqua.domain.product -import com.petqua.domain.product.ProductRepository import com.petqua.domain.product.ProductSourceType.HOME_RECOMMENDED import com.petqua.domain.product.Sorter.ENROLLMENT_DATE_DESC import com.petqua.domain.product.Sorter.REVIEW_COUNT_DESC @@ -19,10 +18,10 @@ import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe -import org.springframework.boot.test.context.SpringBootTest import java.math.BigDecimal.ONE import java.math.BigDecimal.TEN import java.math.BigDecimal.ZERO +import org.springframework.boot.test.context.SpringBootTest @SpringBootTest class ProductCustomRepositoryImplTest( diff --git a/src/test/kotlin/com/petqua/domain/ProductTest.kt b/src/test/kotlin/com/petqua/domain/product/ProductTest.kt similarity index 94% rename from src/test/kotlin/com/petqua/domain/ProductTest.kt rename to src/test/kotlin/com/petqua/domain/product/ProductTest.kt index ec0b41bc..76cd9528 100644 --- a/src/test/kotlin/com/petqua/domain/ProductTest.kt +++ b/src/test/kotlin/com/petqua/domain/product/ProductTest.kt @@ -1,4 +1,4 @@ -package com.petqua.domain +package com.petqua.domain.product import com.petqua.test.fixture.product import io.kotest.core.spec.style.BehaviorSpec From 31f9f2d2703e89ca8432f3ceb9205ae5a0ab0c50 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Mon, 29 Jan 2024 17:15:52 +0900 Subject: [PATCH 05/21] =?UTF-8?q?feat:=20CartProduct=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/cart/CartProductService.kt | 5 +- .../com/petqua/domain/cart/DeliveryMethod.kt | 14 ++++- .../exception/cart/CartProductException.kt | 13 +++++ .../cart/CartProductExceptionType.kt | 22 ++++++++ .../cart/CartProductController.kt | 34 ++++++++++++ .../presentation/cart/dto/CartProductDtos.kt | 22 ++++++++ .../cart/CartProductControllerTest.kt | 53 +++++++++++++++++++ 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/petqua/exception/cart/CartProductException.kt create mode 100644 src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt create mode 100644 src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt create mode 100644 src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt create mode 100644 src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt diff --git a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt index 585445b6..4fc943dc 100644 --- a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt +++ b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt @@ -16,9 +16,10 @@ class CartProductService( private val productRepository: ProductRepository, ) { - fun save(command: SaveCartProductCommand) { + fun save(command: SaveCartProductCommand): Long { // TODO memberRepository.existByIdOrThrow(command.memberId, MemberException(NOT_FOUND_MEMBER)) productRepository.existByIdOrThrow(command.productId, ProductException(NOT_FOUND_PRODUCT)) - cartProductRepository.save(command.toCartProduct()) + val savedCartProduct = cartProductRepository.save(command.toCartProduct()) + return savedCartProduct.id } } diff --git a/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt b/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt index 289454b5..0bfb1ef7 100644 --- a/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt +++ b/src/main/kotlin/com/petqua/domain/cart/DeliveryMethod.kt @@ -1,10 +1,22 @@ package com.petqua.domain.cart +import com.petqua.exception.cart.CartProductException +import com.petqua.exception.cart.CartProductExceptionType.INVALID_DELIVERY_METHOD +import java.util.Locale.ENGLISH + enum class DeliveryMethod( val description: String, ) { - + COMMON("일반 운송"), SAFETY("안전 운송"), PICK_UP("직접 방문"), + ; + + companion object { + fun from(name: String): DeliveryMethod { + return enumValues().find { it.name == name.uppercase(ENGLISH) } + ?: throw CartProductException(INVALID_DELIVERY_METHOD) + } + } } diff --git a/src/main/kotlin/com/petqua/exception/cart/CartProductException.kt b/src/main/kotlin/com/petqua/exception/cart/CartProductException.kt new file mode 100644 index 00000000..aa8ba83e --- /dev/null +++ b/src/main/kotlin/com/petqua/exception/cart/CartProductException.kt @@ -0,0 +1,13 @@ +package com.petqua.exception.cart + +import com.petqua.common.exception.BaseException +import com.petqua.common.exception.BaseExceptionType + +class CartProductException( + private val exceptionType: CartProductExceptionType, +) : BaseException() { + + override fun exceptionType(): BaseExceptionType { + return exceptionType + } +} diff --git a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt new file mode 100644 index 00000000..51865a4e --- /dev/null +++ b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt @@ -0,0 +1,22 @@ +package com.petqua.exception.cart + +import com.petqua.common.exception.BaseExceptionType +import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatus.NOT_FOUND + +enum class CartProductExceptionType( + private val httpStatus: HttpStatus, + private val errorMessage: String, +) : BaseExceptionType { + + INVALID_DELIVERY_METHOD(httpStatus = NOT_FOUND, errorMessage = "유효하지 않는 배송 방법입니다.") + ; + + override fun httpStatus(): HttpStatus { + return httpStatus + } + + override fun errorMessage(): String { + return errorMessage + } +} diff --git a/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt b/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt new file mode 100644 index 00000000..be81aa31 --- /dev/null +++ b/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt @@ -0,0 +1,34 @@ +package com.petqua.presentation.cart + +import com.petqua.application.cart.CartProductService +import com.petqua.application.product.dto.ProductDetailResponse +import com.petqua.presentation.cart.dto.SaveCartProductRequest +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.servlet.support.ServletUriComponentsBuilder + +@RequestMapping("/carts") +@RestController +class CartProductController( + private val cartProductService: CartProductService, +) { + + @PostMapping + fun save( +// @AuthMember member: Long, TODO: AuthMember + @RequestHeader("X-MEMBER-ID") memberId: Long, + @RequestBody request: SaveCartProductRequest + ): ResponseEntity { + val command = request.toCommand(memberId) + val cartProductId = cartProductService.save(command) + val location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/items/{id}") + .buildAndExpand(cartProductId) + .toUri() + return ResponseEntity.created(location).build() + } +} diff --git a/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt b/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt new file mode 100644 index 00000000..2e308b65 --- /dev/null +++ b/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt @@ -0,0 +1,22 @@ +package com.petqua.presentation.cart.dto + +import com.petqua.application.cart.dto.SaveCartProductCommand +import com.petqua.domain.cart.DeliveryMethod + +data class SaveCartProductRequest( + val productId: Long, + val quantity: Int, + val isMale: Boolean, + val deliveryMethod: String, +) { + + fun toCommand(memberId: Long): SaveCartProductCommand { + return SaveCartProductCommand( + memberId = memberId, + productId = productId, + quantity = quantity, + isMale = isMale, + deliveryMethod = DeliveryMethod.valueOf(deliveryMethod) + ) + } +} diff --git a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt new file mode 100644 index 00000000..63c6ae5a --- /dev/null +++ b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt @@ -0,0 +1,53 @@ +package com.petqua.presentation.cart + +import com.petqua.domain.product.ProductRepository +import com.petqua.presentation.cart.dto.SaveCartProductRequest +import com.petqua.test.ApiTestConfig +import com.petqua.test.fixture.product +import io.restassured.module.kotlin.extensions.Extract +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import org.assertj.core.api.SoftAssertions.assertSoftly +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus + +private const val AUTHORIZATION_HEADER = "X-MEMBER-ID" // FIXME: 인가 구현 완료 후 변경 예정 + +class CartProductControllerTest( + private val productRepository: ProductRepository +) : ApiTestConfig() { + init { + val savedProduct = productRepository.save(product(id = 1L)) + Given("봉달에 상품 저장을") { + val request = SaveCartProductRequest( + productId = savedProduct.id, + quantity = 1, + isMale = true, + deliveryMethod = "SAFETY" + ) + When("요청 하면") { + val response = Given { + log().all() + .body(request) + .header(AUTHORIZATION_HEADER, 1L) + .contentType("application/json") + } When { + post("/carts") + } Then { + log().all() + } Extract { + response() + } + + Then("배너 목록을 응답한다.") { + assertSoftly { + it.assertThat(response.statusCode).isEqualTo(HttpStatus.CREATED.value()) + it.assertThat(response.header(HttpHeaders.LOCATION)).contains("/carts/items") + + } + } + } + } + } +} From 839b8dc2351d4a19b30dc059ad0a44a94e021936 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Mon, 29 Jan 2024 17:31:57 +0900 Subject: [PATCH 06/21] =?UTF-8?q?test:=20CartProduct=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EC=83=81=ED=99=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cart/CartProductExceptionType.kt | 4 +- .../presentation/cart/dto/CartProductDtos.kt | 2 +- .../cart/CartProductControllerTest.kt | 62 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt index 51865a4e..de38e910 100644 --- a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt +++ b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt @@ -2,14 +2,14 @@ package com.petqua.exception.cart import com.petqua.common.exception.BaseExceptionType import org.springframework.http.HttpStatus -import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.http.HttpStatus.BAD_REQUEST enum class CartProductExceptionType( private val httpStatus: HttpStatus, private val errorMessage: String, ) : BaseExceptionType { - INVALID_DELIVERY_METHOD(httpStatus = NOT_FOUND, errorMessage = "유효하지 않는 배송 방법입니다.") + INVALID_DELIVERY_METHOD(httpStatus = BAD_REQUEST, errorMessage = "유효하지 않는 배송 방법입니다.") ; override fun httpStatus(): HttpStatus { diff --git a/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt b/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt index 2e308b65..a4ef4bb0 100644 --- a/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt +++ b/src/main/kotlin/com/petqua/presentation/cart/dto/CartProductDtos.kt @@ -16,7 +16,7 @@ data class SaveCartProductRequest( productId = productId, quantity = quantity, isMale = isMale, - deliveryMethod = DeliveryMethod.valueOf(deliveryMethod) + deliveryMethod = DeliveryMethod.from(deliveryMethod) ) } } diff --git a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt index 63c6ae5a..181cddd9 100644 --- a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt +++ b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt @@ -1,6 +1,9 @@ package com.petqua.presentation.cart +import com.petqua.common.exception.ExceptionResponse import com.petqua.domain.product.ProductRepository +import com.petqua.exception.cart.CartProductExceptionType.INVALID_DELIVERY_METHOD +import com.petqua.exception.product.ProductExceptionType.NOT_FOUND_PRODUCT import com.petqua.presentation.cart.dto.SaveCartProductRequest import com.petqua.test.ApiTestConfig import com.petqua.test.fixture.product @@ -44,7 +47,66 @@ class CartProductControllerTest( assertSoftly { it.assertThat(response.statusCode).isEqualTo(HttpStatus.CREATED.value()) it.assertThat(response.header(HttpHeaders.LOCATION)).contains("/carts/items") + } + } + } + } + + Given("봉달에 상품 저장 요청시") { + When("지원하지 않는 배송 방식으로 요청 하면") { + val request = SaveCartProductRequest( + productId = savedProduct.id, + quantity = 1, + isMale = true, + deliveryMethod = "NOT_SUPPORTED" + ) + val response = Given { + log().all() + .body(request) + .header(AUTHORIZATION_HEADER, 1L) + .contentType("application/json") + } When { + post("/carts") + } Then { + log().all() + } Extract { + response() + } + + Then("예외가 발생한다") { + val errorResponse = response.`as`(ExceptionResponse::class.java) + assertSoftly { + it.assertThat(response.statusCode).isEqualTo(HttpStatus.BAD_REQUEST.value()) + it.assertThat(errorResponse.message).isEqualTo(INVALID_DELIVERY_METHOD.errorMessage()) + } + } + } + + When("존재 하지 않는 상품 저장을 요청 하면") { + val request = SaveCartProductRequest( + productId = 999L, + quantity = 1, + isMale = true, + deliveryMethod = "SAFETY" + ) + val response = Given { + log().all() + .body(request) + .header(AUTHORIZATION_HEADER, 1L) + .contentType("application/json") + } When { + post("/carts") + } Then { + log().all() + } Extract { + response() + } + Then("예외가 발생한다") { + val errorResponse = response.`as`(ExceptionResponse::class.java) + assertSoftly { + it.assertThat(response.statusCode).isEqualTo(HttpStatus.NOT_FOUND.value()) + it.assertThat(errorResponse.message).isEqualTo(NOT_FOUND_PRODUCT.errorMessage()) } } } From e0bf5b3119f34643d37209145f7c062c53282287 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 00:32:01 +0900 Subject: [PATCH 07/21] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=9C?= =?UTF-8?q?=20dependency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e498957a..6fb954ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,26 +28,20 @@ dependencies { implementation("io.jsonwebtoken:jjwt-api:0.11.5") implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.jsonwebtoken:jjwt-jackson:0.11.5") + implementation("io.jsonwebtoken:jjwt-api:0.11.5") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") // kotlin jdsl implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.3.0") implementation("com.linecorp.kotlin-jdsl:jpql-render:3.3.0") + // spring boot cache implementation("org.springframework.boot:spring-boot-starter-cache") - implementation("io.jsonwebtoken:jjwt-api:0.11.5") - - // kotlin jdsl - implementation("com.linecorp.kotlin-jdsl:jpql-dsl:3.3.0") - implementation("com.linecorp.kotlin-jdsl:jpql-render:3.3.0") - - implementation("org.springframework.boot:spring-boot-starter-cache") runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.h2database:h2") - annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("io.kotest:kotest-runner-junit5:5.4.2") testImplementation("io.kotest:kotest-assertions-core:5.4.2") From 9f677d7dbbff497fc4fa2b12b8492433b882af7c Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 00:33:12 +0900 Subject: [PATCH 08/21] =?UTF-8?q?chore:=20mockk=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 6fb954ff..82617306 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.h2database:h2") + testImplementation("io.mockk:mockk:1.13.9") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("io.kotest:kotest-runner-junit5:5.4.2") testImplementation("io.kotest:kotest-assertions-core:5.4.2") From d942e40a7e824480df6204c2d37136f2c0e4b671 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 00:49:56 +0900 Subject: [PATCH 09/21] =?UTF-8?q?test:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=85=8B=EC=97=85=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/petqua/test/ApiTestConfig.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/kotlin/com/petqua/test/ApiTestConfig.kt b/src/test/kotlin/com/petqua/test/ApiTestConfig.kt index da7538c7..069be5c5 100644 --- a/src/test/kotlin/com/petqua/test/ApiTestConfig.kt +++ b/src/test/kotlin/com/petqua/test/ApiTestConfig.kt @@ -1,12 +1,21 @@ package com.petqua.test +import com.petqua.application.auth.AuthResponse +import com.petqua.test.config.OauthTestConfig import io.kotest.core.spec.style.BehaviorSpec import io.restassured.RestAssured +import io.restassured.module.kotlin.extensions.Extract +import io.restassured.module.kotlin.extensions.Given +import io.restassured.module.kotlin.extensions.Then +import io.restassured.module.kotlin.extensions.When +import io.restassured.response.Response import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.boot.test.web.server.LocalServerPort +import org.springframework.context.annotation.Import +@Import(OauthTestConfig::class) @SpringBootTest(webEnvironment = RANDOM_PORT) abstract class ApiTestConfig : BehaviorSpec() { @@ -25,4 +34,22 @@ abstract class ApiTestConfig : BehaviorSpec() { dataCleaner.clean() } } + + final fun signInAsMember(): AuthResponse { + val response = requestSignIn() + return response.`as`(AuthResponse::class.java) + } + + private fun requestSignIn(): Response { + return Given { + log().all() + .queryParam("code", "code") + } When { + get("/oauth/login/{oauthServerType}", "kakao") + } Then { + log().all() + } Extract { + response() + } + } } From 82b7decd47c6c76fc9b0d43d425bc024110ce0bc Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 01:00:52 +0900 Subject: [PATCH 10/21] =?UTF-8?q?test:=20=EA=B5=AC=ED=98=84=EB=90=9C=20Aut?= =?UTF-8?q?h=20=ED=97=A4=EB=8D=94=20=EC=82=AC=EC=9A=A9=20=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20CartProduct=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../petqua/presentation/cart/CartProductController.kt | 8 ++++---- .../presentation/cart/CartProductControllerTest.kt | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt b/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt index be81aa31..d8cf4ab7 100644 --- a/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt +++ b/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt @@ -2,11 +2,12 @@ package com.petqua.presentation.cart import com.petqua.application.cart.CartProductService import com.petqua.application.product.dto.ProductDetailResponse +import com.petqua.domain.auth.Accessor +import com.petqua.domain.auth.Auth import com.petqua.presentation.cart.dto.SaveCartProductRequest import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.servlet.support.ServletUriComponentsBuilder @@ -19,11 +20,10 @@ class CartProductController( @PostMapping fun save( -// @AuthMember member: Long, TODO: AuthMember - @RequestHeader("X-MEMBER-ID") memberId: Long, + @Auth accessor: Accessor, @RequestBody request: SaveCartProductRequest ): ResponseEntity { - val command = request.toCommand(memberId) + val command = request.toCommand(accessor.memberId) val cartProductId = cartProductService.save(command) val location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/items/{id}") diff --git a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt index 181cddd9..b8fa8b6e 100644 --- a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt +++ b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt @@ -13,14 +13,14 @@ import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.When import org.assertj.core.api.SoftAssertions.assertSoftly import org.springframework.http.HttpHeaders +import org.springframework.http.HttpHeaders.AUTHORIZATION import org.springframework.http.HttpStatus -private const val AUTHORIZATION_HEADER = "X-MEMBER-ID" // FIXME: 인가 구현 완료 후 변경 예정 - class CartProductControllerTest( private val productRepository: ProductRepository ) : ApiTestConfig() { init { + val memberAuthResponse = signInAsMember() val savedProduct = productRepository.save(product(id = 1L)) Given("봉달에 상품 저장을") { val request = SaveCartProductRequest( @@ -33,7 +33,7 @@ class CartProductControllerTest( val response = Given { log().all() .body(request) - .header(AUTHORIZATION_HEADER, 1L) + .header(AUTHORIZATION, memberAuthResponse.accessToken) .contentType("application/json") } When { post("/carts") @@ -63,7 +63,7 @@ class CartProductControllerTest( val response = Given { log().all() .body(request) - .header(AUTHORIZATION_HEADER, 1L) + .header(AUTHORIZATION, memberAuthResponse.accessToken) .contentType("application/json") } When { post("/carts") @@ -92,7 +92,7 @@ class CartProductControllerTest( val response = Given { log().all() .body(request) - .header(AUTHORIZATION_HEADER, 1L) + .header(AUTHORIZATION, memberAuthResponse.accessToken) .contentType("application/json") } When { post("/carts") From 6fa783dd99903afb3bf3194f9caba21de31c970d Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 15:19:17 +0900 Subject: [PATCH 11/21] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=9C?= =?UTF-8?q?=20dependency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 82617306..dbd6c011 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,15 +20,23 @@ repositories { } dependencies { + // kotlin + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + + // spring data jpa implementation("org.springframework.boot:spring-boot-starter-data-jpa") + + // spring boot web implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-webflux") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation("org.jetbrains.kotlin:kotlin-reflect") + + // jwt implementation("io.jsonwebtoken:jjwt-api:0.11.5") implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.jsonwebtoken:jjwt-jackson:0.11.5") - implementation("io.jsonwebtoken:jjwt-api:0.11.5") + + // annotation processor annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") // kotlin jdsl @@ -37,8 +45,7 @@ dependencies { // spring boot cache implementation("org.springframework.boot:spring-boot-starter-cache") - - + runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.h2database:h2") From 842404ee0fece274149a05de8ced036dd8534751 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 15:22:48 +0900 Subject: [PATCH 12/21] =?UTF-8?q?refactor:=20Accessor=20->=20LoginMember?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/{Accessor.kt => LoginMember.kt} | 6 +++--- .../presentation/auth/LoginArgumentResolver.kt | 14 +++++++------- .../presentation/cart/CartProductController.kt | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) rename src/main/kotlin/com/petqua/domain/auth/{Accessor.kt => LoginMember.kt} (72%) diff --git a/src/main/kotlin/com/petqua/domain/auth/Accessor.kt b/src/main/kotlin/com/petqua/domain/auth/LoginMember.kt similarity index 72% rename from src/main/kotlin/com/petqua/domain/auth/Accessor.kt rename to src/main/kotlin/com/petqua/domain/auth/LoginMember.kt index ed523c56..544610e8 100644 --- a/src/main/kotlin/com/petqua/domain/auth/Accessor.kt +++ b/src/main/kotlin/com/petqua/domain/auth/LoginMember.kt @@ -2,14 +2,14 @@ package com.petqua.domain.auth import com.petqua.domain.auth.token.AccessTokenClaims -class Accessor( +class LoginMember( val memberId: Long, val authority: Authority, ) { companion object { - fun from(accessTokenClaims: AccessTokenClaims): Accessor { - return Accessor( + fun from(accessTokenClaims: AccessTokenClaims): LoginMember { + return LoginMember( memberId = accessTokenClaims.memberId, authority = accessTokenClaims.authority ) diff --git a/src/main/kotlin/com/petqua/presentation/auth/LoginArgumentResolver.kt b/src/main/kotlin/com/petqua/presentation/auth/LoginArgumentResolver.kt index a75e0aa4..6e927c24 100644 --- a/src/main/kotlin/com/petqua/presentation/auth/LoginArgumentResolver.kt +++ b/src/main/kotlin/com/petqua/presentation/auth/LoginArgumentResolver.kt @@ -2,8 +2,8 @@ package com.petqua.presentation.auth import com.petqua.common.exception.auth.AuthException import com.petqua.common.exception.auth.AuthExceptionType -import com.petqua.domain.auth.Accessor import com.petqua.domain.auth.Auth +import com.petqua.domain.auth.LoginMember import com.petqua.domain.auth.token.AuthTokenProvider import com.petqua.domain.auth.token.RefreshTokenRepository import jakarta.servlet.http.HttpServletRequest @@ -25,7 +25,7 @@ class LoginArgumentResolver( override fun supportsParameter(parameter: MethodParameter): Boolean { return parameter.hasParameterAnnotation(Auth::class.java) - && parameter.getParameterType() == Accessor::class.java + && parameter.getParameterType() == LoginMember::class.java } override fun resolveArgument( @@ -33,21 +33,21 @@ class LoginArgumentResolver( mavContainer: ModelAndViewContainer?, webRequest: NativeWebRequest, binderFactory: WebDataBinderFactory? - ): Accessor { + ): LoginMember { val request = webRequest.getNativeRequest(HttpServletRequest::class.java) ?: throw AuthException(AuthExceptionType.INVALID_REQUEST) - val refreshToken = request.cookies?.find {it.name == REFRESH_TOKEN_COOKIE}?.value + val refreshToken = request.cookies?.find { it.name == REFRESH_TOKEN_COOKIE }?.value val accessToken = webRequest.getHeader(HttpHeaders.AUTHORIZATION) as String val accessTokenClaims = authTokenProvider.getAccessTokenClaims(accessToken) if (refreshToken == null) { - return Accessor.from(accessTokenClaims) + return LoginMember.from(accessTokenClaims) } val savedRefreshToken = refreshTokenRepository.findByMemberId(accessTokenClaims.memberId) ?: throw AuthException(AuthExceptionType.INVALID_REFRESH_TOKEN) if (savedRefreshToken.token == refreshToken) { - return Accessor.from(accessTokenClaims) + return LoginMember.from(accessTokenClaims) } throw AuthException(AuthExceptionType.INVALID_REFRESH_TOKEN) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt b/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt index d8cf4ab7..881dd576 100644 --- a/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt +++ b/src/main/kotlin/com/petqua/presentation/cart/CartProductController.kt @@ -2,8 +2,8 @@ package com.petqua.presentation.cart import com.petqua.application.cart.CartProductService import com.petqua.application.product.dto.ProductDetailResponse -import com.petqua.domain.auth.Accessor import com.petqua.domain.auth.Auth +import com.petqua.domain.auth.LoginMember import com.petqua.presentation.cart.dto.SaveCartProductRequest import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -20,10 +20,10 @@ class CartProductController( @PostMapping fun save( - @Auth accessor: Accessor, + @Auth loginMember: LoginMember, @RequestBody request: SaveCartProductRequest ): ResponseEntity { - val command = request.toCommand(accessor.memberId) + val command = request.toCommand(loginMember.memberId) val cartProductId = cartProductService.save(command) val location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/items/{id}") From 830ac6835d95ae5055fbdd6ff285a1946b468488 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 15:23:47 +0900 Subject: [PATCH 13/21] =?UTF-8?q?test:=20=EA=B2=80=EC=A6=9D=EB=B6=80=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/petqua/presentation/cart/CartProductControllerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt index b8fa8b6e..8aa966dc 100644 --- a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt +++ b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt @@ -43,7 +43,7 @@ class CartProductControllerTest( response() } - Then("배너 목록을 응답한다.") { + Then("봉달 목록에 상품이 저장된다") { assertSoftly { it.assertThat(response.statusCode).isEqualTo(HttpStatus.CREATED.value()) it.assertThat(response.header(HttpHeaders.LOCATION)).contains("/carts/items") From c6e37a60637fac02f90093f5b382356eac00a81e Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 15:28:14 +0900 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=20=EC=9C=A0=EB=AC=B4=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/cart/CartProductService.kt | 6 ++++- .../exception/member/MemberException.kt | 13 +++++++++++ .../exception/member/MemberExceptionType.kt | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/petqua/exception/member/MemberException.kt create mode 100644 src/main/kotlin/com/petqua/exception/member/MemberExceptionType.kt diff --git a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt index 4fc943dc..3287bc2e 100644 --- a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt +++ b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt @@ -3,7 +3,10 @@ package com.petqua.application.cart import com.petqua.application.cart.dto.SaveCartProductCommand import com.petqua.common.domain.existByIdOrThrow import com.petqua.domain.cart.CartProductRepository +import com.petqua.domain.member.MemberRepository import com.petqua.domain.product.ProductRepository +import com.petqua.exception.member.MemberException +import com.petqua.exception.member.MemberExceptionType.NOT_FOUND_MEMBER import com.petqua.exception.product.ProductException import com.petqua.exception.product.ProductExceptionType.NOT_FOUND_PRODUCT import org.springframework.stereotype.Service @@ -14,10 +17,11 @@ import org.springframework.transaction.annotation.Transactional class CartProductService( private val cartProductRepository: CartProductRepository, private val productRepository: ProductRepository, + private val memberRepository: MemberRepository, ) { fun save(command: SaveCartProductCommand): Long { -// TODO memberRepository.existByIdOrThrow(command.memberId, MemberException(NOT_FOUND_MEMBER)) + memberRepository.existByIdOrThrow(command.memberId, MemberException(NOT_FOUND_MEMBER)) productRepository.existByIdOrThrow(command.productId, ProductException(NOT_FOUND_PRODUCT)) val savedCartProduct = cartProductRepository.save(command.toCartProduct()) return savedCartProduct.id diff --git a/src/main/kotlin/com/petqua/exception/member/MemberException.kt b/src/main/kotlin/com/petqua/exception/member/MemberException.kt new file mode 100644 index 00000000..dcf7395b --- /dev/null +++ b/src/main/kotlin/com/petqua/exception/member/MemberException.kt @@ -0,0 +1,13 @@ +package com.petqua.exception.member + +import com.petqua.common.exception.BaseException +import com.petqua.common.exception.BaseExceptionType + +class MemberException( + private val exceptionType: MemberExceptionType, +) : BaseException() { + + override fun exceptionType(): BaseExceptionType { + return exceptionType + } +} diff --git a/src/main/kotlin/com/petqua/exception/member/MemberExceptionType.kt b/src/main/kotlin/com/petqua/exception/member/MemberExceptionType.kt new file mode 100644 index 00000000..79a35f1d --- /dev/null +++ b/src/main/kotlin/com/petqua/exception/member/MemberExceptionType.kt @@ -0,0 +1,22 @@ +package com.petqua.exception.member + +import com.petqua.common.exception.BaseExceptionType +import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatus.NOT_FOUND + +enum class MemberExceptionType( + private val httpStatus: HttpStatus, + private val errorMessage: String, +) : BaseExceptionType { + + NOT_FOUND_MEMBER(NOT_FOUND, "존재하지 않는 회원입니다."), + ; + + override fun httpStatus(): HttpStatus { + return httpStatus + } + + override fun errorMessage(): String { + return errorMessage + } +} From e67b0fccdb5ee9e2d0ad87152616c1c6342471d1 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 15:29:40 +0900 Subject: [PATCH 15/21] =?UTF-8?q?test:=20application=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=82=B4=20=EB=B9=88=20=EB=93=B1=EB=A1=9D=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/petqua/application/cart/CartProductServiceTest.kt | 3 ++- .../com/petqua/application/product/ProductServiceTest.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt index a385b324..e4a69dd3 100644 --- a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt +++ b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt @@ -9,8 +9,9 @@ import com.petqua.test.fixture.product import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE -@SpringBootTest +@SpringBootTest(webEnvironment = NONE) class CartProductServiceTest( private val cartProductService: CartProductService, private val cartProductRepository: CartProductRepository, diff --git a/src/test/kotlin/com/petqua/application/product/ProductServiceTest.kt b/src/test/kotlin/com/petqua/application/product/ProductServiceTest.kt index ccfeb9d2..f6c01960 100644 --- a/src/test/kotlin/com/petqua/application/product/ProductServiceTest.kt +++ b/src/test/kotlin/com/petqua/application/product/ProductServiceTest.kt @@ -14,8 +14,9 @@ import com.petqua.test.fixture.store import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment -@SpringBootTest +@SpringBootTest(webEnvironment = WebEnvironment.NONE) class ProductServiceTest( private val productService: ProductService, private val productRepository: ProductRepository, From 2348e94049f6bc8fb4e731e12c0374ebc0169ce0 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 15:36:16 +0900 Subject: [PATCH 16/21] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/cart/CartProductServiceTest.kt | 5 ++++- .../com/petqua/test/fixture/MemberFixtures.kt | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/com/petqua/test/fixture/MemberFixtures.kt diff --git a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt index e4a69dd3..8d449d5f 100644 --- a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt +++ b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt @@ -3,8 +3,10 @@ package com.petqua.application.cart import com.petqua.application.cart.dto.SaveCartProductCommand import com.petqua.domain.cart.CartProductRepository import com.petqua.domain.cart.DeliveryMethod +import com.petqua.domain.member.MemberRepository import com.petqua.domain.product.ProductRepository import com.petqua.test.DataCleaner +import com.petqua.test.fixture.member import com.petqua.test.fixture.product import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe @@ -16,12 +18,13 @@ class CartProductServiceTest( private val cartProductService: CartProductService, private val cartProductRepository: CartProductRepository, private val productRepository: ProductRepository, + private val memberRepository: MemberRepository, private val dataCleaner: DataCleaner, ) : BehaviorSpec({ Given("봉달 상품 저장 명령으로") { val productId = productRepository.save(product(id = 1L)).id - val memberId = 1L + val memberId = memberRepository.save(member(id = 1L)).id val command = SaveCartProductCommand( memberId = memberId, productId = productId, diff --git a/src/test/kotlin/com/petqua/test/fixture/MemberFixtures.kt b/src/test/kotlin/com/petqua/test/fixture/MemberFixtures.kt new file mode 100644 index 00000000..bc0d1832 --- /dev/null +++ b/src/test/kotlin/com/petqua/test/fixture/MemberFixtures.kt @@ -0,0 +1,18 @@ +package com.petqua.test.fixture + +import com.petqua.domain.auth.Authority +import com.petqua.domain.member.Member + +fun member( + id: Long = 1L, + oauthId: String = "oauthId", + oauthServerNumber: Int = 1, + authority: Authority = Authority.MEMBER +): Member { + return Member( + id, + oauthId, + oauthServerNumber, + authority + ) +} From d0a9c55c2b31f6940de0703d62b3c6f303914daf Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 16:00:16 +0900 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=88=98?= =?UTF-8?q?=EB=9F=89=20=EA=B0=92=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/petqua/common/util/Validate.kt | 5 +++ .../petqua/domain/cart/CartProductQuantity.kt | 21 +++++++++++++ .../cart/CartProductExceptionType.kt | 4 ++- .../domain/cart/CartProductQuantityTest.kt | 31 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/petqua/common/util/Validate.kt create mode 100644 src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt create mode 100644 src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt diff --git a/src/main/kotlin/com/petqua/common/util/Validate.kt b/src/main/kotlin/com/petqua/common/util/Validate.kt new file mode 100644 index 00000000..be265dca --- /dev/null +++ b/src/main/kotlin/com/petqua/common/util/Validate.kt @@ -0,0 +1,5 @@ +package com.petqua.common.util + +inline fun throwExceptionWhen(condition: Boolean, exceptionSupplier: () -> RuntimeException) { + if (condition) throw exceptionSupplier() +} diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt new file mode 100644 index 00000000..f6004fbf --- /dev/null +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt @@ -0,0 +1,21 @@ +package com.petqua.domain.cart + +import com.petqua.common.util.throwExceptionWhen +import com.petqua.exception.cart.CartProductException +import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_OVER_MAXIMUM +import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_UNDER_MINIMUM +import jakarta.persistence.Embeddable + +private const val MIN_QUANTITY = 1 +private const val MAX_QUANTITY = 99 + +@Embeddable +class CartProductQuantity( + val quantity: Int, +) { + + init { + throwExceptionWhen(quantity < MIN_QUANTITY) { CartProductException(PRODUCT_QUANTITY_UNDER_MINIMUM) } + throwExceptionWhen(quantity > MAX_QUANTITY) { CartProductException(PRODUCT_QUANTITY_OVER_MAXIMUM) } + } +} diff --git a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt index de38e910..81e79ad4 100644 --- a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt +++ b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt @@ -9,7 +9,9 @@ enum class CartProductExceptionType( private val errorMessage: String, ) : BaseExceptionType { - INVALID_DELIVERY_METHOD(httpStatus = BAD_REQUEST, errorMessage = "유효하지 않는 배송 방법입니다.") + INVALID_DELIVERY_METHOD(httpStatus = BAD_REQUEST, errorMessage = "유효하지 않는 배송 방법입니다."), + PRODUCT_QUANTITY_UNDER_MINIMUM(httpStatus = BAD_REQUEST, errorMessage = "최소 1개 이상의 상품을 담을 수 있습니다."), + PRODUCT_QUANTITY_OVER_MAXIMUM(httpStatus = BAD_REQUEST, errorMessage = "최대 99개까지 구매할 수 있습니다."), ; override fun httpStatus(): HttpStatus { diff --git a/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt b/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt new file mode 100644 index 00000000..810d116c --- /dev/null +++ b/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt @@ -0,0 +1,31 @@ +package com.petqua.domain.cart + +import com.petqua.exception.cart.CartProductException +import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_OVER_MAXIMUM +import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_UNDER_MINIMUM +import io.kotest.core.spec.style.StringSpec +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy + +class CartProductQuantityTest : StringSpec({ + "상품 수량 값객체 생성" { + val quantity = CartProductQuantity(5) + assertThat(quantity.quantity).isEqualTo(5) + } + + "최소 수량 미만인 경우 생성 실패" { + assertThatThrownBy { + CartProductQuantity(0) + }.isInstanceOf(CartProductException::class.java) + .extracting("exceptionType") + .isEqualTo(PRODUCT_QUANTITY_UNDER_MINIMUM) + } + + "최대 수량 초과인 경우 생성 실패" { + assertThatThrownBy { + CartProductQuantity(100) + }.isInstanceOf(CartProductException::class.java) + .extracting("exceptionType") + .isEqualTo(PRODUCT_QUANTITY_OVER_MAXIMUM) + } +}) From 18e179ff0e19258e5780592f6ef773bc8868dabe Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 16:02:44 +0900 Subject: [PATCH 18/21] =?UTF-8?q?feat:=20=EB=B4=89=EB=8B=AC=20=EC=83=81?= =?UTF-8?q?=ED=92=88=20=EC=88=98=EB=9F=89=20=EA=B0=92=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/petqua/application/cart/dto/CartProductDtos.kt | 3 ++- src/main/kotlin/com/petqua/domain/cart/CartProduct.kt | 5 +++-- .../kotlin/com/petqua/domain/cart/CartProductQuantity.kt | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt b/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt index f70d8560..12f71e4e 100644 --- a/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt +++ b/src/main/kotlin/com/petqua/application/cart/dto/CartProductDtos.kt @@ -1,6 +1,7 @@ package com.petqua.application.cart.dto import com.petqua.domain.cart.CartProduct +import com.petqua.domain.cart.CartProductQuantity import com.petqua.domain.cart.DeliveryMethod data class SaveCartProductCommand( @@ -14,7 +15,7 @@ data class SaveCartProductCommand( return CartProduct( memberId = memberId, productId = productId, - quantity = quantity, + quantity = CartProductQuantity(quantity), isMale = isMale, deliveryMethod = deliveryMethod, ) diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt b/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt index 3a99947d..e3819136 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt @@ -2,6 +2,7 @@ package com.petqua.domain.cart import com.petqua.common.domain.BaseEntity import jakarta.persistence.Column +import jakarta.persistence.Embedded import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated @@ -20,8 +21,8 @@ class CartProduct( @Column(nullable = false) val productId: Long, - @Column(nullable = false) - val quantity: Int = 1, + @Embedded + val quantity: CartProductQuantity, @Column(nullable = false) val isMale: Boolean, diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt index f6004fbf..323a36cd 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt @@ -4,6 +4,7 @@ import com.petqua.common.util.throwExceptionWhen import com.petqua.exception.cart.CartProductException import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_OVER_MAXIMUM import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_UNDER_MINIMUM +import jakarta.persistence.Column import jakarta.persistence.Embeddable private const val MIN_QUANTITY = 1 @@ -11,7 +12,8 @@ private const val MAX_QUANTITY = 99 @Embeddable class CartProductQuantity( - val quantity: Int, + @Column(nullable = false) + val quantity: Int = 1, ) { init { From b53c1f45fe971fa62ef2f7dab9d3bec413703347 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Tue, 30 Jan 2024 16:06:59 +0900 Subject: [PATCH 19/21] =?UTF-8?q?test:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EB=B4=89=EB=8B=AC=20=EC=83=81=ED=92=88=20=EC=88=98=EB=9F=89?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cart/CartProductControllerTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt index 8aa966dc..00dd53d0 100644 --- a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt +++ b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt @@ -3,6 +3,7 @@ package com.petqua.presentation.cart import com.petqua.common.exception.ExceptionResponse import com.petqua.domain.product.ProductRepository import com.petqua.exception.cart.CartProductExceptionType.INVALID_DELIVERY_METHOD +import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_OVER_MAXIMUM import com.petqua.exception.product.ProductExceptionType.NOT_FOUND_PRODUCT import com.petqua.presentation.cart.dto.SaveCartProductRequest import com.petqua.test.ApiTestConfig @@ -110,6 +111,35 @@ class CartProductControllerTest( } } } + + When("유효하지 않은 상품 수량을 담으면") { + val request = SaveCartProductRequest( + productId = savedProduct.id, + quantity = 1_000, + isMale = false, + deliveryMethod = "SAFETY" + ) + val response = Given { + log().all() + .body(request) + .header(AUTHORIZATION, memberAuthResponse.accessToken) + .contentType("application/json") + } When { + post("/carts") + } Then { + log().all() + } Extract { + response() + } + + Then("예외가 발생한다") { + val errorResponse = response.`as`(ExceptionResponse::class.java) + assertSoftly { + it.assertThat(response.statusCode).isEqualTo(HttpStatus.BAD_REQUEST.value()) + it.assertThat(errorResponse.message).isEqualTo(PRODUCT_QUANTITY_OVER_MAXIMUM.errorMessage()) + } + } + } } } } From 096a109a40602280997792a7c3c6c26b78b41117 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Wed, 31 Jan 2024 11:15:05 +0900 Subject: [PATCH 20/21] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=20?= =?UTF-8?q?=EB=B4=89=EB=8B=AC=20=EB=AA=A9=EB=A1=9D=EC=97=90=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EB=8A=94=20=EC=83=81=ED=92=88=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/cart/CartProductService.kt | 15 ++++- .../petqua/domain/cart/CartProductQuantity.kt | 6 +- .../domain/cart/CartProductRepository.kt | 10 +++- .../cart/CartProductExceptionType.kt | 1 + .../cart/CartProductServiceTest.kt | 57 +++++++++++++++++++ .../domain/cart/CartProductQuantityTest.kt | 2 +- .../cart/CartProductControllerTest.kt | 1 + 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt index 3287bc2e..04d485ce 100644 --- a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt +++ b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt @@ -5,6 +5,8 @@ import com.petqua.common.domain.existByIdOrThrow import com.petqua.domain.cart.CartProductRepository import com.petqua.domain.member.MemberRepository import com.petqua.domain.product.ProductRepository +import com.petqua.exception.cart.CartProductException +import com.petqua.exception.cart.CartProductExceptionType.DUPLICATED_PRODUCT import com.petqua.exception.member.MemberException import com.petqua.exception.member.MemberExceptionType.NOT_FOUND_MEMBER import com.petqua.exception.product.ProductException @@ -23,7 +25,16 @@ class CartProductService( fun save(command: SaveCartProductCommand): Long { memberRepository.existByIdOrThrow(command.memberId, MemberException(NOT_FOUND_MEMBER)) productRepository.existByIdOrThrow(command.productId, ProductException(NOT_FOUND_PRODUCT)) - val savedCartProduct = cartProductRepository.save(command.toCartProduct()) - return savedCartProduct.id + validateDuplicatedProduct(command) + return cartProductRepository.save(command.toCartProduct()).id + } + + private fun validateDuplicatedProduct(command: SaveCartProductCommand) { + cartProductRepository.findByMemberIdAndProductIdAndMaleAndDeliveryMethod( + memberId = command.memberId, + productId = command.productId, + isMale = command.isMale, + deliveryMethod = command.deliveryMethod + )?.also { throw CartProductException(DUPLICATED_PRODUCT) } } } diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt index 323a36cd..9a7cdd59 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt @@ -13,11 +13,11 @@ private const val MAX_QUANTITY = 99 @Embeddable class CartProductQuantity( @Column(nullable = false) - val quantity: Int = 1, + val value: Int = 1, ) { init { - throwExceptionWhen(quantity < MIN_QUANTITY) { CartProductException(PRODUCT_QUANTITY_UNDER_MINIMUM) } - throwExceptionWhen(quantity > MAX_QUANTITY) { CartProductException(PRODUCT_QUANTITY_OVER_MAXIMUM) } + throwExceptionWhen(value < MIN_QUANTITY) { CartProductException(PRODUCT_QUANTITY_UNDER_MINIMUM) } + throwExceptionWhen(value > MAX_QUANTITY) { CartProductException(PRODUCT_QUANTITY_OVER_MAXIMUM) } } } diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt index aa2c5cbb..db5321c3 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt @@ -2,4 +2,12 @@ package com.petqua.domain.cart import org.springframework.data.jpa.repository.JpaRepository -interface CartProductRepository : JpaRepository +interface CartProductRepository : JpaRepository { + + fun findByMemberIdAndProductIdAndMaleAndDeliveryMethod( + memberId: Long, + productId: Long, + isMale: Boolean, + deliveryMethod: DeliveryMethod + ): CartProduct? +} diff --git a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt index 81e79ad4..5e80b45a 100644 --- a/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt +++ b/src/main/kotlin/com/petqua/exception/cart/CartProductExceptionType.kt @@ -12,6 +12,7 @@ enum class CartProductExceptionType( INVALID_DELIVERY_METHOD(httpStatus = BAD_REQUEST, errorMessage = "유효하지 않는 배송 방법입니다."), PRODUCT_QUANTITY_UNDER_MINIMUM(httpStatus = BAD_REQUEST, errorMessage = "최소 1개 이상의 상품을 담을 수 있습니다."), PRODUCT_QUANTITY_OVER_MAXIMUM(httpStatus = BAD_REQUEST, errorMessage = "최대 99개까지 구매할 수 있습니다."), + DUPLICATED_PRODUCT(httpStatus = BAD_REQUEST, errorMessage = "이미 장바구니에 담긴 상품입니다.") ; override fun httpStatus(): HttpStatus { diff --git a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt index 8d449d5f..38779411 100644 --- a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt +++ b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt @@ -5,9 +5,15 @@ import com.petqua.domain.cart.CartProductRepository import com.petqua.domain.cart.DeliveryMethod import com.petqua.domain.member.MemberRepository import com.petqua.domain.product.ProductRepository +import com.petqua.exception.cart.CartProductExceptionType.DUPLICATED_PRODUCT +import com.petqua.exception.member.MemberException +import com.petqua.exception.member.MemberExceptionType.NOT_FOUND_MEMBER +import com.petqua.exception.product.ProductException +import com.petqua.exception.product.ProductExceptionType.NOT_FOUND_PRODUCT import com.petqua.test.DataCleaner import com.petqua.test.fixture.member import com.petqua.test.fixture.product +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import org.springframework.boot.test.context.SpringBootTest @@ -42,6 +48,57 @@ class CartProductServiceTest( } } + Given("봉달 상품 저장시") { + val productId = productRepository.save(product(id = 1L)).id + val memberId = memberRepository.save(member(id = 1L)).id + + When("존재 하지 않는 회원이 요청 하는 경우") { + val command = SaveCartProductCommand( + memberId = 999L, + productId = productId, + quantity = 1, + isMale = true, + deliveryMethod = DeliveryMethod.COMMON, + ) + Then("예외가 발생 한다") { + shouldThrow { + cartProductService.save(command) + }.exceptionType() shouldBe NOT_FOUND_MEMBER + } + } + + When("존재 하지 않는 상품이 요청 하는 경우") { + val command = SaveCartProductCommand( + memberId = memberId, + productId = 999L, + quantity = 1, + isMale = true, + deliveryMethod = DeliveryMethod.COMMON, + ) + Then("예외가 발생 한다") { + shouldThrow { + cartProductService.save(command) + }.exceptionType() shouldBe NOT_FOUND_PRODUCT + } + } + + When("중복 상품이 요청 하는 경우") { + val command = SaveCartProductCommand( + memberId = memberId, + productId = productId, + quantity = 1, + isMale = true, + deliveryMethod = DeliveryMethod.COMMON, + ) + cartProductService.save(command) + Then("예외가 발생 한다") { + shouldThrow { + cartProductService.save(command) + }.exceptionType() shouldBe DUPLICATED_PRODUCT + } + } + } + afterContainer { dataCleaner.clean() } diff --git a/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt b/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt index 810d116c..fec7ab1f 100644 --- a/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt +++ b/src/test/kotlin/com/petqua/domain/cart/CartProductQuantityTest.kt @@ -10,7 +10,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy class CartProductQuantityTest : StringSpec({ "상품 수량 값객체 생성" { val quantity = CartProductQuantity(5) - assertThat(quantity.quantity).isEqualTo(5) + assertThat(quantity.value).isEqualTo(5) } "최소 수량 미만인 경우 생성 실패" { diff --git a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt index 00dd53d0..95a6dbb9 100644 --- a/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt +++ b/src/test/kotlin/com/petqua/presentation/cart/CartProductControllerTest.kt @@ -140,6 +140,7 @@ class CartProductControllerTest( } } } +// TODO When("중복 상품을 담으면") { } } } From 5712afac24230f5824b444f944458002825d07b6 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Wed, 31 Jan 2024 11:30:01 +0900 Subject: [PATCH 21/21] =?UTF-8?q?fix:=20value=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=EC=96=B4=20=EC=82=AC=EC=9A=A9=EC=97=90=20?= =?UTF-8?q?=EC=9D=98=ED=95=9C=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/petqua/application/cart/CartProductService.kt | 2 +- src/main/kotlin/com/petqua/domain/cart/CartProduct.kt | 2 ++ src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt | 2 -- .../kotlin/com/petqua/domain/cart/CartProductRepository.kt | 2 +- .../com/petqua/application/cart/CartProductServiceTest.kt | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt index 04d485ce..6e9433be 100644 --- a/src/main/kotlin/com/petqua/application/cart/CartProductService.kt +++ b/src/main/kotlin/com/petqua/application/cart/CartProductService.kt @@ -30,7 +30,7 @@ class CartProductService( } private fun validateDuplicatedProduct(command: SaveCartProductCommand) { - cartProductRepository.findByMemberIdAndProductIdAndMaleAndDeliveryMethod( + cartProductRepository.findByMemberIdAndProductIdAndIsMaleAndDeliveryMethod( memberId = command.memberId, productId = command.productId, isMale = command.isMale, diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt b/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt index e3819136..d596e270 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProduct.kt @@ -1,6 +1,7 @@ package com.petqua.domain.cart import com.petqua.common.domain.BaseEntity +import jakarta.persistence.AttributeOverride import jakarta.persistence.Column import jakarta.persistence.Embedded import jakarta.persistence.Entity @@ -22,6 +23,7 @@ class CartProduct( val productId: Long, @Embedded + @AttributeOverride(name = "value", column = Column(name = "quantity", nullable = false)) val quantity: CartProductQuantity, @Column(nullable = false) diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt index 9a7cdd59..a09e8f4c 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductQuantity.kt @@ -4,7 +4,6 @@ import com.petqua.common.util.throwExceptionWhen import com.petqua.exception.cart.CartProductException import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_OVER_MAXIMUM import com.petqua.exception.cart.CartProductExceptionType.PRODUCT_QUANTITY_UNDER_MINIMUM -import jakarta.persistence.Column import jakarta.persistence.Embeddable private const val MIN_QUANTITY = 1 @@ -12,7 +11,6 @@ private const val MAX_QUANTITY = 99 @Embeddable class CartProductQuantity( - @Column(nullable = false) val value: Int = 1, ) { diff --git a/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt b/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt index db5321c3..3e31c0de 100644 --- a/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt +++ b/src/main/kotlin/com/petqua/domain/cart/CartProductRepository.kt @@ -4,7 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository interface CartProductRepository : JpaRepository { - fun findByMemberIdAndProductIdAndMaleAndDeliveryMethod( + fun findByMemberIdAndProductIdAndIsMaleAndDeliveryMethod( memberId: Long, productId: Long, isMale: Boolean, diff --git a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt index 38779411..006c039d 100644 --- a/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt +++ b/src/test/kotlin/com/petqua/application/cart/CartProductServiceTest.kt @@ -5,6 +5,7 @@ import com.petqua.domain.cart.CartProductRepository import com.petqua.domain.cart.DeliveryMethod import com.petqua.domain.member.MemberRepository import com.petqua.domain.product.ProductRepository +import com.petqua.exception.cart.CartProductException import com.petqua.exception.cart.CartProductExceptionType.DUPLICATED_PRODUCT import com.petqua.exception.member.MemberException import com.petqua.exception.member.MemberExceptionType.NOT_FOUND_MEMBER @@ -92,7 +93,7 @@ class CartProductServiceTest( ) cartProductService.save(command) Then("예외가 발생 한다") { - shouldThrow { + shouldThrow { cartProductService.save(command) }.exceptionType() shouldBe DUPLICATED_PRODUCT }