diff --git a/.gitignore b/.gitignore index 63d2f1a..c426c32 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,4 @@ out/ /.nb-gradle/ ### VS Code ### -.vscode/ +.vscode/ \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/common/entity/BaseTimeEntity.kt b/src/main/java/io/beaniejoy/dongnecafe/common/entity/BaseTimeEntity.kt index 633cad1..bc366e1 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/common/entity/BaseTimeEntity.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/common/entity/BaseTimeEntity.kt @@ -11,16 +11,20 @@ import javax.persistence.MappedSuperclass @MappedSuperclass @EntityListeners(AuditingEntityListener::class) -class BaseTimeEntity( +abstract class BaseTimeEntity protected constructor() { @CreatedDate - val createdAt: LocalDateTime = LocalDateTime.now(), + var createdAt: LocalDateTime = LocalDateTime.now() + protected set @CreatedBy - val createdBy: String = "", + var createdBy: String = "" + protected set @LastModifiedDate - val updatedAt: LocalDateTime? = null, + var updatedAt: LocalDateTime? = null + protected set @LastModifiedBy - val updatedBy: String? = null -) \ No newline at end of file + var updatedBy: String? = null + protected set +} \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt index 19d68f5..21b2f2f 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt @@ -2,7 +2,7 @@ package io.beaniejoy.dongnecafe.domain.cafe.controller import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto -import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeUpdateRequestDto +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.CafeInfoRequestDto import io.beaniejoy.dongnecafe.domain.cafe.service.CafeService import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable @@ -15,8 +15,19 @@ import org.springframework.web.bind.annotation.* class CafeController( private val cafeService: CafeService ) { + @PostMapping + fun createCafe(@RequestBody resource: CafeInfoRequestDto): Long { + return cafeService.createCafe( + name = resource.name!!, + address = resource.address!!, + phoneNumber = resource.phoneNumber!!, + description = resource.description!!, + cafeMenuRequestList = resource.cafeMenuList + ) + } + @GetMapping - fun searchCafeList( + fun searchCafe( @PageableDefault(sort = ["name"], direction = Sort.Direction.ASC, page = 0, size = 10) pageable: Pageable ): Page { return cafeService.getCafeList(pageable) @@ -31,7 +42,7 @@ class CafeController( @PutMapping("/{id}") fun updateCafeInfo( @PathVariable("id") id: Long, - @RequestBody resource: CafeUpdateRequestDto + @RequestBody resource: CafeInfoRequestDto ): String { cafeService.updateCafe( id = id, diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/cafe/CafeUpdateRequestDto.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/cafe/CafeUpdateRequestDto.kt deleted file mode 100644 index 3664938..0000000 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/cafe/CafeUpdateRequestDto.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.beaniejoy.dongnecafe.domain.cafe.dto.cafe - -data class CafeUpdateRequestDto( - val name: String? = null, - val address: String? = null, - val phoneNumber: String? = null, - val description: String? = null -) \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/CafeInfoRequestDto.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/CafeInfoRequestDto.kt new file mode 100644 index 0000000..70fa918 --- /dev/null +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/CafeInfoRequestDto.kt @@ -0,0 +1,9 @@ +package io.beaniejoy.dongnecafe.domain.cafe.dto.request + +data class CafeInfoRequestDto( + val name: String? = null, + val address: String? = null, + val phoneNumber: String? = null, + val description: String? = null, + val cafeMenuList: List = arrayListOf() +) \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/CafeMenuInfoRequestDto.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/CafeMenuInfoRequestDto.kt new file mode 100644 index 0000000..1621c4e --- /dev/null +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/CafeMenuInfoRequestDto.kt @@ -0,0 +1,9 @@ +package io.beaniejoy.dongnecafe.domain.cafe.dto.request + +import java.math.BigDecimal + +data class CafeMenuInfoRequestDto( + val name: String? = null, + val price: BigDecimal = BigDecimal.ZERO, + val menuOptionList: List +) diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/MenuOptionInfoRequestDto.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/MenuOptionInfoRequestDto.kt new file mode 100644 index 0000000..f38d6d5 --- /dev/null +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/dto/request/MenuOptionInfoRequestDto.kt @@ -0,0 +1,13 @@ +package io.beaniejoy.dongnecafe.domain.cafe.dto.request + +import java.math.BigDecimal + +data class MenuOptionInfoRequestDto( + val title: String, + val optionDetailList: List +) + +data class OptionDetailInfoRequestDto( + val name: String, + val extraPrice: BigDecimal +) \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/Cafe.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/Cafe.kt index c5bd554..bd694fb 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/Cafe.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/Cafe.kt @@ -1,37 +1,84 @@ package io.beaniejoy.dongnecafe.domain.cafe.entity import io.beaniejoy.dongnecafe.common.entity.BaseTimeEntity +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.CafeMenuInfoRequestDto import javax.persistence.* @Entity @Table(name = "cafe") -class Cafe( +class Cafe protected constructor( + name: String, + address: String, + phoneNumber: String, + description: String, +) : BaseTimeEntity() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0L, + val id: Long = 0L @Column(name = "name", nullable = false) - var name: String, + var name: String = name + protected set @Column(name = "address", nullable = false) - var address: String, + var address: String = address + protected set @Column(name = "phone_number", nullable = false) - var phoneNumber: String, + var phoneNumber: String = phoneNumber + protected set @Column(name = "total_rate", nullable = false) - val totalRate: Double, + val totalRate: Double = 0.0 @Column(name = "description", nullable = false) - var description: String, + var description: String = description + protected set - @OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY) - val cafeMenuList: MutableList, + @OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + val cafeMenuList: MutableList = arrayListOf() - @OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY) - val cafeImageList: MutableList -) : BaseTimeEntity() { - fun updateInfo(name: String, address: String, phoneNumber: String, description: String) { + @OneToMany(mappedBy = "cafe", fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + val cafeImageList: MutableList = arrayListOf() + + companion object { + fun createCafe( + name: String, + address: String, + phoneNumber: String, + description: String, + cafeMenuRequestList: List, + ): Cafe { + val cafeMenuEntityList = cafeMenuRequestList.map { cafeMenuRequestDto -> + CafeMenu.createCafeMenu( + name = cafeMenuRequestDto.name!!, + price = cafeMenuRequestDto.price, + menuOptionRequestList = cafeMenuRequestDto.menuOptionList + ) + } + + return Cafe( + name = name, + address = address, + phoneNumber = phoneNumber, + description = description + ).apply { + cafeMenuEntityList.forEach { this.addCafeMenu(it) } + } + } + } + + fun addCafeMenu(cafeMenu: CafeMenu) { + this.cafeMenuList.add(cafeMenu) + cafeMenu.updateCafe(this) + } + + fun updateInfo( + name: String, + address: String, + phoneNumber: String, + description: String, + ) { this.name = name this.address = address this.phoneNumber = phoneNumber diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeMenu.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeMenu.kt index b8f2872..ed9d00d 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeMenu.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeMenu.kt @@ -1,26 +1,58 @@ package io.beaniejoy.dongnecafe.domain.cafe.entity import io.beaniejoy.dongnecafe.common.entity.BaseTimeEntity +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.MenuOptionInfoRequestDto import java.math.BigDecimal import javax.persistence.* @Entity @Table(name = "cafe_menu") -class CafeMenu( +class CafeMenu protected constructor( + name: String, + price: BigDecimal, +) : BaseTimeEntity() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0L, + val id: Long = 0L @Column(name = "name", nullable = false) - val name: String, + val name: String = name @Column(name = "price", nullable = false) - val price: BigDecimal = BigDecimal.ZERO, + val price: BigDecimal = price - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "cafe_id", nullable = false) - val cafe: Cafe, + var cafe: Cafe? = null + protected set - @OneToMany(mappedBy = "cafeMenu", fetch = FetchType.LAZY) - val menuOptionList: MutableList -) : BaseTimeEntity() \ No newline at end of file + @OneToMany(mappedBy = "cafeMenu", fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) + val menuOptionList: MutableList = arrayListOf() + + companion object { + fun createCafeMenu(name: String, price: BigDecimal, menuOptionRequestList: List): CafeMenu { + val menuOptionEntityList = menuOptionRequestList.map { menuOptionRequestDto -> + MenuOption.createMenuOption( + title = menuOptionRequestDto.title, + optionDetailRequestList = menuOptionRequestDto.optionDetailList + ) + } + + return CafeMenu( + name = name, + price = price + ).apply { + menuOptionEntityList.forEach { this.addMenuOption(it) } + } + } + } + + fun updateCafe(cafe: Cafe) { + this.cafe = cafe + } + + fun addMenuOption(menuOption: MenuOption) { + this.menuOptionList.add(menuOption) + menuOption.updateCafeMenu(this) + } +} \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/MenuOption.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/MenuOption.kt index 65884ba..ac18c11 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/MenuOption.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/MenuOption.kt @@ -1,22 +1,52 @@ package io.beaniejoy.dongnecafe.domain.cafe.entity import io.beaniejoy.dongnecafe.common.entity.BaseTimeEntity +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.OptionDetailInfoRequestDto import javax.persistence.* @Entity @Table(name = "menu_option") -class MenuOption( +class MenuOption protected constructor( + title: String +) : BaseTimeEntity() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0L, + val id: Long = 0L @Column(name = "title", nullable = false) - val title: String, + val title: String = title - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "menu_id", nullable = false) - val cafeMenu: CafeMenu, + var cafeMenu: CafeMenu? = null + protected set - @OneToMany(mappedBy = "menuOption", fetch = FetchType.EAGER) - val optionDetailList: MutableList -) : BaseTimeEntity() \ No newline at end of file + @OneToMany(mappedBy = "menuOption", fetch = FetchType.EAGER, cascade = [CascadeType.ALL]) + val optionDetailList: MutableList = arrayListOf() + + companion object { + fun createMenuOption(title: String, optionDetailRequestList: List): MenuOption { + val optionDetailEntityList = optionDetailRequestList.map { optionDetailRequestDto -> + OptionDetail.createOptionDetail( + name = optionDetailRequestDto.name, + extraPrice = optionDetailRequestDto.extraPrice + ) + } + + return MenuOption( + title = title + ).apply { + optionDetailEntityList.forEach { this.addOptionDetail(it) } + } + } + } + + fun updateCafeMenu(cafeMenu: CafeMenu) { + this.cafeMenu = cafeMenu + } + + fun addOptionDetail(optionDetail: OptionDetail) { + this.optionDetailList.add(optionDetail) + optionDetail.updateMenuOption(this) + } +} \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/OptionDetail.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/OptionDetail.kt index 68b8bf1..59b83f0 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/OptionDetail.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/entity/OptionDetail.kt @@ -6,18 +6,35 @@ import javax.persistence.* @Entity @Table(name = "option_detail") -class OptionDetail( +class OptionDetail protected constructor( + name: String, + extraPrice: BigDecimal +) : BaseTimeEntity() { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0L, + val id: Long = 0L @Column(name = "name", nullable = false) - val name: String, + val name: String = name @Column(name = "extra_price", nullable = false) - val extraPrice: BigDecimal, + val extraPrice: BigDecimal = extraPrice - @ManyToOne + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "option_id", nullable = false) - val menuOption: MenuOption -): BaseTimeEntity() \ No newline at end of file + var menuOption: MenuOption? = null + protected set + + companion object { + fun createOptionDetail(name: String, extraPrice: BigDecimal): OptionDetail { + return OptionDetail( + name = name, + extraPrice = extraPrice + ) + } + } + + fun updateMenuOption(menuOption: MenuOption) { + this.menuOption = menuOption + } +} \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/error/CafeExistedException.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/error/CafeExistedException.kt new file mode 100644 index 0000000..3775d57 --- /dev/null +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/error/CafeExistedException.kt @@ -0,0 +1,3 @@ +package io.beaniejoy.dongnecafe.domain.cafe.error + +class CafeExistedException(name: String): RuntimeException("Cafe[$name] is already existed") \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepository.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepository.kt index 3d0314f..8cd1bac 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepository.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepository.kt @@ -4,4 +4,5 @@ import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe import org.springframework.data.jpa.repository.JpaRepository interface CafeRepository : JpaRepository { + fun findByName(name: String): Cafe? } \ No newline at end of file diff --git a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt index adeffc1..c3563df 100644 --- a/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt +++ b/src/main/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt @@ -2,7 +2,12 @@ package io.beaniejoy.dongnecafe.domain.cafe.service import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeInfoResponseDto import io.beaniejoy.dongnecafe.domain.cafe.dto.cafe.CafeSearchResponseDto +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.CafeMenuInfoRequestDto import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe +import io.beaniejoy.dongnecafe.domain.cafe.entity.CafeMenu +import io.beaniejoy.dongnecafe.domain.cafe.entity.MenuOption +import io.beaniejoy.dongnecafe.domain.cafe.entity.OptionDetail +import io.beaniejoy.dongnecafe.domain.cafe.error.CafeExistedException import io.beaniejoy.dongnecafe.domain.cafe.error.CafeNotFoundException import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeRepository import mu.KLogging @@ -13,20 +18,56 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service -@Transactional +@Transactional(readOnly = true) class CafeService( - private val cafeRepository: CafeRepository + private val cafeRepository: CafeRepository, ) { - companion object: KLogging() + companion object : KLogging() + + /** + * 카페 생성 로직 + * - 카페 생성시 카페정보 뿐만 아니라 하위 메뉴정보, 옵션, 옵션상세 같이 생성 + * - 카페 정보(이름, 주소, 전화번호, 소개글) + * - 카페 메뉴정보 (메뉴 이름, 가격 /ex. 아메리카노, 2,800) + * - 메뉴 옵션 (옵션 이름 /ex. 사이즈) + * - 옵션 상세 (상세 이름, 추가 금액 /ex. [(medium, 0), (large, 200), (venti, 700)]) + */ + @Transactional + fun createCafe( + name: String, + address: String, + phoneNumber: String, + description: String, + cafeMenuRequestList: List, + ): Long { + checkCafeExistedByName(name) + + val cafe = Cafe.createCafe( + name = name, + address = address, + phoneNumber = phoneNumber, + description = description, + cafeMenuRequestList = cafeMenuRequestList + ) + + val savedCafe = cafeRepository.save(cafe) + + return savedCafe.id + } + + private fun checkCafeExistedByName(name: String) { + val findCafe = cafeRepository.findByName(name) + if (findCafe != null) { + throw CafeExistedException(name) + } + } - @Transactional(readOnly = true) fun getCafeList(pageable: Pageable): Page { val cafeList: Page = cafeRepository.findAll(pageable) return cafeList.map { CafeSearchResponseDto.of(it) } } - @Transactional(readOnly = true) fun getCafeInfoByCafeId(id: Long): CafeInfoResponseDto { val cafe = cafeRepository.findByIdOrNull(id) ?: throw CafeNotFoundException(id) @@ -34,6 +75,11 @@ class CafeService( return CafeInfoResponseDto.of(cafe) } + /** + * 카페 정보 수정 + * - 카페 정보만 수정 (하위 엔티티에 대해서는 각 도메인 영역에서 수정) + */ + @Transactional fun updateCafe( id: Long, name: String, diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b923ae6..e3e2509 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,8 +11,13 @@ spring: hibernate: dialect: org.hibernate.dialect.MySQL5InnoDBDialect format_sql: true - show-sql: true + show-sql: false flyway: baseline-on-migrate: true locations: classpath:db/migration,classpath:db/seed # baseline-version: 0 + +logging: + level: + org.hibernate.SQL: debug # logger 통해 로깅 +# org.hibernate.type: trace diff --git a/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeTest.kt b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeTest.kt new file mode 100644 index 0000000..f3d6628 --- /dev/null +++ b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/entity/CafeTest.kt @@ -0,0 +1,21 @@ +package io.beaniejoy.dongnecafe.domain.cafe.entity + +import io.beaniejoy.dongnecafe.domain.cafe.utils.CafeTestUtils +import org.junit.jupiter.api.Test + +internal class CafeTest { + @Test + fun create_cafe_test() { + val cafeRequestDto = CafeTestUtils.createCafeRequestDto() + + val cafe = Cafe.createCafe( + name = cafeRequestDto.name!!, + address = cafeRequestDto.address!!, + phoneNumber = cafeRequestDto.phoneNumber!!, + description = cafeRequestDto.description!!, + cafeMenuRequestList = cafeRequestDto.cafeMenuList + ) + + CafeTestUtils.assertCafeEquals(request = cafeRequestDto, entity = cafe) + } +} \ No newline at end of file diff --git a/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepositoryTest.kt b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepositoryTest.kt new file mode 100644 index 0000000..8e085fd --- /dev/null +++ b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/repository/CafeRepositoryTest.kt @@ -0,0 +1,84 @@ +package io.beaniejoy.dongnecafe.domain.cafe.repository + +import io.beaniejoy.dongnecafe.common.config.AuditingConfig +import io.beaniejoy.dongnecafe.common.entity.BaseEntityAuditorAware +import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe +import io.beaniejoy.dongnecafe.domain.cafe.utils.CafeTestUtils +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.FilterType +import org.springframework.data.repository.findByIdOrNull + +@DataJpaTest( + includeFilters = [ + ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [AuditingConfig::class]), + ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [BaseEntityAuditorAware::class]) + ] +) +internal class CafeRepositoryTest { + @Autowired + lateinit var cafeRepository: CafeRepository + + @Test + @DisplayName("[JPA] 신규 Cafe save 테스트") + fun save_cafe_test() { + val cafeRequestDto = CafeTestUtils.createCafeRequestDto() + val cafe = cafeRequestDto.let { + Cafe.createCafe( + name = it.name!!, + address = it.address!!, + phoneNumber = it.phoneNumber!!, + description = it.description!!, + cafeMenuRequestList = it.cafeMenuList + ) + } + + val savedCafe = cafeRepository.save(cafe) + + assertEquals(1L, savedCafe.id) + CafeTestUtils.assertCafeEquals(cafeRequestDto, savedCafe) + } + + @Test + @DisplayName("[JPA] 기존 Cafe 정보 update 테스트") + fun update_cafe_test() { + // TODO 테스트용 카페 데이터 주입 구성하기 + val cafeRequestDto = CafeTestUtils.createCafeRequestDto() + val cafe = cafeRequestDto.let { + Cafe.createCafe( + name = it.name!!, + address = it.address!!, + phoneNumber = it.phoneNumber!!, + description = it.description!!, + cafeMenuRequestList = it.cafeMenuList + ) + } + + val savedId = cafeRepository.save(cafe).id + + val findCafe = cafeRepository.findByIdOrNull(savedId) + + val updatedName = "update cafe name" + val updatedAddress = "update cafe address" + val updatedPhoneNumber = "01011112222" + val updatedDescription = "update description" + + findCafe!!.updateInfo( + name = updatedName, + address = updatedAddress, + phoneNumber = updatedPhoneNumber, + description = updatedDescription + ) + + val updatedCafe = cafeRepository.findByIdOrNull(savedId) + + assertEquals(updatedName, updatedCafe!!.name) + assertEquals(updatedAddress, updatedCafe.address) + assertEquals(updatedPhoneNumber, updatedCafe.phoneNumber) + assertEquals(updatedDescription, updatedCafe.description) + } +} \ No newline at end of file diff --git a/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt new file mode 100644 index 0000000..9cb91c7 --- /dev/null +++ b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt @@ -0,0 +1,149 @@ +package io.beaniejoy.dongnecafe.domain.cafe.service + +import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe +import io.beaniejoy.dongnecafe.domain.cafe.error.CafeExistedException +import io.beaniejoy.dongnecafe.domain.cafe.error.CafeNotFoundException +import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeRepository +import io.beaniejoy.dongnecafe.domain.cafe.utils.CafeTestUtils +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.junit.jupiter.MockitoExtension +import java.util.* +import javax.persistence.GeneratedValue + +@ExtendWith(MockitoExtension::class) +@TestMethodOrder(MethodOrderer.DisplayName::class) +internal class CafeServiceTest { + @InjectMocks + lateinit var mockCafeService: CafeService + + @Mock + lateinit var mockCafeRepository: CafeRepository + + @Test + @DisplayName("카페 신규 생성 테스트") + fun create_cafe_test() { + // given + val cafeRequestDto = CafeTestUtils.createCafeRequestDto() + val savedMockCafeId = 100L + + `when`(mockCafeRepository.findByName(cafeRequestDto.name!!)).thenReturn(null) + `when`(mockCafeRepository.save(any(Cafe::class.java))).thenAnswer { + injectCafeId(it.getArgument(0), savedMockCafeId) + } + + // when + val savedCafeId = mockCafeService.createCafe( + name = cafeRequestDto.name!!, + address = cafeRequestDto.address!!, + phoneNumber = cafeRequestDto.phoneNumber!!, + description = cafeRequestDto.description!!, + cafeMenuRequestList = cafeRequestDto.cafeMenuList + ) + + // then + verify(mockCafeRepository).findByName(cafeRequestDto.name!!) // TODO eq 에러 발생 이유 + verify(mockCafeRepository).save(any(Cafe::class.java)) + + assertEquals(savedCafeId, savedMockCafeId) + } + + @Test + @DisplayName("카페 신규 생성시 이미 존재하는 카페 예외 발생 테스트") + fun fail_create_cafe_when_existed() { + // given + val cafeRequestDto = CafeTestUtils.createCafeRequestDto() + val cafe = Cafe.createCafe( + name = cafeRequestDto.name!!, + address = cafeRequestDto.address!!, + phoneNumber = cafeRequestDto.phoneNumber!!, + description = cafeRequestDto.description!!, + cafeMenuRequestList = cafeRequestDto.cafeMenuList + ) + + `when`(mockCafeRepository.findByName(cafeRequestDto.name!!)).thenReturn(cafe) + + // then + assertThrows { + // when + mockCafeService.createCafe( + name = cafeRequestDto.name!!, + address = cafeRequestDto.address!!, + phoneNumber = cafeRequestDto.phoneNumber!!, + description = cafeRequestDto.description!!, + cafeMenuRequestList = cafeRequestDto.cafeMenuList + ) + } + + verify(mockCafeRepository).findByName(cafeRequestDto.name!!) + } + + @Test + @DisplayName("카페 정보 변경 테스트") + fun update_cafe_test() { + // given + val cafeRequestDto = CafeTestUtils.createCafeRequestDto() + val cafe = Cafe.createCafe( + name = cafeRequestDto.name!!, + address = cafeRequestDto.address!!, + phoneNumber = cafeRequestDto.phoneNumber!!, + description = cafeRequestDto.description!!, + cafeMenuRequestList = cafeRequestDto.cafeMenuList + ) + val cafeId = 50L + + // TODO findByIdOrNull은 kotlin test 라이브러리 필요한 듯 + `when`(mockCafeRepository.findById(cafeId)).thenReturn(Optional.of(cafe)) + + // then + mockCafeService.updateCafe( + id = cafeId, + name = "", + address = "", + phoneNumber = "", + description = "", + ) + + verify(mockCafeRepository).findById(eq(cafeId)) + + // TODO update TEST 방법? + } + + @Test + @DisplayName("카페 정보 변경시 존재하지 않는 카페 예외 발생 테스트") + fun fail_update_cafe_when_not_found() { + // given + val cafeId = 50L + + `when`(mockCafeRepository.findById(cafeId)).thenReturn(Optional.empty()) + + assertThrows { + mockCafeService.updateCafe( + id = cafeId, + name = "", + address = "", + phoneNumber = "", + description = "", + ) + } + } + + private fun injectCafeId( + cafe: Cafe, + newCafeId: Long, + ): Cafe { + val idField = cafe.javaClass.declaredFields + .find { f -> + f.getAnnotation(GeneratedValue::class.java) != null + } ?: return cafe + + idField.isAccessible = true + idField.set(cafe, newCafeId) + + return cafe + } +} \ No newline at end of file diff --git a/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/utils/CafeTestUtils.kt b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/utils/CafeTestUtils.kt new file mode 100644 index 0000000..f16125a --- /dev/null +++ b/src/test/java/io/beaniejoy/dongnecafe/domain/cafe/utils/CafeTestUtils.kt @@ -0,0 +1,102 @@ +package io.beaniejoy.dongnecafe.domain.cafe.utils + +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.CafeInfoRequestDto +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.CafeMenuInfoRequestDto +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.MenuOptionInfoRequestDto +import io.beaniejoy.dongnecafe.domain.cafe.dto.request.OptionDetailInfoRequestDto +import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe +import io.beaniejoy.dongnecafe.domain.cafe.entity.CafeMenu +import io.beaniejoy.dongnecafe.domain.cafe.entity.MenuOption +import io.beaniejoy.dongnecafe.domain.cafe.entity.OptionDetail +import org.junit.jupiter.api.Assertions.* +import java.math.BigDecimal + +class CafeTestUtils { + companion object { + fun assertCafeEquals(request: CafeInfoRequestDto, entity: Cafe) { + assertEquals(request.name, entity.name) + assertEquals(request.address, entity.address) + assertEquals(request.phoneNumber, entity.phoneNumber) + assertEquals(request.description, entity.description) + + assertCafeMenuListEquals(request.cafeMenuList, entity.cafeMenuList) + } + + private fun assertCafeMenuListEquals( + cafeMenuRequestList: List, + cafeMenuList: List, + ) { + for (index in cafeMenuRequestList.indices) { + assertEquals(cafeMenuRequestList[index].name, cafeMenuList[index].name) + assertEquals(cafeMenuRequestList[index].price, cafeMenuList[index].price) + + assertMenuOptionListEquals( + cafeMenuRequestList[index].menuOptionList, + cafeMenuList[index].menuOptionList + ) + } + } + + private fun assertMenuOptionListEquals( + menuOptionRequestList: List, + menuOptionList: List, + ) { + for (index in menuOptionRequestList.indices) { + assertEquals(menuOptionRequestList[index].title, menuOptionList[index].title) + + assertOptionDetailListEquals( + menuOptionRequestList[index].optionDetailList, + menuOptionList[index].optionDetailList + ) + } + } + + private fun assertOptionDetailListEquals( + optionDetailRequestList: List, + optionDetailList: MutableList, + ) { + for (index in optionDetailRequestList.indices) { + assertEquals(optionDetailRequestList[index].name, optionDetailList[index].name) + assertEquals(optionDetailRequestList[index].extraPrice, optionDetailList[index].extraPrice) + } + } + + fun createCafeRequestDto(): CafeInfoRequestDto { + val cafeName = "beanie_cafe" + val cafeAddress = "beanie_cafe_address" + val phoneNumber = "01012345678" + val description = "beanie_cafe_description" + + val sizeOptionDetailList = listOf( + OptionDetailInfoRequestDto(name = "medium", extraPrice = BigDecimal.ZERO), + OptionDetailInfoRequestDto(name = "large", extraPrice = BigDecimal.valueOf(200L)), + OptionDetailInfoRequestDto(name = "venti", extraPrice = BigDecimal.valueOf(700L)) + ) + val sizeMenuOption = MenuOptionInfoRequestDto( + title = "size", + optionDetailList = sizeOptionDetailList + ) + + val cafeMenuList = listOf( + CafeMenuInfoRequestDto( + name = "menu1", + price = BigDecimal.valueOf(2_800L), + menuOptionList = listOf(sizeMenuOption) + ), + CafeMenuInfoRequestDto( + name = "menu2", + price = BigDecimal.valueOf(3_500L), + menuOptionList = listOf(sizeMenuOption) + ), + ) + + return CafeInfoRequestDto( + name = cafeName, + address = cafeAddress, + phoneNumber = phoneNumber, + description = description, + cafeMenuList = cafeMenuList + ) + } + } +} \ No newline at end of file diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..dc94f3f --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,13 @@ +spring: + flyway: + enabled: false + jpa: + properties: + hibernate: + format_sql: true + show_sql: false + +logging: + level: + org.hibernate.SQL: debug + org.hibernate.type: trace \ No newline at end of file