Skip to content

Commit

Permalink
Merge pull request #52 from le2sky/feat/51
Browse files Browse the repository at this point in the history
[기능 구현] 예약 승인 기능 구현(issue#51)
  • Loading branch information
le2sky authored Aug 24, 2023
2 parents 47cfa82 + 3438488 commit 8a9b0a7
Show file tree
Hide file tree
Showing 33 changed files with 595 additions and 21 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/mealkitary-main-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
echo "::set-output name=DEPLOYMENT::false"
fi
- uses: actions/checkout@v3
- name: Firebase 시크릿 생성
run: |
echo ${{ secrets.ENCODED_FIREBASE_JSON }} | base64 -d > ./mealkitary-infrastructure/adapter-firebase-notification/src/main/resources/firebase.json
- name: JDK 11 구성
uses: actions/setup-java@v3
with:
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/mealkitary-main-develop-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Firebase 시크릿 생성
run: |
echo ${{ secrets.ENCODED_FIREBASE_JSON }} | base64 -d > ./mealkitary-infrastructure/adapter-firebase-notification/src/main/resources/firebase.json
- name: JDK 11 구성
uses: actions/setup-java@v3
with:
Expand Down Expand Up @@ -50,6 +53,7 @@ jobs:
./mealkitary-application/build/test-results/**/*.xml
./mealkitary-infrastructure/adapter-persistence-spring-data-jpa/build/test-results/**/*.xml
./mealkitary-infrastructure/adapter-paymentgateway-tosspayments/build/test-results/**/*.xml
./mealkitary-infrastructure/adapter-firebase-notification/build/test-results/**/*.xml
- name: Jacoco Coverage 리포트 전송
uses: codecov/codecov-action@v3
Expand All @@ -60,7 +64,8 @@ jobs:
./mealkitary-domain/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-application/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-infrastructure/adapter-persistence-spring-data-jpa/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-infrastructure/adapter-paymentgateway-tosspayments/build/reports/jacoco/test/jacocoTestReport.xml
./mealkitary-infrastructure/adapter-paymentgateway-tosspayments/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-infrastructure/adapter-firebase-notification/build/reports/jacoco/test/jacocoTestReport.xml
name: mealkitary-codecov
verbose: true

Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/mealkitary-test-coverage-automation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Firebase 시크릿 생성
run: |
echo ${{ secrets.ENCODED_FIREBASE_JSON }} | base64 -d > ./mealkitary-infrastructure/adapter-firebase-notification/src/main/resources/firebase.json
- name: JDK 11 구성
uses: actions/setup-java@v3
with:
Expand All @@ -38,6 +41,7 @@ jobs:
./mealkitary-application/build/test-results/**/*.xml
./mealkitary-infrastructure/adapter-persistence-spring-data-jpa/build/test-results/**/*.xml
./mealkitary-infrastructure/adapter-paymentgateway-tosspayments/build/test-results/**/*.xml
./mealkitary-infrastructure/adapter-firebase-notification/build/test-results/**/*.xml
- name: Jacoco Coverage 리포트 전송
uses: codecov/codecov-action@v3
Expand All @@ -48,6 +52,7 @@ jobs:
./mealkitary-domain/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-application/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-infrastructure/adapter-persistence-spring-data-jpa/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-infrastructure/adapter-paymentgateway-tosspayments/build/reports/jacoco/test/jacocoTestReport.xml
./mealkitary-infrastructure/adapter-paymentgateway-tosspayments/build/reports/jacoco/test/jacocoTestReport.xml,
./mealkitary-infrastructure/adapter-firebase-notification/build/reports/jacoco/test/jacocoTestReport.xml
name: mealkitary-codecov
verbose: true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ out/

## Spring REST docs ##
**/src/main/resources/static/docs/

## Firebase ##
**/firebase.json
8 changes: 6 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ subprojects {
"**/exception/*",
"**/utils/*",
"**/*Config*",
"**/*BaseEntity*"
"**/*BaseEntity*",
"**/Firebase*Initializer*",
"**/Firebase*Client*",
)
}
)
Expand All @@ -101,7 +103,9 @@ subprojects {
"**.exception.*",
"**.utils.*",
"**.*Config*",
"**.*BaseEntity*"
"**.*BaseEntity*",
"**.Firebase*Initializer*",
"**.Firebase*Client*",
)
}
}
Expand Down
4 changes: 3 additions & 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.1.1
applicationVersion=0.2.0
projectGroup=com.mealkitary
# test
kotestVersion=4.4.3
Expand All @@ -25,3 +25,5 @@ asciidoctorVersion=3.3.2
jibVersion=3.1.4
# ulid
ulidCreatorVersion=5.2.0
# firebase
firebaseAdminVersion=9.1.1
1 change: 1 addition & 0 deletions mealkitary-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
implementation(project(":mealkitary-domain"))
implementation(project(":mealkitary-infrastructure:adapter-persistence-spring-data-jpa"))
implementation(project(":mealkitary-infrastructure:adapter-paymentgateway-tosspayments"))
implementation(project(":mealkitary-infrastructure:adapter-firebase-notification"))
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
asciidoctorExt("org.springframework.restdocs:spring-restdocs-asciidoctor")
}
Expand Down
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 @@ -49,3 +49,17 @@ include::{snippets}/reservation-post-pay/http-response.adoc[]
===== 응답 헤더

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

