From c5b92d250e2a77c9a67e0335d7aefe3af719c393 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 30 May 2026 17:45:06 +0900 Subject: [PATCH] =?UTF-8?q?feat(recommend):=20=ED=81=AC=EB=A6=AC=EC=97=90?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=8D=B0=EB=B7=94=20=ED=8C=90=EC=A0=95=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v2/recommend/domain/CreatorDebutPolicy.kt | 19 +++++ .../domain/CreatorDebutPolicyTest.kt | 71 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicy.kt create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicyTest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicy.kt new file mode 100644 index 00000000..8e2f8d5b --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicy.kt @@ -0,0 +1,19 @@ +package kr.co.vividnext.sodalive.v2.recommend.domain + +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit + +class CreatorDebutPolicy { + fun resolveDebutAt(firstContentPublishedAt: LocalDateTime?, firstLiveAt: LocalDateTime?): LocalDateTime? { + return listOfNotNull(firstContentPublishedAt, firstLiveAt).minOrNull() + } + + fun isNewCreator(debutAt: LocalDateTime?, now: LocalDateTime): Boolean { + if (debutAt == null) { + return false + } + + val days = ChronoUnit.DAYS.between(debutAt.toLocalDate(), now.toLocalDate()) + return days in 0..30 + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicyTest.kt new file mode 100644 index 00000000..b782cee5 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommend/domain/CreatorDebutPolicyTest.kt @@ -0,0 +1,71 @@ +package kr.co.vividnext.sodalive.v2.recommend.domain + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import java.time.LocalDateTime + +class CreatorDebutPolicyTest { + private val policy = CreatorDebutPolicy() + + @Test + @DisplayName("첫 공개 콘텐츠만 있으면 콘텐츠 공개일을 데뷔일로 선택한다") + fun shouldResolveDebutAtFromFirstContentOnly() { + val firstContentPublishedAt = LocalDateTime.of(2026, 5, 1, 10, 0) + + val debutAt = policy.resolveDebutAt(firstContentPublishedAt, firstLiveAt = null) + + assertEquals(firstContentPublishedAt, debutAt) + } + + @Test + @DisplayName("첫 라이브만 있으면 라이브 일시를 데뷔일로 선택한다") + fun shouldResolveDebutAtFromFirstLiveOnly() { + val firstLiveAt = LocalDateTime.of(2026, 5, 2, 10, 0) + + val debutAt = policy.resolveDebutAt(firstContentPublishedAt = null, firstLiveAt) + + assertEquals(firstLiveAt, debutAt) + } + + @Test + @DisplayName("첫 공개 콘텐츠와 첫 라이브가 모두 있으면 빠른 일시를 데뷔일로 선택한다") + fun shouldResolveEarliestDebutAtWhenBothExist() { + val firstContentPublishedAt = LocalDateTime.of(2026, 5, 3, 10, 0) + val firstLiveAt = LocalDateTime.of(2026, 5, 2, 10, 0) + + val debutAt = policy.resolveDebutAt(firstContentPublishedAt, firstLiveAt) + + assertEquals(firstLiveAt, debutAt) + } + + @Test + @DisplayName("첫 공개 콘텐츠와 첫 라이브가 모두 없으면 데뷔일은 null이다") + fun shouldReturnNullWhenDebutSourcesDoNotExist() { + val debutAt = policy.resolveDebutAt(firstContentPublishedAt = null, firstLiveAt = null) + + assertNull(debutAt) + } + + @Test + @DisplayName("데뷔 후 30일 이내이면 신규 크리에이터로 판정한다") + fun shouldReturnTrueWhenCreatorIsWithinThirtyDaysFromDebut() { + val now = LocalDateTime.of(2026, 5, 30, 12, 0) + + assertTrue(policy.isNewCreator(now.minusDays(0), now)) + assertTrue(policy.isNewCreator(now.minusDays(30), now)) + } + + @Test + @DisplayName("데뷔 후 30일이 지났거나 데뷔일이 없으면 신규 크리에이터가 아니다") + fun shouldReturnFalseWhenCreatorIsNotWithinThirtyDaysFromDebut() { + val now = LocalDateTime.of(2026, 5, 30, 12, 0) + + assertFalse(policy.isNewCreator(now.minusDays(31), now)) + assertFalse(policy.isNewCreator(now.plusDays(1), now)) + assertFalse(policy.isNewCreator(debutAt = null, now)) + } +}