Skip to content

Commit

Permalink
Merge branch 'develop' into feat/72
Browse files Browse the repository at this point in the history
  • Loading branch information
1o18z authored Sep 23, 2023
2 parents d389bd7 + eb2284a commit 3f6073e
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 5 deletions.
14 changes: 14 additions & 0 deletions mealkitary-api/src/docs/asciidoc/reservation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ include::{snippets}/reservation-get/path-parameters.adoc[]

include::{snippets}/reservation-get/http-response.adoc[]

==== ๋Œ€์ƒ ๊ฐ€๊ฒŒ ์˜ˆ์•ฝ ๋ชฉ๋ก ์กฐํšŒ

๊ฐ€๊ฒŒ ์‹๋ณ„์ž๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์˜ˆ์•ฝ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

===== ์š”์ฒญ

include::{snippets}/reservation-get-all/curl-request.adoc[]
include::{snippets}/reservation-get-all/http-request.adoc[]
include::{snippets}/reservation-get-all/request-parameters.adoc[]

===== ์‘๋‹ต

include::{snippets}/reservation-get-all/http-response.adoc[]

==== ์˜ˆ์•ฝ ๊ฒฐ์ œ

๋ฏธ๊ฒฐ์ œ ์ƒํƒœ์˜ ์˜ˆ์•ฝ์— ๋Œ€ํ•ด ๊ฒฐ์ œ๋ฅผ ์ƒ์„ฑ/์Šน์ธํ•ฉ๋‹ˆ๋‹ค.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.mealkitary.reservation.web