==== 예약 승인

결제된 예약에 대해서 예약 승인 처리합니다. 결제된 예약이 아닌 경우, 승인 처리할 수 없습니다.

===== 요청

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

===== 응답

include::{snippets}/reservation-post-accept/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mealkitary.reservation.adapter.input.web

import com.mealkitary.common.utils.UUIDUtils
import com.mealkitary.reservation.application.port.input.AcceptReservationUseCase
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("/reservations")
class AcceptReservationController(
private val acceptReservationUseCase: AcceptReservationUseCase
) {

@PostMapping("/{reservationId}/accept")
fun acceptReservation(@PathVariable("reservationId") reservationId: String): ResponseEntity<Unit> {
acceptReservationUseCase.accept(UUIDUtils.fromString(reservationId))

return ResponseEntity.noContent().build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.docs.reservation

import com.docs.RestDocsSupport
import com.mealkitary.reservation.adapter.input.web.AcceptReservationController
import com.mealkitary.reservation.application.port.input.AcceptReservationUseCase
import io.mockk.every
import io.mockk.mockk
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.request.RequestDocumentation.parameterWithName
import org.springframework.restdocs.request.RequestDocumentation.pathParameters
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.util.UUID

class AcceptReservationControllerDocsTest : RestDocsSupport() {

private val acceptReservationUseCase = mockk<AcceptReservationUseCase>()

@Test
fun `api docs test - acceptReservation`() {
val id = UUID.randomUUID()
every { acceptReservationUseCase.accept(any()) }.answers { }

mvc.perform(
RestDocumentationRequestBuilders.post("/reservations/{reservationId}/accept", id)
)
.andExpect(status().isNoContent)
.andDo(
document(
"reservation-post-accept",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
parameterWithName("reservationId").description("승인 대상 예약의 식별자")
),
)
)
}

override fun initController() = AcceptReservationController(acceptReservationUseCase)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.mealkitary

import com.fasterxml.jackson.databind.ObjectMapper
import com.mealkitary.reservation.adapter.input.web.AcceptReservationController
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.PayReservationUseCase
import com.mealkitary.reservation.application.port.input.ReserveProductUseCase
import com.mealkitary.shop.adapter.input.web.GetProductController
Expand All @@ -22,6 +24,7 @@ import org.springframework.test.web.servlet.MockMvc
controllers = [
ReserveProductController::class,
PayReservationController::class,
AcceptReservationController::class,
GetShopController::class,
GetReservableTimeController::class,
GetProductController::class
Expand All @@ -43,6 +46,9 @@ abstract class WebIntegrationTestSupport : AnnotationSpec() {
@MockkBean
protected lateinit var payReservationUseCase: PayReservationUseCase

@MockkBean
protected lateinit var acceptReservationUseCase: AcceptReservationUseCase

@MockkBean
protected lateinit var getShopQuery: GetShopQuery

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

import com.mealkitary.WebIntegrationTestSupport
import com.mealkitary.common.exception.EntityNotFoundException
import io.mockk.every
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import java.util.UUID

class AcceptReservationControllerTest : WebIntegrationTestSupport() {

@Test
fun `api integration test - acceptReservation`() {
val id = UUID.randomUUID()
every { acceptReservationUseCase.accept(any()) } answers {}

mvc.perform(
MockMvcRequestBuilders.post("/reservations/{reservationId}/accept", id.toString())
)
.andExpect(MockMvcResultMatchers.status().isNoContent)
}

@Test
fun `api integration test - 예약 식별자가 UUID 형태가 아니라면 400 에러를 발생한다`() {
mvc.perform(
MockMvcRequestBuilders.post("/reservations/{reservationId}/accept", "invalid-uuid-test")
)
.andExpect(MockMvcResultMatchers.status().isBadRequest)
.andExpect(MockMvcResultMatchers.jsonPath("$.status").value("400"))
.andExpect(MockMvcResultMatchers.jsonPath("$.message").value("잘못된 UUID 형식입니다."))
}

@Test
fun `api integration test - 내부에서 EntityNotFound 에러가 발생하면 404 에러를 발생한다`() {
val id = UUID.randomUUID()
every { acceptReservationUseCase.accept(any()) }.throws(EntityNotFoundException("존재하지 않는 예약입니다."))

mvc.perform(
MockMvcRequestBuilders.post("/reservations/{reservationId}/accept", id.toString())
)
.andExpect(MockMvcResultMatchers.status().isNotFound)
.andExpect(MockMvcResultMatchers.jsonPath("$.status").value("404"))
.andExpect(MockMvcResultMatchers.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 AcceptReservationUseCase {

fun accept(reservationId: UUID)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mealkitary.reservation.application.port.output

interface SendAcceptedReservationMessagePort {

fun sendAcceptedReservationMessage()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mealkitary.reservation.application.port.output

import java.util.UUID

interface SendNewReservationMessagePort {

fun sendNewReservationMessage(reservationId: UUID, description: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mealkitary.reservation.application.service

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

@Service
@Transactional(readOnly = true)
class AcceptReservationService(
private val loadReservationPort: LoadReservationPort,
private val sendAcceptedReservationMessagePort: SendAcceptedReservationMessagePort
) : AcceptReservationUseCase {

@Transactional
override fun accept(reservationId: UUID) {
val reservation = loadReservationPort.loadOneReservationById(reservationId)

reservation.accept()

sendAcceptedReservationMessagePort.sendAcceptedReservationMessage()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import com.mealkitary.reservation.application.port.input.PayReservationRequest
import com.mealkitary.reservation.application.port.input.PayReservationUseCase
import com.mealkitary.reservation.application.port.output.LoadReservationPort
import com.mealkitary.reservation.application.port.output.SavePaymentPort
import com.mealkitary.reservation.application.port.output.SendNewReservationMessagePort
import com.mealkitary.reservation.domain.payment.ConfirmPaymentService
import com.mealkitary.reservation.domain.payment.Payment
import com.mealkitary.reservation.domain.payment.PaymentGatewayService
import com.mealkitary.reservation.domain.reservation.Reservation
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.UUID
Expand All @@ -17,6 +19,7 @@ import java.util.UUID
class PayReservationService(
private val loadReservationPort: LoadReservationPort,
private val savePaymentPort: SavePaymentPort,
private val sendNewReservationMessagePort: SendNewReservationMessagePort,
paymentGatewayService: PaymentGatewayService

) : PayReservationUseCase {
Expand All @@ -26,14 +29,19 @@ class PayReservationService(
@Transactional
override fun pay(payReservationRequest: PayReservationRequest): UUID {
val reservation = loadReservationPort.loadOneReservationById(payReservationRequest.reservationId)
val payment = Payment.of(
payReservationRequest.paymentKey,
reservation,
Money.from(payReservationRequest.amount)
)
val payment = createPayment(payReservationRequest, reservation)

confirmPaymentService.confirm(payment)

sendNewReservationMessage(reservation)

return savePaymentPort.saveOne(payment)
}

private fun createPayment(payReservationRequest: PayReservationRequest, reservation: Reservation) =
Payment.of(payReservationRequest.paymentKey, reservation, Money.from(payReservationRequest.amount))

private fun sendNewReservationMessage(reservation: Reservation) {
sendNewReservationMessagePort.sendNewReservationMessage(reservation.id, reservation.buildDescription())
}
}
Loading

0 comments on commit 8a9b0a7

Please sign in to comment.