Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[기능 구현] 가게 상태 변경 기능 구현(issue#72) #76

Merged
merged 37 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c1f440b
Merge pull request #3 from le2sky/develop
le2sky May 17, 2023
ceb0b0e
Merge pull request #10 from le2sky/develop
le2sky May 25, 2023
cf5186f
Merge branch 'develop'
le2sky May 25, 2023
ad8685d
Merge branch 'develop'
le2sky May 25, 2023
ace07f1
Merge pull request #14 from le2sky/develop
le2sky May 27, 2023
e0a6862
Merge pull request #17 from le2sky/develop
le2sky May 31, 2023
47ff9d3
Merge pull request #20 from le2sky/develop
le2sky Jun 2, 2023
1fa215e
Merge pull request #25 from le2sky/develop
le2sky Jun 10, 2023
33e8f56
Merge pull request #30 from le2sky/develop
le2sky Aug 2, 2023
8fe715a
Merge pull request #33 from le2sky/develop
le2sky Aug 4, 2023
97c044f
Merge pull request #36 from le2sky/develop
le2sky Aug 5, 2023
0223d08
Merge pull request #40 from le2sky/develop
le2sky Aug 6, 2023
daf05a1
Merge pull request #41 from le2sky/develop
le2sky Aug 6, 2023
50772f6
Merge pull request #44 from le2sky/develop
le2sky Aug 11, 2023
4ae2998
Merge pull request #47 from le2sky/develop
le2sky Aug 20, 2023
40ac08e
Merge pull request #50 from le2sky/develop
le2sky Aug 21, 2023
f0f3482
Merge pull request #53 from le2sky/develop
le2sky Aug 24, 2023
a4f62b4
Merge pull request #58 from le2sky/develop
le2sky Aug 27, 2023
4bb8d89
Merge pull request #61 from le2sky/develop
le2sky Aug 27, 2023
218c07c
Merge pull request #68 from le2sky/develop
le2sky Sep 8, 2023
ca5a0dd
Merge pull request #74 from le2sky/develop
le2sky Sep 19, 2023
a66d596
feat: 가게 식별자 기반 예약 존재 확인 쿼리 구현
1o18z Sep 23, 2023
98ff9ef
feat: 가게 도메인의 상태 변경 및 확인 기능 구현
1o18z Sep 23, 2023
56cf391
feat: 가게 상태 변경 기능 구현
1o18z Sep 23, 2023
d389bd7
feat: 가게 상태 변경 기능 API 구현
1o18z Sep 23, 2023
3f6073e
Merge branch 'develop' into feat/72
1o18z Sep 23, 2023
7da22b5
feat: 가게 식별자 기반 예약 존재 확인 쿼리 구현
1o18z Sep 23, 2023
7f4cc5f
feat: 가게 도메인의 상태 변경 및 확인 기능 구현
1o18z Sep 23, 2023
88c795c
feat: 가게 상태 변경 기능 구현
1o18z Sep 23, 2023
2320566
feat: 가게 상태 변경 기능 API 구현
1o18z Sep 23, 2023
47ce8b3
Merge remote-tracking branch 'origin/feat/72' into feat/72
1o18z Sep 23, 2023
bc607b0
fix: 충돌 해결
1o18z Sep 23, 2023
e64ba67
test: 가게 도메인 상태 변경 테스트 작성
1o18z Sep 24, 2023
8f4274c
test: 데이터 추가로 인한 테스트 수정
1o18z Sep 24, 2023
9f67061
test: 가게 상태 변경 API 문서 테스트 작성
1o18z Sep 25, 2023
5789df1
refactor: 메서드 분리 및 클래스명 변경
1o18z Sep 26, 2023
2b2c20f
refactor: 메서드 인라인화
1o18z Sep 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified gradlew
100644 → 100755
le2sky marked this conversation as resolved.
Show resolved Hide resolved
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mealkitary.shop.web

import com.mealkitary.shop.application.port.input.UpdateShopStatusUseCase
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/shops")
class UpdateShopStatusController(
private val updateShopStatusUseCase: UpdateShopStatusUseCase
) {

@PostMapping("/{shopId}/status")
fun updateShopStatus(@PathVariable("shopId") shopId: Long): ResponseEntity<Unit> {
updateShopStatusUseCase.update(shopId)

return ResponseEntity.noContent().build()
le2sky marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import com.mealkitary.reservation.web.ReserveProductController
import com.mealkitary.shop.application.port.input.GetProductQuery
import com.mealkitary.shop.application.port.input.GetReservableTimeQuery
import com.mealkitary.shop.application.port.input.GetShopQuery
import com.mealkitary.shop.application.port.input.UpdateShopStatusUseCase
import com.mealkitary.shop.web.GetProductController
import com.mealkitary.shop.web.GetReservableTimeController
import com.mealkitary.shop.web.GetShopController
import com.mealkitary.shop.web.UpdateShopStatusController
import com.ninjasquad.springmockk.MockkBean
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.extensions.spring.SpringExtension
Expand All @@ -33,7 +35,8 @@ import org.springframework.test.web.servlet.MockMvc
GetReservationController::class,
GetShopController::class,
GetReservableTimeController::class,
GetProductController::class
GetProductController::class,
UpdateShopStatusController::class
]
)
abstract class WebIntegrationTestSupport : AnnotationSpec() {
Expand Down Expand Up @@ -69,4 +72,7 @@ abstract class WebIntegrationTestSupport : AnnotationSpec() {

@MockkBean
protected lateinit var getProductQuery: GetProductQuery

@MockkBean
protected lateinit var updateShopStatusUseCase: UpdateShopStatusUseCase
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mealkitary.shop.web

import com.mealkitary.WebIntegrationTestSupport
import io.mockk.every
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

class UpdateShopStatusControllerTest : WebIntegrationTestSupport() {

@Test
fun `api integration test - updateShopStatus`() {
val shopId = 1L
every { updateShopStatusUseCase.update(any()) } answers {}

mvc.perform(
MockMvcRequestBuilders.post("/shops/{shopId}/status", shopId)
)
.andExpect(MockMvcResultMatchers.status().isNoContent)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mealkitary.shop.application.port.input

interface UpdateShopStatusUseCase {

fun update(shopId: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mealkitary.shop.application.port.output

import com.mealkitary.shop.domain.shop.Shop

interface ShopPersistencePort {
le2sky marked this conversation as resolved.
Show resolved Hide resolved

fun loadOneShopById(shopId: Long): Shop

fun hasReservations(shopId: Long): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.mealkitary.shop.application.service

import com.mealkitary.shop.application.port.input.UpdateShopStatusUseCase
import com.mealkitary.shop.application.port.output.ShopPersistencePort
import com.mealkitary.shop.domain.shop.Shop
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
class UpdateShopStatusService(
private val shopPersistencePort: ShopPersistencePort,
) : UpdateShopStatusUseCase {

override fun update(shopId: Long) {
val shop = findShopById(shopId)

if (shop.status.isValidStatus()) {
checkReservationByShopId(shopId)
}
updateStatus(shop)
}
le2sky marked this conversation as resolved.
Show resolved Hide resolved

private fun findShopById(shopId: Long): Shop {
return shopPersistencePort.loadOneShopById(shopId)
}

private fun updateStatus(shop: Shop) {
if (shop.status.isValidStatus()) {
shop.changeStatusInvalid()
} else {
shop.changeStatusValid()
}
}

private fun checkReservationByShopId(shopId: Long) {
if (shopPersistencePort.hasReservations(shopId)) {
throw IllegalStateException("예약이 존재할 경우 가게 상태를 INVALID로 변경할 수 없습니다.")
}
}
}
le2sky marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.mealkitary.shop.application.service

import com.mealkitary.shop.application.port.output.ShopPersistencePort
import com.mealkitary.shop.domain.shop.Shop
import com.mealkitary.shop.domain.shop.ShopStatus
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk

class UpdateShopStatusServiceTest : AnnotationSpec() {

private val shopPersistencePort = mockk<ShopPersistencePort>()
private val updateShopStatusService = UpdateShopStatusService(shopPersistencePort)

@Test
fun `service unit test - 가게의 상태를 INVALID로 변경한다`() {
val shopId = 1L
val validShop = Shop(
"제목",
ShopStatus.VALID,
mutableListOf(),
mutableListOf()
)

every { shopPersistencePort.loadOneShopById(shopId) } returns validShop
every { shopPersistencePort.hasReservations(shopId) } returns false

updateShopStatusService.update(shopId)

validShop.status shouldBe ShopStatus.INVALID
}

@Test
fun `service unit test - 가게의 상태를 VALID로 변경한다`() {
val shopId = 1L
val validShop = Shop(
"제목",
ShopStatus.INVALID,
mutableListOf(),
mutableListOf()
)

every { shopPersistencePort.loadOneShopById(shopId) } returns validShop
every { shopPersistencePort.hasReservations(shopId) } returns false

updateShopStatusService.update(shopId)

validShop.status shouldBe ShopStatus.VALID
}

@Test
fun `service unit test - 가게의 상태를 INVALID로 변경할 때, 예약이 존재하면 예외가 발생한다`() {
val shopId = 1L
val validShop = Shop(
"제목",
ShopStatus.VALID,
mutableListOf(),
mutableListOf()
)

every { shopPersistencePort.loadOneShopById(shopId) } returns validShop
every { shopPersistencePort.hasReservations(shopId) } returns true

shouldThrow<IllegalStateException> {
updateShopStatusService.update(shopId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,12 @@ class Shop(
val match = reservableTimes.filter { it == reserveAt.toLocalTime() }
return match.isNotEmpty()
}

fun changeStatusValid() {
status = ShopStatus.VALID
}

fun changeStatusInvalid() {
status = ShopStatus.INVALID
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ enum class ShopStatus {
fun isInvalidStatus(): Boolean {
return this == INVALID
}

fun isValidStatus(): Boolean {
return this == VALID
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mealkitary.shop.domain.shop

import data.ShopTestData.Companion.defaultShop
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test

class ShopStatusTest {

@Test
fun isInvalidStatus() {
val shop = defaultShop().withStatus(ShopStatus.VALID).build()

shop.status.isValidStatus() shouldBe true
}

@Test
fun isValidStatus() {
val shop = defaultShop().withStatus(ShopStatus.INVALID).build()

shop.status.isInvalidStatus() shouldBe true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,31 @@ import data.ShopTestData.Companion.defaultShop
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.inspectors.forAll
import io.kotest.matchers.shouldBe
import io.kotest.matchers.throwable.shouldHaveMessage

class ShopTest : AnnotationSpec() {

@Test
fun `가게 상태를 VALID로 변경한다`() {
val sut = defaultShop().withStatus(ShopStatus.INVALID)
.build()

sut.changeStatusValid()

sut.status shouldBe ShopStatus.VALID
}

@Test
fun `가게 상태를 INVALID로 변경한다`() {
val sut = defaultShop().withStatus(ShopStatus.VALID)
.build()

sut.changeStatusInvalid()

sut.status shouldBe ShopStatus.INVALID
}

@Test
fun `유효하지 않은 가게라면 예외를 발생한다`() {
val sut = defaultShop().withStatus(ShopStatus.INVALID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ interface ReservationRepository : JpaRepository<Reservation, UUID> {
fun findOneWithShopById(reservationId: UUID): Optional<Reservation>

fun findAllByShopId(shopId: Long): List<Reservation>

fun existsReservationByShopId(shopId: Long): Boolean
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.mealkitary.shop.persistence

import com.mealkitary.common.exception.EntityNotFoundException
import com.mealkitary.reservation.persistence.ReservationRepository
import com.mealkitary.shop.application.port.output.LoadProductPort
import com.mealkitary.shop.application.port.output.LoadReservableTimePort
import com.mealkitary.shop.application.port.output.LoadShopPort
import com.mealkitary.shop.application.port.output.ShopPersistencePort
import com.mealkitary.shop.domain.shop.Shop
import org.springframework.stereotype.Repository
import java.util.Optional
Expand All @@ -14,7 +16,8 @@ private const val NOT_FOUND_SHOP_MESSAGE = "존재하지 않는 가게입니다.
@Repository
class SpringDataJpaShopPersistenceAdapter(
private val shopRepository: ShopRepository,
) : LoadShopPort, LoadProductPort, LoadReservableTimePort {
private val reservationRepository: ReservationRepository
) : LoadShopPort, LoadProductPort, LoadReservableTimePort, ShopPersistencePort {

override fun loadAllShop(): List<Shop> = shopRepository.findAll()

Expand All @@ -26,6 +29,10 @@ class SpringDataJpaShopPersistenceAdapter(
override fun loadAllReservableTimeByShopId(shopId: Long) =
getShopOrThrow(shopRepository::findOneWithReservableTimesById, shopId).reservableTimes

override fun hasReservations(shopId: Long): Boolean {
return reservationRepository.existsReservationByShopId(shopId)
}

private fun getShopOrThrow(queryMethod: Function<Long, Optional<Shop>>, shopId: Long): Shop {
return (queryMethod.apply(shopId).orElseThrow { throw EntityNotFoundException(NOT_FOUND_SHOP_MESSAGE) })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,13 @@ insert into reservable_time
values (3, '20:00');
insert into reservable_time
values (3, '23:30');


insert into shop
values (4, 'VALID', '집밥뚝딱 다산점');
insert into product
values (10, '김치찌개', 15800, 4);
insert into reservable_time
values (4, '16:30');
insert into reservation (id, shop_id, reserve_at, reservation_status)
values ('c1e170e4-57e4-4d7d-8c10-6f4a8c658020', 4, '2023-09-22T16:30:00', 'NOTPAID');
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.mealkitary
import com.mealkitary.reservation.application.service.AcceptReservationService
import com.mealkitary.reservation.application.service.PayReservationService
import com.mealkitary.reservation.application.service.RejectReservationService
import com.mealkitary.shop.application.service.UpdateShopStatusService
import com.ninjasquad.springmockk.MockkBean
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.extensions.spring.SpringExtension
Expand Down Expand Up @@ -32,4 +33,7 @@ abstract class PersistenceIntegrationTestSupport : AnnotationSpec() {

@MockkBean
private lateinit var rejectReservationService: RejectReservationService

@MockkBean
private lateinit var updateShopStatusService: UpdateShopStatusService
le2sky marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ class SpringDataJpaShopPersistenceAdapterTest(
private val adapterUnderTest: SpringDataJpaShopPersistenceAdapter,
) : PersistenceIntegrationTestSupport() {

@Test
fun `db integration test - 가게에 예약이 존재하는지 확인한다`() {
val existsReservation = adapterUnderTest.hasReservations(4L)

existsReservation.shouldBeTrue()
}

@Test
fun `db integration test - 모든 가게를 조회한다`() {
val shops = adapterUnderTest.loadAllShop()

shops.size shouldBe 3
shops.size shouldBe 4
shops.get(0).title shouldBe "집밥뚝딱 철산점"
}

Expand Down Expand Up @@ -94,6 +101,8 @@ class SpringDataJpaShopPersistenceAdapterTest(

@Test
fun `db integration test - 가게가 하나도 존재하지 않는 경우 빈 배열을 반환한다 `() {
em.createQuery("delete from Reservation r")
.executeUpdate()
em.createQuery("delete from Product p")
.executeUpdate()
em.createNativeQuery("delete from reservable_time")
Expand Down