Skip to content

Commit

Permalink
Merge pull request #55 from le2sky/feat/54
Browse files Browse the repository at this point in the history
[๊ธฐ๋Šฅ ๊ตฌํ˜„] ์˜ˆ์•ฝ ์ƒ์„ธ ์กฐํšŒ ๊ธฐ๋Šฅ ๊ตฌํ˜„(issue#54)
  • Loading branch information
le2sky authored Aug 27, 2023
2 parents 8a9b0a7 + 8f21e9b commit a05098b
Show file tree
Hide file tree
Showing 16 changed files with 362 additions and 12 deletions.
5 changes: 1 addition & 4 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ comment:

coverage:
status:
patch:
default:
target: auto
threshold: 0.1%
patch: off
project:
default:
target: auto
Expand Down
17 changes: 16 additions & 1 deletion mealkitary-api/src/docs/asciidoc/reservation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ include::{snippets}/reservation-post/http-response.adoc[]

include::{snippets}/reservation-post/response-headers.adoc[]

==== ์˜ˆ์•ฝ ์ƒ์„ธ ์กฐํšŒ

์˜ˆ์•ฝ์— ๋Œ€ํ•œ ์ƒ์„ธ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

===== ์š”์ฒญ

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

===== ์‘๋‹ต

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

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

๋ฏธ๊ฒฐ์ œ ์ƒํƒœ์˜ ์˜ˆ์•ฝ์— ๋Œ€ํ•ด ๊ฒฐ์ œ๋ฅผ ์ƒ์„ฑ/์Šน์ธํ•ฉ๋‹ˆ๋‹ค.
Expand All @@ -52,7 +66,8 @@ include::{snippets}/reservation-post-pay/response-headers.adoc[]

==== ์˜ˆ์•ฝ ์Šน์ธ

๊ฒฐ์ œ๋œ ์˜ˆ์•ฝ์— ๋Œ€ํ•ด์„œ ์˜ˆ์•ฝ ์Šน์ธ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ฒฐ์ œ๋œ ์˜ˆ์•ฝ์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ์Šน์ธ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
๊ฒฐ์ œ๋œ ์˜ˆ์•ฝ์— ๋Œ€ํ•ด์„œ ์˜ˆ์•ฝ ์Šน์ธ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
๊ฒฐ์ œ๋œ ์˜ˆ์•ฝ์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ์Šน์ธ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

===== ์š”์ฒญ

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mealkitary.reservation.adapter.input.web

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

@RestController
@RequestMapping("/reservations")
class GetReservationController(
private val getReservationQuery: GetReservationQuery
) {

@GetMapping("/{reservationId}")
fun getOneReservation(@PathVariable("reservationId") reservationId: String) =
getReservationQuery.loadOneReservationById(UUIDUtils.fromString(reservationId))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.docs.reservation

import com.docs.RestDocsSupport
import com.mealkitary.reservation.adapter.input.web.GetReservationController
import com.mealkitary.reservation.application.port.input.GetReservationQuery
import com.mealkitary.reservation.application.port.input.ReservationResponse
import com.mealkitary.reservation.application.port.input.ReservedProduct
import io.mockk.every
import io.mockk.mockk
import org.springframework.http.MediaType
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders
import org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest
import org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse
import org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint
import org.springframework.restdocs.payload.JsonFieldType
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.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.time.LocalDateTime
import java.util.UUID

class GetReservationControllerDocsTest : RestDocsSupport() {

private val getReservationQuery = mockk<GetReservationQuery>()

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

mvc.perform(RestDocumentationRequestBuilders.get("/reservations/{reservationId}", reservationId))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andDo(
document(
"reservation-get",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
parameterWithName("reservationId").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 @@ -2,9 +2,11 @@ package com.mealkitary

import com.fasterxml.jackson.databind.ObjectMapper
import com.mealkitary.reservation.adapter.input.web.AcceptReservationController
import com.mealkitary.reservation.adapter.input.web.GetReservationController
import com.mealkitary.reservation.adapter.input.web.PayReservationController
import com.mealkitary.reservation.adapter.input.web.ReserveProductController
import com.mealkitary.reservation.application.port.input.AcceptReservationUseCase
import com.mealkitary.reservation.application.port.input.GetReservationQuery
import com.mealkitary.reservation.application.port.input.PayReservationUseCase
import com.mealkitary.reservation.application.port.input.ReserveProductUseCase
import com.mealkitary.shop.adapter.input.web.GetProductController
Expand All @@ -25,6 +27,7 @@ import org.springframework.test.web.servlet.MockMvc
ReserveProductController::class,
PayReservationController::class,
AcceptReservationController::class,
GetReservationController::class,
GetShopController::class,
GetReservableTimeController::class,
GetProductController::class
Expand All @@ -49,6 +52,9 @@ abstract class WebIntegrationTestSupport : AnnotationSpec() {
@MockkBean
protected lateinit var acceptReservationUseCase: AcceptReservationUseCase

@MockkBean
protected lateinit var getReservationQuery: GetReservationQuery

@MockkBean
protected lateinit var getShopQuery: GetShopQuery

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.mealkitary.reservation.adapter.input.web

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.mockk.every
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.time.LocalDateTime
import java.util.UUID

class GetReservationControllerTest : WebIntegrationTestSupport() {

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

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

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

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

mvc.perform(get("/reservations/{reservationId}", UUID.randomUUID()))
.andExpect(status().isNotFound())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.message").value("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์˜ˆ์•ฝ์ž…๋‹ˆ๋‹ค."))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mealkitary.reservation.application.port.input

import java.util.UUID

interface GetReservationQuery {

fun loadOneReservationById(reservationId: UUID): ReservationResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.mealkitary.reservation.application.port.input

import java.time.LocalDateTime
import java.util.UUID

data class ReservationResponse(
val reservationId: UUID,
val shopName: String,
val description: String,
val reserveAt: LocalDateTime,
val status: String,
val reservedProduct: List<ReservedProduct>
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.mealkitary.reservation.application.port.output

import com.mealkitary.reservation.application.port.input.ReservationResponse
import com.mealkitary.reservation.domain.reservation.Reservation
import java.util.UUID

interface LoadReservationPort {

fun loadOneReservationById(reservationId: UUID): Reservation

fun queryOneReservationById(reservationId: UUID): ReservationResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.mealkitary.reservation.application.service

import com.mealkitary.reservation.application.port.input.GetReservationQuery
import com.mealkitary.reservation.application.port.output.LoadReservationPort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.UUID

@Service
@Transactional(readOnly = true)
class GetReservationService(
private val loadReservationPort: LoadReservationPort
) : GetReservationQuery {

override fun loadOneReservationById(reservationId: UUID) =
loadReservationPort.queryOneReservationById(reservationId)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mealkitary.reservation.application.service

import com.mealkitary.reservation.application.port.input.ReservationResponse
import com.mealkitary.reservation.application.port.output.LoadReservationPort
import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import java.time.LocalDateTime
import java.util.UUID

class GetReservationServiceTest : AnnotationSpec() {

private val loadReservationPort = mockk<LoadReservationPort>()
private val getReservationService = GetReservationService(loadReservationPort)

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

val result = getReservationService.loadOneReservationById(reservationId)

result.reservationId shouldBe reservationId
result.shopName shouldBe "์ง‘๋ฐฅ๋š๋”ฑ ์•ˆ์–‘์ "
result.status shouldBe "PAID"
result.reservedProduct.isEmpty().shouldBeTrue()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ class Reservation private constructor(
@CollectionTable(
name = "reservation_line_item", joinColumns = [JoinColumn(name = "reservation_id")]
)
private val lineItems: MutableList<ReservationLineItem> = lineItems
var lineItems: MutableList<ReservationLineItem> = lineItems
protected set

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_id", nullable = false)
private val shop: Shop = shop
var shop: Shop = shop
protected set

@Column(nullable = false)
private val reserveAt: LocalDateTime = reserveAt
var reserveAt: LocalDateTime = reserveAt
protected set

@Column(nullable = false)
@Enumerated(EnumType.STRING)
Expand Down
Loading

0 comments on commit a05098b

Please sign in to comment.