Skip to content

Commit

Permalink
Merge pull request #58 from le2sky/develop
Browse files Browse the repository at this point in the history
[๋ฐฐํฌ] v0.3.0(PR#55, PR#57)
  • Loading branch information
le2sky authored Aug 27, 2023
2 parents f0f3482 + fd4bff4 commit a4f62b4
Show file tree
Hide file tree
Showing 18 changed files with 365 additions and 13 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
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ktlintVersion=11.0.0
springBootVersion=2.7.11
springDependencyManagementVersion=1.0.15.RELEASE
# project
applicationVersion=0.2.0
applicationVersion=0.3.0
projectGroup=com.mealkitary
# test
kotestVersion=4.4.3
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
@@ -1,6 +1,7 @@
package com.mealkitary.common.config

import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

Expand All @@ -10,5 +11,6 @@ class WebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOrigins("*")
.exposedHeaders(HttpHeaders.LOCATION)
}
}
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()
}
}
Loading

0 comments on commit a4f62b4

Please sign in to comment.