From 90b86f4098f2694d82bd20048274582332bb4eca Mon Sep 17 00:00:00 2001 From: pricelees Date: Fri, 18 Jul 2025 01:16:05 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20ThemeEntity=20=EC=BD=94=ED=8B=80?= =?UTF-8?q?=EB=A6=B0=20=EC=A0=84=ED=99=98=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ThemeRepository.kt | 26 +-- .../persistence/ThemeRepositoryTest.kt | 155 ++++++++++++++++++ 2 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt diff --git a/src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt b/src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt index 0a48af56..f0ea0b5a 100644 --- a/src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt +++ b/src/main/java/roomescape/theme/infrastructure/persistence/ThemeRepository.kt @@ -1,14 +1,12 @@ -package roomescape.theme.infrastructure.persistence; +package roomescape.theme.infrastructure.persistence -import java.time.LocalDate; -import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.time.LocalDate -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; +interface ThemeRepository : JpaRepository { -public interface ThemeRepository extends JpaRepository { - - @Query(value = """ + @Query(value = """ SELECT t FROM ThemeEntity t RIGHT JOIN Reservation r ON t.id = r.theme.id @@ -16,17 +14,19 @@ public interface ThemeRepository extends JpaRepository { GROUP BY r.theme.id ORDER BY COUNT(r.theme.id) DESC, t.id ASC LIMIT :limit - """) - List findTopNThemeBetweenStartDateAndEndDate(LocalDate startDate, LocalDate endDate, int limit); + + """ + ) + fun findTopNThemeBetweenStartDateAndEndDate(startDate: LocalDate, endDate: LocalDate, limit: Int): List - boolean existsByName(String name); + fun existsByName(name: String): Boolean - @Query(value = """ + @Query(value = """ SELECT EXISTS( SELECT 1 FROM Reservation r WHERE r.theme.id = :id ) """) - boolean isReservedTheme(Long id); + fun isReservedTheme(id: Long): Boolean } diff --git a/src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt b/src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt new file mode 100644 index 00000000..6780ecd0 --- /dev/null +++ b/src/test/java/roomescape/theme/infrastructure/persistence/ThemeRepositoryTest.kt @@ -0,0 +1,155 @@ +package roomescape.theme.infrastructure.persistence + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainInOrder +import io.kotest.matchers.shouldBe +import jakarta.persistence.EntityManager +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.transaction.support.TransactionTemplate +import roomescape.theme.util.TestThemeCreateUtil +import java.time.LocalDate + +@DataJpaTest +class ThemeRepositoryTest( + val themeRepository: ThemeRepository, + val transactionTemplate: TransactionTemplate, + val entityManager: EntityManager +) : FunSpec() { + + init { + beforeSpec { + /** + * 테마 10개를 생성한다. + * 이름: "테마N", 예약 수: N, 날짜: 오늘 기준으로 N일 전 + */ + transactionTemplate.executeWithoutResult { + for (i in 1..10) { + TestThemeCreateUtil.createThemeWithReservations( + entityManager = entityManager, + name = "테마$i", + reservedCount = i, + date = LocalDate.now().minusDays(i.toLong()), + ) + } + } + } + + context("findTopNThemeBetweenStartDateAndEndDate") { + test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회한다.") { + themeRepository.findTopNThemeBetweenStartDateAndEndDate( + LocalDate.now().minusDays(10), + LocalDate.now().minusDays(1), + 5 + ).also { themes -> + themes.size shouldBe 5 + themes.map { it.name } shouldContainInOrder listOf( + "테마10", "테마9", "테마8", "테마7", "테마6" + ) + } + } + + test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회한다.") { + themeRepository.findTopNThemeBetweenStartDateAndEndDate( + LocalDate.now().minusDays(8), + LocalDate.now().minusDays(5), + 3 + ).also { themes -> + themes.size shouldBe 3 + themes.map { it.name } shouldContainInOrder listOf( + "테마8", "테마7", "테마6" + ) + } + } + + test("예약 수가 동일하면 먼저 생성된 테마를 우선 조회한다.") { + TestThemeCreateUtil.createThemeWithReservations( + entityManager = entityManager, + name = "테마11", + reservedCount = 5, + date = LocalDate.now().minusDays(5), + ) + + themeRepository.findTopNThemeBetweenStartDateAndEndDate( + LocalDate.now().minusDays(6), + LocalDate.now().minusDays(4), + 5 + ).also { themes -> + themes.size shouldBe 4 + themes.map { it.name } shouldContainInOrder listOf( + "테마6", "테마5", "테마11", "테마4" + ) + } + } + + test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환한다.") { + themeRepository.findTopNThemeBetweenStartDateAndEndDate( + LocalDate.now().minusDays(10), + LocalDate.now().minusDays(6), + 10 + ).also { themes -> + themes.size shouldBe 5 + } + } + + test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환한다.") { + themeRepository.findTopNThemeBetweenStartDateAndEndDate( + LocalDate.now().minusDays(10), + LocalDate.now().minusDays(1), + 15 + ).also { themes -> + themes.size shouldBe 10 + } + } + + test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트를 반환한다.") { + themeRepository.findTopNThemeBetweenStartDateAndEndDate( + LocalDate.now().plusDays(1), + LocalDate.now().plusDays(10), + 5 + ).also { themes -> + themes.size shouldBe 0 + } + } + } + context("existsByName ") { + val themeName = "test-theme" + beforeTest { + TestThemeCreateUtil.createThemeWithReservations( + entityManager = entityManager, + name = themeName, + reservedCount = 0, + date = LocalDate.now() + ) + } + test("테마 이름이 존재하면 true를 반환한다.") { + themeRepository.existsByName(themeName) shouldBe true + } + + test("테마 이름이 존재하지 않으면 false를 반환한다.") { + themeRepository.existsByName(themeName.repeat(2)) shouldBe false + } + } + + context("isReservedTheme") { + test("테마가 예약 중이면 true를 반환한다.") { + val theme = TestThemeCreateUtil.createThemeWithReservations( + entityManager = entityManager, + name = "예약된 테마", + reservedCount = 1, + date = LocalDate.now() + ) + themeRepository.isReservedTheme(theme.id!!) shouldBe true + } + + test("테마가 예약 중이 아니면 false를 반환한다.") { + val theme = TestThemeCreateUtil.createThemeWithReservations( + entityManager = entityManager, + name = "예약되지 않은 테마", + reservedCount = 0, + date = LocalDate.now() + ) + themeRepository.isReservedTheme(theme.id!!) shouldBe false + } + } + } +}