import com.mealkitary.common.utils.HttpResponseUtils
import com.mealkitary.common.utils.UUIDUtils
import com.mealkitary.reservation.application.port.input.GetReservationQuery
import com.mealkitary.reservation.application.port.input.ReservationResponse
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
Expand All @@ -16,4 +20,12 @@ class GetReservationController(
@GetMapping("/{reservationId}")
fun getOneReservation(@PathVariable("reservationId") reservationId: String) =
getReservationQuery.loadOneReservationById(UUIDUtils.fromString(reservationId))

@GetMapping
fun getAllReservation(@RequestParam("shopId") shopIdParam: Long?): ResponseEntity<List<ReservationResponse>> {
val shopId = requireNotNull(shopIdParam) { "๊ฐ€๊ฒŒ ์‹๋ณ„์ž๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค." }

return HttpResponseUtils
.mapToResponseEntity(emptiableList = getReservationQuery.loadAllReservationByShopId(shopId))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import org.springframework.restdocs.payload.PayloadDocumentation.responseFields
import org.springframework.restdocs.request.RequestDocumentation.parameterWithName
import org.springframework.restdocs.request.RequestDocumentation.pathParameters
import org.springframework.restdocs.request.RequestDocumentation.requestParameters
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.util.UUID

class GetReservationControllerDocsTest : RestDocsSupport() {
Expand Down Expand Up @@ -84,5 +87,67 @@ class GetReservationControllerDocsTest : RestDocsSupport() {
)
}

@Test
fun `api integration test - getAllReservation`() {
val reservationId = UUID.randomUUID()
val reserveAt = LocalDateTime.of(
LocalDate.of(2023, 6, 23), LocalTime.of(6, 30)
)
every { getReservationQuery.loadAllReservationByShopId(1L) } answers {
listOf(
ReservationResponse(
reservationId,
"์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ ",
"๋ถ€๋Œ€์ฐŒ๊ฐœ ์™ธ 1๊ฑด",
reserveAt,
"PAID",
listOf(
ReservedProduct(
1L,
"๋ถ€๋Œ€์ฐŒ๊ฐœ",
20000,
2
),
ReservedProduct(
2L,
"๊น€์น˜์ฐŒ๊ฐœ",
20000,
1
)
)
)
)
}

mvc.perform(RestDocumentationRequestBuilders.get("/reservations?shopId=1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andDo(
document(
"reservation-get-all",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestParameters(
parameterWithName("shopId").description("์˜ˆ์•ฝ ์กฐํšŒ ๋Œ€์ƒ ๊ฐ€๊ฒŒ ์‹๋ณ„์ž")
),
responseFields(
fieldWithPath("[].reservationId").type(JsonFieldType.STRING).description("์˜ˆ์•ฝ ์‹๋ณ„์ž"),
fieldWithPath("[].shopName").type(JsonFieldType.STRING).description("์˜ˆ์•ฝ ๋Œ€์ƒ ๊ฐ€๊ฒŒ์˜ ์ด๋ฆ„"),
fieldWithPath("[].description").type(JsonFieldType.STRING).description("์˜ˆ์•ฝ ๊ฐœ์š”"),
fieldWithPath("[].reserveAt").type(JsonFieldType.STRING)
.description("์˜ˆ์•ฝ ์‹œ๊ฐ„(yyyy-mm-ddThh:mm:ss)"),
fieldWithPath("[].status").type(JsonFieldType.STRING).description("์˜ˆ์•ฝ ์ƒํƒœ"),
fieldWithPath("[].reservedProduct.[].productId").type(JsonFieldType.NUMBER)
.description("์˜ˆ์•ฝ ์ƒํ’ˆ ์‹๋ณ„์ž"),
fieldWithPath("[].reservedProduct.[].name").type(JsonFieldType.STRING)
.description("์˜ˆ์•ฝ ์ƒํ’ˆ๋ช…"),
fieldWithPath("[].reservedProduct.[].price").type(JsonFieldType.NUMBER)
.description("์˜ˆ์•ฝ ์ƒํ’ˆ ๊ฐ€๊ฒฉ"),
fieldWithPath("[].reservedProduct.[].count").type(JsonFieldType.NUMBER).description("์˜ˆ์•ฝ ์ˆ˜๋Ÿ‰")
)
)
)
}

override fun initController() = GetReservationController(getReservationQuery)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.mealkitary.WebIntegrationTestSupport
import com.mealkitary.common.exception.EntityNotFoundException
import com.mealkitary.reservation.application.port.input.ReservationResponse
import com.mealkitary.reservation.application.port.input.ReservedProduct
import io.kotest.inspectors.forAll
import io.mockk.every
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
Expand Down Expand Up @@ -66,17 +67,88 @@ class GetReservationControllerTest : WebIntegrationTestSupport() {
}

@Test
fun `api integration test - ์˜ˆ์•ฝ ์‹๋ณ„์ž๊ฐ€ UUID ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด 400 ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•œ๋‹ค`() {
fun `api integration test - getAllReservation`() {
val reservationId = UUID.randomUUID()
val reserveAt = LocalDateTime.of(
LocalDate.of(2023, 6, 23), LocalTime.of(6, 30)
)
every { getReservationQuery.loadAllReservationByShopId(1L) } answers {
listOf(
ReservationResponse(
reservationId,
"์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ ",
"๋ถ€๋Œ€์ฐŒ๊ฐœ ์™ธ 1๊ฑด",
reserveAt,
"PAID",
listOf(
ReservedProduct(
1L,
"๋ถ€๋Œ€์ฐŒ๊ฐœ",
20000,
2
),
ReservedProduct(
2L,
"๊น€์น˜์ฐŒ๊ฐœ",
20000,
1
)
)
)
)
}

mvc.perform(get("/reservations?shopId=1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].reservationId").value(reservationId.toString()))
.andExpect(jsonPath("$.[0].shopName").value("์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ "))
.andExpect(jsonPath("$.[0].reserveAt").value(reserveAt.format(DateTimeFormatter.ISO_DATE_TIME)))
.andExpect(jsonPath("$.[0].status").value("PAID"))
.andExpect(jsonPath("$.[0].reservedProduct[0].productId").value(1L))
.andExpect(jsonPath("$.[0].reservedProduct[0].name").value("๋ถ€๋Œ€์ฐŒ๊ฐœ"))
.andExpect(jsonPath("$.[0].reservedProduct[0].price").value(20000))
.andExpect(jsonPath("$.[0].reservedProduct[0].count").value(2))
.andExpect(jsonPath("$.[0].reservedProduct[1].productId").value(2L))
.andExpect(jsonPath("$.[0].reservedProduct[1].name").value("๊น€์น˜์ฐŒ๊ฐœ"))
.andExpect(jsonPath("$.[0].reservedProduct[1].price").value(20000))
.andExpect(jsonPath("$.[0].reservedProduct[1].count").value(1))
}

@Test
fun `api integration test - getAllReservation - ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๊ฐ€๊ฒŒ ์‹๋ณ„์ž๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ 400 ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•œ๋‹ค`() {
every { getReservationQuery.loadAllReservationByShopId(any()) } answers { emptyList() }

listOf("/reservations?shopId=", "/reservations", "/reservations?shopId")
.forAll {
mvc.perform(get(it))
.andExpect(status().isBadRequest())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.message").value("๊ฐ€๊ฒŒ ์‹๋ณ„์ž๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค."))
}
}

@Test
fun `api integration test - getAllReservation - ํ•ด๋‹น ๊ฐ€๊ฒŒ์˜ ์˜ˆ์•ฝ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด 204 NoContent๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค`() {
every { getReservationQuery.loadAllReservationByShopId(any()) } answers { emptyList() }

mvc.perform(get("/reservations?shopId=1"))
.andExpect(status().isNoContent())
}

@Test
fun `api integration test - getOneReservation - ์˜ˆ์•ฝ ์‹๋ณ„์ž๊ฐ€ UUID ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด 400 ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•œ๋‹ค`() {
mvc.perform(
get("/reservations/{reservationId}", "invalid-uuid-test")
)
.andExpect(status().isBadRequest)
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value("400"))
.andExpect(jsonPath("$.message").value("์ž˜๋ชป๋œ UUID ํ˜•์‹์ž…๋‹ˆ๋‹ค."))
}

@Test
fun `api integration test - ๋‚ด๋ถ€์—์„œ EntityNotFound ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด 404 ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•œ๋‹ค`() {
fun `api integration test - getOneReservation - ๋‚ด๋ถ€์—์„œ EntityNotFound ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด 404 ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•œ๋‹ค`() {
every { getReservationQuery.loadOneReservationById(any()) }
.throws(EntityNotFoundException("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์˜ˆ์•ฝ์ž…๋‹ˆ๋‹ค."))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import java.util.UUID
interface GetReservationQuery {

fun loadOneReservationById(reservationId: UUID): ReservationResponse

fun loadAllReservationByShopId(shopId: Long): List<ReservationResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface LoadReservationPort {
fun loadOneReservationById(reservationId: UUID): Reservation

fun queryOneReservationById(reservationId: UUID): ReservationResponse

fun queryAllReservationByShopId(shopId: Long): List<ReservationResponse>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mealkitary.reservation.application.service

import com.mealkitary.reservation.application.port.input.GetReservationQuery
import com.mealkitary.reservation.application.port.input.ReservationResponse
import com.mealkitary.reservation.application.port.output.LoadReservationPort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand All @@ -14,4 +15,7 @@ class GetReservationService(

override fun loadOneReservationById(reservationId: UUID) =
loadReservationPort.queryOneReservationById(reservationId)

override fun loadAllReservationByShopId(shopId: Long): List<ReservationResponse> =
loadReservationPort.queryAllReservationByShopId(shopId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,38 @@ class GetReservationServiceTest : AnnotationSpec() {
result.status shouldBe "PAID"
result.reservedProduct.isEmpty().shouldBeTrue()
}

@Test
fun `service unit test - ๊ฐ€๊ฒŒ ์‹๋ณ„์ž๋กœ ์˜ˆ์•ฝ์˜ ์ƒ์„ธ ์ •๋ณด ๋ชฉ๋ก์„ ์กฐํšŒํ•œ๋‹ค`() {
val reservationId = UUID.randomUUID()
every { loadReservationPort.queryAllReservationByShopId(any()) } answers {
listOf(
ReservationResponse(
reservationId,
"์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ ",
"๋ถ€๋Œ€์ฐŒ๊ฐœ ์™ธ 1๊ฑด",
LocalDateTime.now(),
"PAID",
emptyList()
),
ReservationResponse(
reservationId,
"์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ ",
"๋ถ€๋Œ€์ฐŒ๊ฐœ ์™ธ 1๊ฑด",
LocalDateTime.now(),
"PAID",
emptyList()
)
)
}

val result = getReservationService.loadAllReservationByShopId(1L)

val resultReservation = result.get(0)
result.size shouldBe 2
resultReservation.reservationId shouldBe reservationId
resultReservation.shopName shouldBe "์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ "
resultReservation.status shouldBe "PAID"
resultReservation.reservedProduct.isEmpty().shouldBeTrue()
}
}
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 existsReservationByShopId(shopId: Long): Boolean

fun findAllByShopId(shopId: Long): List<Reservation>
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ class SpringDataJpaReservationPersistenceAdapter(
val reservation = reservationRepository.findOneWithShopById(reservationId)
.orElseThrow { throw EntityNotFoundException(NOT_FOUND_RESERVATION_MESSAGE) }

return ReservationResponse(
return mapToReservationResponse(reservation)
}

override fun queryAllReservationByShopId(shopId: Long): List<ReservationResponse> {
val reservations = reservationRepository.findAllByShopId(shopId)

return reservations.map { mapToReservationResponse(it) }
}

private fun mapToReservationResponse(reservation: Reservation) =
ReservationResponse(
reservation.id,
reservation.shop.title,
reservation.buildDescription(),
Expand All @@ -62,5 +72,4 @@ class SpringDataJpaReservationPersistenceAdapter(
)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.mealkitary.reservation.domain.reservation.ReservationStatus
import com.mealkitary.shop.persistence.ShopRepository
import data.ReservationTestData
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.inspectors.forAll
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.kotest.matchers.throwable.shouldHaveMessage
Expand Down Expand Up @@ -91,6 +92,40 @@ class SpringDataJpaReservationPersistenceAdapterTest(
result.description shouldBe "๋ถ€๋Œ€์ฐŒ๊ฐœ ์™ธ 1๊ฑด"
}

@Test
fun `db integration test - ๊ฐ€๊ฒŒ ์‹๋ณ„์ž๋กœ ์˜ˆ์•ฝ์˜ ์ƒ์„ธ ์ •๋ณด ๋ชฉ๋ก์„ ์กฐํšŒํ•œ๋‹ค`() {
val reservation = ReservationTestData.defaultReservation()
.withReservationStatus(ReservationStatus.NOTPAID)
.withShop(shopRepository.findOneWithProductsById(1L).orElseThrow())
.build()
val saved = adapterUnderTest.saveOne(reservation)
em.flush()
em.clear()

val result = adapterUnderTest.queryAllReservationByShopId(1L)

val resultReservation = result.get(0)
result.size shouldBe 1
resultReservation.reservationId shouldBe saved
resultReservation.status shouldBe "NOTPAID"
resultReservation.shopName shouldBe "์ง‘๋ฐฅ๋š๋”ฑ ์ฒ ์‚ฐ์ "
resultReservation.reservedProduct.size shouldBe 2
resultReservation.description shouldBe "๋ถ€๋Œ€์ฐŒ๊ฐœ ์™ธ 1๊ฑด"
}

@Test
fun `db integration test - ๊ฐ€๊ฒŒ ์‹๋ณ„์ž๋กœ ์˜ˆ์•ฝ์˜ ์ƒ์„ธ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ๋•Œ, ๊ด€๋ จ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค`() {
val unknownShopId = 12345L
val noReservationShopId = 1L
val source = listOf(unknownShopId, noReservationShopId)

source.forAll {
val result = adapterUnderTest.queryAllReservationByShopId(it)

result.isEmpty().shouldBeTrue()
}
}

@Test
fun `db integration test - ๊ฒฐ์ œ๋ฅผ ์กฐํšŒํ•œ๋‹ค`() {
val reservation = ReservationTestData.defaultReservation()
Expand Down

0 comments on commit 3f6073e

Please sign in to comment.