diff --git a/build.gradle.kts b/build.gradle.kts index a2dc970..63894d5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,9 @@ plugins { id("io.spring.dependency-management") version "1.1.4" id("com.google.osdetector") version "1.7.1" kotlin("jvm") version "1.9.23" + kotlin("plugin.allopen") version "1.9.23" kotlin("plugin.spring") version "1.9.23" + kotlin("plugin.jpa") version "1.9.23" } allprojects { @@ -20,6 +22,8 @@ subprojects { plugin("org.springframework.boot") plugin("org.jetbrains.kotlin.jvm") plugin("org.jetbrains.kotlin.plugin.spring") + plugin("org.jetbrains.kotlin.plugin.jpa") + plugin("org.jetbrains.kotlin.plugin.allopen") plugin("com.google.osdetector") } @@ -66,4 +70,10 @@ subprojects { tasks.test { useJUnitPlatform() } + + allOpen { + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.Embeddable") + annotation("jakarta.persistence.MappedSuperclass") + } } diff --git a/contract-stubs/META-INF/com.example.estdelivery.event/event/1.0-SNAPSHOT/contracts/event/findEvent.kts b/contract-stubs/META-INF/com.example.estdelivery.event/event/1.0-SNAPSHOT/contracts/event/findEvent.kts new file mode 100644 index 0000000..21afb0f --- /dev/null +++ b/contract-stubs/META-INF/com.example.estdelivery.event/event/1.0-SNAPSHOT/contracts/event/findEvent.kts @@ -0,0 +1,42 @@ +package event + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = GET + url = url("/events/1") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "id" to 1, + "description" to "이벤트 설명", + "isProgress" to true, + "discountType" to "FIXED", + "intervalsProbability" to listOf( + mapOf( + "min" to 1000, + "max" to 1500, + "probability" to 0.5 + ), + mapOf( + "min" to 1600, + "max" to 2000, + "probability" to 0.3 + ), + mapOf( + "min" to 2100, + "max" to 2500, + "probability" to 0.2 + ) + ), + "discountRangeMin" to 1000, + "discountRangeMax" to 2500, + "participatedMembers" to listOf(1, 2, 3) + ) + } +} diff --git a/contract-stubs/META-INF/com.example.estdelivery.event/event/1.0-SNAPSHOT/contracts/event/participateMember.kts b/contract-stubs/META-INF/com.example.estdelivery.event/event/1.0-SNAPSHOT/contracts/event/participateMember.kts new file mode 100644 index 0000000..f9a0920 --- /dev/null +++ b/contract-stubs/META-INF/com.example.estdelivery.event/event/1.0-SNAPSHOT/contracts/event/participateMember.kts @@ -0,0 +1,12 @@ + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = PUT + url = url("/events/1/participants/1") + } + response { + status = OK + } +} diff --git a/contract-stubs/META-INF/com.example.estdelivery.member/member/1.0-SNAPSHOT/contracts/member/findMember.kts b/contract-stubs/META-INF/com.example.estdelivery.member/member/1.0-SNAPSHOT/contracts/member/findMember.kts new file mode 100644 index 0000000..e72cf08 --- /dev/null +++ b/contract-stubs/META-INF/com.example.estdelivery.member/member/1.0-SNAPSHOT/contracts/member/findMember.kts @@ -0,0 +1,20 @@ +package member + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = GET + url = url(v(consumer(regex("\\/members\\/[0-9]{1,10}")), producer("/members/1"))) + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "id" to 1, + "name" to "이건창" + ) + } +} diff --git a/contract-stubs/META-INF/com.example.estdelivery.shop/shop/1.0-SNAPSHOT/contracts/shop/findShopOwnerByShopId.kts b/contract-stubs/META-INF/com.example.estdelivery.shop/shop/1.0-SNAPSHOT/contracts/shop/findShopOwnerByShopId.kts new file mode 100644 index 0000000..226c686 --- /dev/null +++ b/contract-stubs/META-INF/com.example.estdelivery.shop/shop/1.0-SNAPSHOT/contracts/shop/findShopOwnerByShopId.kts @@ -0,0 +1,42 @@ +package shop + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +listOf( + contract { + name = "get owner shop id is 1" + request { + method = GET + url = url("/owners/shop/1") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 1, + "shopId" to 1, + "shopName" to "첫 번째 가게" + ) + } + }, + contract { + name = "get owner shop id is 2" + request { + method = GET + url = url("/owners/shop/2") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 2, + "shopId" to 2, + "shopName" to "두 번째 가게" + ) + } + } +) diff --git a/contract-stubs/META-INF/com.example.estdelivery.shop/shop/1.0-SNAPSHOT/contracts/shop/findShopOwnerByShopOwnerId.kts b/contract-stubs/META-INF/com.example.estdelivery.shop/shop/1.0-SNAPSHOT/contracts/shop/findShopOwnerByShopOwnerId.kts new file mode 100644 index 0000000..14ba94d --- /dev/null +++ b/contract-stubs/META-INF/com.example.estdelivery.shop/shop/1.0-SNAPSHOT/contracts/shop/findShopOwnerByShopOwnerId.kts @@ -0,0 +1,42 @@ +package shop + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +listOf( + contract { + name = "get owner id is 1" + request { + method = GET + url = url("/owners/1") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 1, + "shopId" to 1, + "shopName" to "첫 번째 가게" + ) + } + }, + contract { + name = "get owner id is 2" + request { + method = GET + url = url("/owners/2") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 2, + "shopId" to 2, + "shopName" to "두 번째 가게" + ) + } + } +) diff --git a/coupon/build.gradle.kts b/coupon/build.gradle.kts index 13e51a4..ff64917 100644 --- a/coupon/build.gradle.kts +++ b/coupon/build.gradle.kts @@ -1,4 +1,8 @@ - tasks.withType { enabled = true } + +dependencies { + testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner") + testImplementation("org.springframework.cloud:spring-cloud-contract-spec-kotlin") +} diff --git a/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/MemberEntity.kt b/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/MemberEntity.kt index 504a0d3..9af5a26 100644 --- a/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/MemberEntity.kt +++ b/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/MemberEntity.kt @@ -14,7 +14,9 @@ import jakarta.persistence.Table @Table(name = "member") class MemberEntity( var name: String, - @ManyToMany + @ManyToMany( + targetEntity = CouponEntity::class + ) @JoinTable( name = "member_coupon_book", joinColumns = [JoinColumn(name = "member_id")], diff --git a/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/ShopEntity.kt b/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/ShopEntity.kt index b386941..a210f3a 100644 --- a/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/ShopEntity.kt +++ b/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/entity/ShopEntity.kt @@ -13,35 +13,45 @@ import jakarta.persistence.Table @Entity @Table(name = "shop") class ShopEntity( - @ManyToMany + @ManyToMany( + targetEntity = CouponEntity::class + ) @JoinTable( name = "publish_coupon_book", joinColumns = [JoinColumn(name = "shop_id")], inverseJoinColumns = [JoinColumn(name = "coupon_id")], ) val publishedCouponBook: List, - @ManyToMany + @ManyToMany( + targetEntity = CouponEntity::class + ) @JoinTable( name = "publish_event_coupon_book", joinColumns = [JoinColumn(name = "shop_id")], inverseJoinColumns = [JoinColumn(name = "coupon_id")], ) val publishedEventCouponBook: List, - @ManyToMany + @ManyToMany( + targetEntity = CouponEntity::class + ) @JoinTable( name = "handout_coupon_book", joinColumns = [JoinColumn(name = "shop_id")], inverseJoinColumns = [JoinColumn(name = "coupon_id")], ) var handOutCouponBook: List, - @ManyToMany + @ManyToMany( + targetEntity = CouponEntity::class + ) @JoinTable( name = "use_coupon_book", joinColumns = [JoinColumn(name = "shop_id")], inverseJoinColumns = [JoinColumn(name = "coupon_id")], ) var usedCouponBook: List, - @ManyToMany + @ManyToMany( + targetEntity = MemberEntity::class + ) @JoinTable( name = "use_coupon_book", joinColumns = [JoinColumn(name = "shop_id")], diff --git a/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/repository/ShopOwnerRepository.kt b/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/repository/ShopOwnerRepository.kt index c65a5b0..872cfff 100644 --- a/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/repository/ShopOwnerRepository.kt +++ b/coupon/src/main/kotlin/com/example/estdelivery/application/port/out/persistence/repository/ShopOwnerRepository.kt @@ -2,8 +2,8 @@ package com.example.estdelivery.application.port.out.persistence.repository import com.example.estdelivery.application.port.out.persistence.entity.ShopEntity import com.example.estdelivery.application.port.out.persistence.entity.ShopOwnerEntity -import org.springframework.data.jpa.repository.JpaRepository import java.util.Optional +import org.springframework.data.jpa.repository.JpaRepository interface ShopOwnerRepository : JpaRepository { fun findByShopEntity(shopEntity: ShopEntity): Optional diff --git a/coupon/src/main/kotlin/com/example/estdelivery/domain/shop/Shop.kt b/coupon/src/main/kotlin/com/example/estdelivery/domain/shop/Shop.kt index fc53868..70be6c6 100644 --- a/coupon/src/main/kotlin/com/example/estdelivery/domain/shop/Shop.kt +++ b/coupon/src/main/kotlin/com/example/estdelivery/domain/shop/Shop.kt @@ -22,8 +22,8 @@ class Shop( coupon, CouponBook( publishedCoupons.showPublishedCoupons() + - handOutCouponBook.showHandOutCoupon() + - publishedEventCoupons.showEventCoupons(), + handOutCouponBook.showHandOutCoupon() + + publishedEventCoupons.showEventCoupons(), ), ) } diff --git a/coupon/src/test/kotlin/com/example/estdelivery/EstDeliveryCouponApplicationTests.kt b/coupon/src/test/kotlin/com/example/estdelivery/EstDeliveryCouponApplicationTests.kt index 3c0347f..095e7ef 100644 --- a/coupon/src/test/kotlin/com/example/estdelivery/EstDeliveryCouponApplicationTests.kt +++ b/coupon/src/test/kotlin/com/example/estdelivery/EstDeliveryCouponApplicationTests.kt @@ -6,5 +6,6 @@ import org.springframework.boot.test.context.SpringBootTest @SpringBootTest class EstDeliveryCouponApplicationTests { @Test - fun contextLoads() {} + fun contextLoads() { + } } diff --git a/coupon/src/test/kotlin/com/example/estdelivery/application/IssueRandomCouponServiceTest.kt b/coupon/src/test/kotlin/com/example/estdelivery/application/IssueRandomCouponServiceTest.kt index 3271813..9c69b46 100644 --- a/coupon/src/test/kotlin/com/example/estdelivery/application/IssueRandomCouponServiceTest.kt +++ b/coupon/src/test/kotlin/com/example/estdelivery/application/IssueRandomCouponServiceTest.kt @@ -66,14 +66,14 @@ class IssueRandomCouponServiceTest : FreeSpec({ isProgress = true, disCountType = EventDiscountType.FIXED, probabilityRanges = - DiscountAmountProbability( - listOf( - ProbabilityRange(1000, 1500, 0.5), - ProbabilityRange(1200, 2000, 0.3), - ProbabilityRange(2100, 3000, 0.2), - ), - DiscountRange(1000, 3000), + DiscountAmountProbability( + listOf( + ProbabilityRange(1000, 1500, 0.5), + ProbabilityRange(1200, 2000, 0.3), + ProbabilityRange(2100, 3000, 0.2), ), + DiscountRange(1000, 3000), + ), participatedMembers = emptyList(), ) val 이벤트_쿠폰 = 이벤트_쿠폰 diff --git a/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/CouponPersistenceAdapterTest.kt b/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/CouponPersistenceAdapterTest.kt index 6b56cfc..b5b35dc 100644 --- a/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/CouponPersistenceAdapterTest.kt +++ b/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/CouponPersistenceAdapterTest.kt @@ -9,7 +9,7 @@ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk -import java.util.* +import java.util.Optional class CouponPersistenceAdapterTest : FreeSpec({ var couponRepository = mockk() diff --git a/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/MemberPersistenceAdapterTest.kt b/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/MemberPersistenceAdapterTest.kt index f4befbf..443b5d6 100644 --- a/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/MemberPersistenceAdapterTest.kt +++ b/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/MemberPersistenceAdapterTest.kt @@ -10,7 +10,7 @@ import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import io.mockk.verify -import java.util.* +import java.util.Optional class MemberPersistenceAdapterTest : FreeSpec({ diff --git a/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/ShopOwnerPersistenceAdapterTest.kt b/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/ShopOwnerPersistenceAdapterTest.kt index f19e5d0..9ed9b4f 100644 --- a/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/ShopOwnerPersistenceAdapterTest.kt +++ b/coupon/src/test/kotlin/com/example/estdelivery/application/port/out/persistence/ShopOwnerPersistenceAdapterTest.kt @@ -16,7 +16,7 @@ import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import io.mockk.verify -import java.util.* +import java.util.Optional class ShopOwnerPersistenceAdapterTest : FreeSpec({ diff --git a/event/build.gradle.kts b/event/build.gradle.kts index 264002d..c585a8e 100644 --- a/event/build.gradle.kts +++ b/event/build.gradle.kts @@ -1,3 +1,28 @@ +import org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT5 + +plugins { + id("org.springframework.cloud.contract") version "4.1.2" +} + +group = "com.example.estdelivery.event" +version = "1.0-SNAPSHOT" + tasks.withType { enabled = true } + +dependencies { + testImplementation("org.springframework.cloud:spring-cloud-starter-contract-verifier") + testImplementation("org.springframework.cloud:spring-cloud-contract-spec-kotlin") +} + +tasks.contractTest { + useJUnitPlatform() +} + +contracts { + contractsDslDir.set(file("src/test/resources/contracts")) + testFramework.set(JUNIT5) + packageWithBaseClasses.set("com.example.estdelivery.event") + stubsOutputDir.set(file("../contract-stubs")) +} diff --git a/event/src/main/kotlin/com/example/estdelivery/EstDeliveryEventApplication.kt b/event/src/main/kotlin/com/example/estdelivery/event/EstDeliveryEventApplication.kt similarity index 86% rename from event/src/main/kotlin/com/example/estdelivery/EstDeliveryEventApplication.kt rename to event/src/main/kotlin/com/example/estdelivery/event/EstDeliveryEventApplication.kt index f7d7ac6..8608d12 100644 --- a/event/src/main/kotlin/com/example/estdelivery/EstDeliveryEventApplication.kt +++ b/event/src/main/kotlin/com/example/estdelivery/event/EstDeliveryEventApplication.kt @@ -1,4 +1,4 @@ -package com.example.estdelivery +package com.example.estdelivery.event import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/event/src/main/kotlin/com/example/estdelivery/event/controller/EventController.kt b/event/src/main/kotlin/com/example/estdelivery/event/controller/EventController.kt new file mode 100644 index 0000000..f780c40 --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/controller/EventController.kt @@ -0,0 +1,28 @@ +package com.example.estdelivery.event.controller + +import com.example.estdelivery.event.dto.EventResponse +import com.example.estdelivery.event.service.EventService +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class EventController( + private val eventService: EventService +) { + + @GetMapping( + value = ["/events/{id}"], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + fun getEvent(@PathVariable id: Long): EventResponse { + return eventService.findById(id) + } + + @PutMapping("/events/{id}/participants/{memberId}") + fun participateEvent(@PathVariable id: Long, @PathVariable memberId: Long) { + eventService.participate(id, memberId) + } +} diff --git a/event/src/main/kotlin/com/example/estdelivery/event/dto/EventResponse.kt b/event/src/main/kotlin/com/example/estdelivery/event/dto/EventResponse.kt new file mode 100644 index 0000000..9eba673 --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/dto/EventResponse.kt @@ -0,0 +1,15 @@ +package com.example.estdelivery.event.dto + +import com.example.estdelivery.event.entity.EventDiscountType +import com.example.estdelivery.event.entity.ProbabilityRange + +data class EventResponse( + val id: Long, + val description: String, + val isProgress: Boolean, + val discountType: EventDiscountType, + val intervalsProbability: List, + val discountRangeMin: Int, + val discountRangeMax: Int, + val participatedMembers: List, +) diff --git a/event/src/main/kotlin/com/example/estdelivery/event/entity/Event.kt b/event/src/main/kotlin/com/example/estdelivery/event/entity/Event.kt new file mode 100644 index 0000000..0e1b09f --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/entity/Event.kt @@ -0,0 +1,33 @@ +package com.example.estdelivery.event.entity + +import jakarta.persistence.CascadeType +import jakarta.persistence.CollectionTable +import jakarta.persistence.Column +import jakarta.persistence.ElementCollection +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToMany + +@Entity +class Event( + val description: String, + val isProgress: Boolean, + val disCountType: EventDiscountType, + @OneToMany(mappedBy = "event", cascade = [CascadeType.ALL], orphanRemoval = true) + val intervalsProbability: List, + val disCountRangeMin: Int, + val disCountRangeMax: Int, + @ElementCollection + @CollectionTable( + name = "event_member", + joinColumns = [JoinColumn(name = "event_id")] + ) + @Column(name = "member_id") + var participatedMembers: List = emptyList(), + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, +) diff --git a/event/src/main/kotlin/com/example/estdelivery/event/entity/EventDiscountType.kt b/event/src/main/kotlin/com/example/estdelivery/event/entity/EventDiscountType.kt new file mode 100644 index 0000000..23b8098 --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/entity/EventDiscountType.kt @@ -0,0 +1,6 @@ +package com.example.estdelivery.event.entity + +enum class EventDiscountType { + RATE, + FIXED, +} diff --git a/event/src/main/kotlin/com/example/estdelivery/event/entity/ProbabilityRange.kt b/event/src/main/kotlin/com/example/estdelivery/event/entity/ProbabilityRange.kt new file mode 100644 index 0000000..b33b54e --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/entity/ProbabilityRange.kt @@ -0,0 +1,26 @@ +package com.example.estdelivery.event.entity + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne + +@Entity +class ProbabilityRange( + val min: Int, + val max: Int, + val probability: Double, + @ManyToOne + @JoinColumn(name = "event_id") + val event: Event? = null, + @Id + @GeneratedValue + val id: Long? = null, +) { + init { + require(min < max) { "min 은 max보다 작아야 합니다." } + require(min >= 0) { "min은 0보다 작을 수 없습니다." } + require(0.0 < probability && probability < 1.0) { "probability는 0보다 커야 하고 1보다 작아야 합니다." } + } +} diff --git a/event/src/main/kotlin/com/example/estdelivery/event/repository/EventRepository.kt b/event/src/main/kotlin/com/example/estdelivery/event/repository/EventRepository.kt new file mode 100644 index 0000000..2ec6190 --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/repository/EventRepository.kt @@ -0,0 +1,6 @@ +package com.example.estdelivery.event.repository + +import com.example.estdelivery.event.entity.Event +import org.springframework.data.jpa.repository.JpaRepository + +interface EventRepository: JpaRepository \ No newline at end of file diff --git a/event/src/main/kotlin/com/example/estdelivery/event/service/EventService.kt b/event/src/main/kotlin/com/example/estdelivery/event/service/EventService.kt new file mode 100644 index 0000000..d591dac --- /dev/null +++ b/event/src/main/kotlin/com/example/estdelivery/event/service/EventService.kt @@ -0,0 +1,33 @@ +package com.example.estdelivery.event.service + +import com.example.estdelivery.event.dto.EventResponse +import com.example.estdelivery.event.repository.EventRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class EventService( + private val eventRepository: EventRepository, +) { + fun findById(id: Long): EventResponse { + val event = eventRepository.findByIdOrNull(id) + ?: throw IllegalArgumentException("Event not found. id=$id") + return EventResponse( + id = event.id, + description = event.description, + isProgress = event.isProgress, + discountType = event.disCountType, + discountRangeMin = event.disCountRangeMin, + discountRangeMax = event.disCountRangeMax, + intervalsProbability = event.intervalsProbability, + participatedMembers = event.participatedMembers, + ) + } + + fun participate(id: Long, memberId: Long) { + val event = eventRepository.findByIdOrNull(id) + ?: throw IllegalArgumentException("Event not found. id=$id") + event.participatedMembers += memberId + eventRepository.save(event) + } +} diff --git a/event/src/test/kotlin/com/example/estdelivery/EstDeliveryEventApplicationKtTest.kt b/event/src/test/kotlin/com/example/estdelivery/event/EstDeliveryEventApplicationKtTest.kt similarity index 83% rename from event/src/test/kotlin/com/example/estdelivery/EstDeliveryEventApplicationKtTest.kt rename to event/src/test/kotlin/com/example/estdelivery/event/EstDeliveryEventApplicationKtTest.kt index 5957b66..7ae4e11 100644 --- a/event/src/test/kotlin/com/example/estdelivery/EstDeliveryEventApplicationKtTest.kt +++ b/event/src/test/kotlin/com/example/estdelivery/event/EstDeliveryEventApplicationKtTest.kt @@ -1,4 +1,4 @@ -package com.example.estdelivery +package com.example.estdelivery.event import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest diff --git a/event/src/test/kotlin/com/example/estdelivery/event/EventBase.kt b/event/src/test/kotlin/com/example/estdelivery/event/EventBase.kt new file mode 100644 index 0000000..7d7a23f --- /dev/null +++ b/event/src/test/kotlin/com/example/estdelivery/event/EventBase.kt @@ -0,0 +1,35 @@ +package com.example.estdelivery.event + +import com.example.estdelivery.event.controller.EventController +import com.example.estdelivery.event.dto.EventResponse +import com.example.estdelivery.event.entity.EventDiscountType +import com.example.estdelivery.event.entity.ProbabilityRange +import com.example.estdelivery.event.service.EventService +import io.mockk.every +import io.mockk.mockk +import io.restassured.module.mockmvc.RestAssuredMockMvc +import org.junit.jupiter.api.BeforeEach + +open class EventBase { + @BeforeEach + fun setup() { + val eventService = mockk() + every { eventService.findById(1) } returns EventResponse( + 1, + "이벤트 설명", + true, + EventDiscountType.FIXED, + listOf( + ProbabilityRange(1000, 1500, 0.5), + ProbabilityRange(1600, 2000, 0.3), + ProbabilityRange(2100, 2500, 0.2), + ), + 1000, + 2500, + listOf(1, 2, 3), + ) + + every { eventService.participate(1, 1) } returns Unit + RestAssuredMockMvc.standaloneSetup(EventController(eventService)) + } +} diff --git a/event/src/test/resources/contracts/event/findEvent.kts b/event/src/test/resources/contracts/event/findEvent.kts new file mode 100644 index 0000000..21afb0f --- /dev/null +++ b/event/src/test/resources/contracts/event/findEvent.kts @@ -0,0 +1,42 @@ +package event + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = GET + url = url("/events/1") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "id" to 1, + "description" to "이벤트 설명", + "isProgress" to true, + "discountType" to "FIXED", + "intervalsProbability" to listOf( + mapOf( + "min" to 1000, + "max" to 1500, + "probability" to 0.5 + ), + mapOf( + "min" to 1600, + "max" to 2000, + "probability" to 0.3 + ), + mapOf( + "min" to 2100, + "max" to 2500, + "probability" to 0.2 + ) + ), + "discountRangeMin" to 1000, + "discountRangeMax" to 2500, + "participatedMembers" to listOf(1, 2, 3) + ) + } +} diff --git a/event/src/test/resources/contracts/event/participateMember.kts b/event/src/test/resources/contracts/event/participateMember.kts new file mode 100644 index 0000000..f9a0920 --- /dev/null +++ b/event/src/test/resources/contracts/event/participateMember.kts @@ -0,0 +1,12 @@ + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = PUT + url = url("/events/1/participants/1") + } + response { + status = OK + } +} diff --git a/member/build.gradle.kts b/member/build.gradle.kts index 264002d..68c9ef4 100644 --- a/member/build.gradle.kts +++ b/member/build.gradle.kts @@ -1,3 +1,34 @@ +import org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT5 + +plugins { + id("org.springframework.cloud.contract") version "4.1.2" +} + +group = "com.example.estdelivery.member" +version = "1.0-SNAPSHOT" + tasks.withType { enabled = true } + +dependencies { + testImplementation("org.springframework.cloud:spring-cloud-starter-contract-verifier") + testImplementation("org.springframework.cloud:spring-cloud-contract-spec-kotlin") +} + +tasks.contractTest { + useJUnitPlatform() +} + +contracts { + contractsDslDir.set(file("src/test/resources/contracts")) + testFramework.set(JUNIT5) + packageWithBaseClasses.set("com.example.estdelivery.member") + stubsOutputDir.set(file("../contract-stubs")) +} + +tasks.withType { + doFirst { + delete("~/.m2/repository/com/example/estdelivery/member") + } +} diff --git a/shop/src/main/kotlin/com/example/estdelivery/EstDeliveryShopApplication.kt b/member/src/main/kotlin/com/example/estdelivery/member/EstDeliveryMemberApplication.kt similarity index 86% rename from shop/src/main/kotlin/com/example/estdelivery/EstDeliveryShopApplication.kt rename to member/src/main/kotlin/com/example/estdelivery/member/EstDeliveryMemberApplication.kt index f7d7ac6..2282db2 100644 --- a/shop/src/main/kotlin/com/example/estdelivery/EstDeliveryShopApplication.kt +++ b/member/src/main/kotlin/com/example/estdelivery/member/EstDeliveryMemberApplication.kt @@ -1,4 +1,4 @@ -package com.example.estdelivery +package com.example.estdelivery.member import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/member/src/main/kotlin/com/example/estdelivery/member/controller/MemberController.kt b/member/src/main/kotlin/com/example/estdelivery/member/controller/MemberController.kt new file mode 100644 index 0000000..63eb888 --- /dev/null +++ b/member/src/main/kotlin/com/example/estdelivery/member/controller/MemberController.kt @@ -0,0 +1,22 @@ +package com.example.estdelivery.member.controller + +import com.example.estdelivery.member.dto.MemberResponse +import com.example.estdelivery.member.service.MemberService +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController + +@RestController +class MemberController( + private val memberService: MemberService, +) { + + @GetMapping( + value = ["/members/{id}"], + produces = [MediaType.APPLICATION_JSON_VALUE], + ) + fun findMember(@PathVariable id: Long): MemberResponse { + return memberService.findMemberById(id) + } +} diff --git a/member/src/main/kotlin/com/example/estdelivery/member/dto/MemberResponse.kt b/member/src/main/kotlin/com/example/estdelivery/member/dto/MemberResponse.kt new file mode 100644 index 0000000..23fc58b --- /dev/null +++ b/member/src/main/kotlin/com/example/estdelivery/member/dto/MemberResponse.kt @@ -0,0 +1,6 @@ +package com.example.estdelivery.member.dto + +data class MemberResponse( + val id: Long, + val name: String, +) diff --git a/member/src/main/kotlin/com/example/estdelivery/member/entiry/Member.kt b/member/src/main/kotlin/com/example/estdelivery/member/entiry/Member.kt new file mode 100644 index 0000000..c7fb9e5 --- /dev/null +++ b/member/src/main/kotlin/com/example/estdelivery/member/entiry/Member.kt @@ -0,0 +1,16 @@ +package com.example.estdelivery.member.entiry + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id + +@Entity +class Member( + val name: String, + val email: String, + val password: String, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, +) diff --git a/member/src/main/kotlin/com/example/estdelivery/member/repository/MemberRepository.kt b/member/src/main/kotlin/com/example/estdelivery/member/repository/MemberRepository.kt new file mode 100644 index 0000000..6bfdc9d --- /dev/null +++ b/member/src/main/kotlin/com/example/estdelivery/member/repository/MemberRepository.kt @@ -0,0 +1,7 @@ +package com.example.estdelivery.member.repository + +import com.example.estdelivery.member.entiry.Member +import java.util.Optional +import org.springframework.data.jpa.repository.JpaRepository + +interface MemberRepository : JpaRepository \ No newline at end of file diff --git a/member/src/main/kotlin/com/example/estdelivery/member/service/MemberService.kt b/member/src/main/kotlin/com/example/estdelivery/member/service/MemberService.kt new file mode 100644 index 0000000..3952a61 --- /dev/null +++ b/member/src/main/kotlin/com/example/estdelivery/member/service/MemberService.kt @@ -0,0 +1,18 @@ +package com.example.estdelivery.member.service + +import com.example.estdelivery.member.dto.MemberResponse +import com.example.estdelivery.member.repository.MemberRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class MemberService( + private val memberRepository: MemberRepository, +) { + fun findMemberById(id: Long): MemberResponse { + val member = memberRepository.findByIdOrNull(id) + ?: throw IllegalArgumentException("Member not found") + check(member.id == id) { "Member id mismatch" } + return MemberResponse(id, member.name) + } +} diff --git a/shop/src/test/kotlin/com/example/estdelivery/EstDeliveryShopApplicationKtTest.kt b/member/src/test/kotlin/com/example/estdelivery/member/EstDeliveryMemberApplicationKtTest.kt similarity index 65% rename from shop/src/test/kotlin/com/example/estdelivery/EstDeliveryShopApplicationKtTest.kt rename to member/src/test/kotlin/com/example/estdelivery/member/EstDeliveryMemberApplicationKtTest.kt index 1503035..72882da 100644 --- a/shop/src/test/kotlin/com/example/estdelivery/EstDeliveryShopApplicationKtTest.kt +++ b/member/src/test/kotlin/com/example/estdelivery/member/EstDeliveryMemberApplicationKtTest.kt @@ -1,10 +1,10 @@ -package com.example.estdelivery +package com.example.estdelivery.member import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest @SpringBootTest -class EstDeliveryShopApplicationKtTest { +class EstDeliveryMemberApplicationKtTest { @Test fun contextLoads() { } diff --git a/member/src/test/kotlin/com/example/estdelivery/member/MemberBase.kt b/member/src/test/kotlin/com/example/estdelivery/member/MemberBase.kt new file mode 100644 index 0000000..e3e36df --- /dev/null +++ b/member/src/test/kotlin/com/example/estdelivery/member/MemberBase.kt @@ -0,0 +1,18 @@ +package com.example.estdelivery.member + +import com.example.estdelivery.member.controller.MemberController +import com.example.estdelivery.member.dto.MemberResponse +import com.example.estdelivery.member.service.MemberService +import io.mockk.every +import io.mockk.mockk +import io.restassured.module.mockmvc.RestAssuredMockMvc +import org.junit.jupiter.api.BeforeEach + +open class MemberBase { + @BeforeEach + fun setup() { + val memberService = mockk() + every { memberService.findMemberById(1) } returns MemberResponse(1, "이건창") + RestAssuredMockMvc.standaloneSetup(MemberController(memberService)) + } +} diff --git a/member/src/test/resources/contracts/member/findMember.kts b/member/src/test/resources/contracts/member/findMember.kts new file mode 100644 index 0000000..e72cf08 --- /dev/null +++ b/member/src/test/resources/contracts/member/findMember.kts @@ -0,0 +1,20 @@ +package member + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +contract { + request { + method = GET + url = url(v(consumer(regex("\\/members\\/[0-9]{1,10}")), producer("/members/1"))) + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "id" to 1, + "name" to "이건창" + ) + } +} diff --git a/shop/build.gradle.kts b/shop/build.gradle.kts index 264002d..74c7e94 100644 --- a/shop/build.gradle.kts +++ b/shop/build.gradle.kts @@ -1,3 +1,28 @@ +import org.springframework.cloud.contract.verifier.config.TestFramework.JUNIT5 + +plugins { + id("org.springframework.cloud.contract") version "4.1.2" +} + +group = "com.example.estdelivery.shop" +version = "1.0-SNAPSHOT" + tasks.withType { enabled = true } + +dependencies { + testImplementation("org.springframework.cloud:spring-cloud-starter-contract-verifier") + testImplementation("org.springframework.cloud:spring-cloud-contract-spec-kotlin") +} + +tasks.contractTest { + useJUnitPlatform() +} + +contracts { + contractsDslDir.set(file("src/test/resources/contracts")) + testFramework.set(JUNIT5) + packageWithBaseClasses.set("com.example.estdelivery.shop") + stubsOutputDir.set(file("../contract-stubs")) +} diff --git a/member/src/main/kotlin/com/example/estdelivery/EstDeliveryShopApplication.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/EstDeliveryShopApplication.kt similarity index 87% rename from member/src/main/kotlin/com/example/estdelivery/EstDeliveryShopApplication.kt rename to shop/src/main/kotlin/com/example/estdelivery/shop/EstDeliveryShopApplication.kt index f7d7ac6..575033c 100644 --- a/member/src/main/kotlin/com/example/estdelivery/EstDeliveryShopApplication.kt +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/EstDeliveryShopApplication.kt @@ -1,4 +1,4 @@ -package com.example.estdelivery +package com.example.estdelivery.shop import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/shop/src/main/kotlin/com/example/estdelivery/shop/controller/ShopOwnerController.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/controller/ShopOwnerController.kt new file mode 100644 index 0000000..23bb153 --- /dev/null +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/controller/ShopOwnerController.kt @@ -0,0 +1,24 @@ +package com.example.estdelivery.shop.controller + +import com.example.estdelivery.shop.service.ShopOwnerService +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RestController + +@RestController +class ShopOwnerController( + private val shopOwnerService: ShopOwnerService, +) { + @GetMapping( + value = ["/owners/{id}"], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + fun findShopOwner(@PathVariable id: Long) = shopOwnerService.findShopOwnerById(id) + + @GetMapping( + value = ["/owners/shop/{id}"], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + fun findShopOwnerByShopId(@PathVariable id: Long) = shopOwnerService.findShopOwnerByShopId(id) +} diff --git a/shop/src/main/kotlin/com/example/estdelivery/shop/dto/ShopOwnerResponse.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/dto/ShopOwnerResponse.kt new file mode 100644 index 0000000..96abed4 --- /dev/null +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/dto/ShopOwnerResponse.kt @@ -0,0 +1,7 @@ +package com.example.estdelivery.shop.dto + +data class ShopOwnerResponse( + val shopOwnerId: Long, + val shopId: Long, + val shopName: String, +) diff --git a/shop/src/main/kotlin/com/example/estdelivery/shop/entity/Shop.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/entity/Shop.kt new file mode 100644 index 0000000..f3b9d9a --- /dev/null +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/entity/Shop.kt @@ -0,0 +1,19 @@ +package com.example.estdelivery.shop.entity + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + + +@Entity +@Table(name = "shop") +class Shop( + var name: String, + @Id + @Column(name = "shop_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, +) diff --git a/shop/src/main/kotlin/com/example/estdelivery/shop/entity/ShopOwner.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/entity/ShopOwner.kt new file mode 100644 index 0000000..1acfe92 --- /dev/null +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/entity/ShopOwner.kt @@ -0,0 +1,20 @@ +package com.example.estdelivery.shop.entity + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToOne +import jakarta.persistence.Table + +@Entity +@Table(name = "shop_owner") +class ShopOwner( + @OneToOne + @JoinColumn(name = "shop_id") + var shop: Shop, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, +) diff --git a/shop/src/main/kotlin/com/example/estdelivery/shop/repository/ShopOwnerRepository.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/repository/ShopOwnerRepository.kt new file mode 100644 index 0000000..cca4bf9 --- /dev/null +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/repository/ShopOwnerRepository.kt @@ -0,0 +1,8 @@ +package com.example.estdelivery.shop.repository + +import com.example.estdelivery.shop.entity.ShopOwner +import org.springframework.data.jpa.repository.JpaRepository + +interface ShopOwnerRepository: JpaRepository { + fun findByShopId(shopId: Long): ShopOwner? +} \ No newline at end of file diff --git a/shop/src/main/kotlin/com/example/estdelivery/shop/service/ShopOwnerService.kt b/shop/src/main/kotlin/com/example/estdelivery/shop/service/ShopOwnerService.kt new file mode 100644 index 0000000..cb98886 --- /dev/null +++ b/shop/src/main/kotlin/com/example/estdelivery/shop/service/ShopOwnerService.kt @@ -0,0 +1,40 @@ +package com.example.estdelivery.shop.service + +import com.example.estdelivery.shop.dto.ShopOwnerResponse +import com.example.estdelivery.shop.repository.ShopOwnerRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service + +@Service +class ShopOwnerService( + private val shopOwnerRepository: ShopOwnerRepository, +) { + fun findShopOwnerById(id: Long): ShopOwnerResponse { + val shopOwner = shopOwnerRepository.findByIdOrNull(id) + ?: throw IllegalArgumentException("Shop owner not found") + + check(shopOwner.shop.id == id) { "Shop owner with id $id is not a shop owner" } + check(shopOwner.shop.id != null) { "Shop owner with id $id has no shop id" } + + return ShopOwnerResponse( + shopOwnerId = id, + shopId = shopOwner.shop.id!!, + shopName = shopOwner.shop.name, + ) + } + + fun findShopOwnerByShopId(shopId: Long): ShopOwnerResponse { + val shopOwner = shopOwnerRepository.findByShopId(shopId) + ?: throw IllegalArgumentException("Shop owner not found") + + check(shopOwner.shop.id == shopId) { "Shop owner with shop id $shopId is not a shop owner" } + check(shopOwner.shop.id != null) { "Shop owner with shop id $shopId has no shop id" } + check(shopOwner.id != null) { "Shop owner with shop id $shopId has no shop owner id" } + + return ShopOwnerResponse( + shopOwnerId = shopOwner.id!!, + shopId = shopOwner.shop.id!!, + shopName = shopOwner.shop.name, + ) + } +} diff --git a/member/src/test/kotlin/com/example/estdelivery/EstDeliveryShopApplicationKtTest.kt b/shop/src/test/kotlin/com/example/estdelivery/shop/EstDeliveryShopApplicationKtTest.kt similarity index 84% rename from member/src/test/kotlin/com/example/estdelivery/EstDeliveryShopApplicationKtTest.kt rename to shop/src/test/kotlin/com/example/estdelivery/shop/EstDeliveryShopApplicationKtTest.kt index 1503035..04de3fc 100644 --- a/member/src/test/kotlin/com/example/estdelivery/EstDeliveryShopApplicationKtTest.kt +++ b/shop/src/test/kotlin/com/example/estdelivery/shop/EstDeliveryShopApplicationKtTest.kt @@ -1,4 +1,4 @@ -package com.example.estdelivery +package com.example.estdelivery.shop import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest diff --git a/shop/src/test/kotlin/com/example/estdelivery/shop/ShopBase.kt b/shop/src/test/kotlin/com/example/estdelivery/shop/ShopBase.kt new file mode 100644 index 0000000..b3f878e --- /dev/null +++ b/shop/src/test/kotlin/com/example/estdelivery/shop/ShopBase.kt @@ -0,0 +1,21 @@ +package com.example.estdelivery.shop + +import com.example.estdelivery.shop.controller.ShopOwnerController +import com.example.estdelivery.shop.dto.ShopOwnerResponse +import com.example.estdelivery.shop.service.ShopOwnerService +import io.mockk.every +import io.mockk.mockk +import io.restassured.module.mockmvc.RestAssuredMockMvc +import org.junit.jupiter.api.BeforeEach + +open class ShopBase { + @BeforeEach + fun setup() { + val shopOwnerService = mockk() + every { shopOwnerService.findShopOwnerById(1) } returns ShopOwnerResponse(1, 1, "첫 번째 가게") + every { shopOwnerService.findShopOwnerById(2) } returns ShopOwnerResponse(2, 2, "두 번째 가게") + every { shopOwnerService.findShopOwnerByShopId(1) } returns ShopOwnerResponse(1, 1, "첫 번째 가게") + every { shopOwnerService.findShopOwnerByShopId(2) } returns ShopOwnerResponse(2, 2, "두 번째 가게") + RestAssuredMockMvc.standaloneSetup(ShopOwnerController(shopOwnerService)) + } +} diff --git a/shop/src/test/resources/contracts/shop/findShopOwnerByShopId.kts b/shop/src/test/resources/contracts/shop/findShopOwnerByShopId.kts new file mode 100644 index 0000000..226c686 --- /dev/null +++ b/shop/src/test/resources/contracts/shop/findShopOwnerByShopId.kts @@ -0,0 +1,42 @@ +package shop + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +listOf( + contract { + name = "get owner shop id is 1" + request { + method = GET + url = url("/owners/shop/1") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 1, + "shopId" to 1, + "shopName" to "첫 번째 가게" + ) + } + }, + contract { + name = "get owner shop id is 2" + request { + method = GET + url = url("/owners/shop/2") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 2, + "shopId" to 2, + "shopName" to "두 번째 가게" + ) + } + } +) diff --git a/shop/src/test/resources/contracts/shop/findShopOwnerByShopOwnerId.kts b/shop/src/test/resources/contracts/shop/findShopOwnerByShopOwnerId.kts new file mode 100644 index 0000000..14ba94d --- /dev/null +++ b/shop/src/test/resources/contracts/shop/findShopOwnerByShopOwnerId.kts @@ -0,0 +1,42 @@ +package shop + +import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract + +listOf( + contract { + name = "get owner id is 1" + request { + method = GET + url = url("/owners/1") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 1, + "shopId" to 1, + "shopName" to "첫 번째 가게" + ) + } + }, + contract { + name = "get owner id is 2" + request { + method = GET + url = url("/owners/2") + } + response { + status = OK + headers { + contentType = "application/json" + } + body = body( + "shopOwnerId" to 2, + "shopId" to 2, + "shopName" to "두 번째 가게" + ) + } + } +)