test #426

Merged
klaus merged 415 commits from test into main 2026-06-27 00:35:30 +00:00
4 changed files with 229 additions and 0 deletions
Showing only changes of commit 41937c7cce - Show all commits

View File

@@ -0,0 +1,30 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.domain
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
import org.springframework.stereotype.Component
@Component
class CreatorChannelFanTalkQueryPolicy {
fun createPage(page: Int?, size: Int?): CreatorChannelPage {
return CreatorChannelPage(
page = page?.coerceAtLeast(MIN_PAGE) ?: DEFAULT_PAGE,
size = size?.coerceIn(MIN_PAGE_SIZE, MAX_PAGE_SIZE) ?: DEFAULT_PAGE_SIZE
)
}
fun <T> limitItems(fetched: List<T>, page: CreatorChannelPage): List<T> {
return fetched.take(page.size)
}
fun hasNext(fetched: List<*>, page: CreatorChannelPage): Boolean {
return fetched.size > page.size
}
companion object {
private const val DEFAULT_PAGE = 0
private const val DEFAULT_PAGE_SIZE = 20
private const val MIN_PAGE = 0
private const val MIN_PAGE_SIZE = 20
private const val MAX_PAGE_SIZE = 50
}
}

View File

@@ -0,0 +1,30 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.domain
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
import java.time.LocalDateTime
data class CreatorChannelFanTalkTab(
val fanTalkCount: Int,
val fanTalks: List<CreatorChannelFanTalk>,
val page: CreatorChannelPage,
val hasNext: Boolean
)
data class CreatorChannelFanTalk(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImageUrl: String,
val content: String,
val createdAt: LocalDateTime,
val creatorReplies: List<CreatorChannelFanTalkReply>
)
data class CreatorChannelFanTalkReply(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImageUrl: String,
val content: String,
val createdAt: LocalDateTime
)

View File

@@ -0,0 +1,49 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out
import kr.co.vividnext.sodalive.member.MemberRole
import java.time.LocalDateTime
interface CreatorChannelFanTalkQueryPort {
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkCreatorRecord?
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
fun countFanTalks(creatorId: Long, viewerId: Long): Int
fun findFanTalks(
creatorId: Long,
viewerId: Long,
offset: Long,
limit: Int
): List<CreatorChannelFanTalkRecord>
fun findCreatorReplies(
creatorId: Long,
parentFanTalkIds: List<Long>
): List<CreatorChannelFanTalkReplyRecord>
}
data class CreatorChannelFanTalkCreatorRecord(
val creatorId: Long,
val role: MemberRole,
val nickname: String
)
data class CreatorChannelFanTalkRecord(
val fanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImagePath: String?,
val content: String,
val createdAt: LocalDateTime
)
data class CreatorChannelFanTalkReplyRecord(
val fanTalkId: Long,
val parentFanTalkId: Long,
val writerId: Long,
val writerNickname: String,
val writerProfileImagePath: String?,
val content: String,
val createdAt: LocalDateTime
)

View File

@@ -0,0 +1,120 @@
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.domain
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkCreatorRecord
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkRecord
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkReplyRecord
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 CreatorChannelFanTalkQueryPolicyTest {
private val policy = CreatorChannelFanTalkQueryPolicy()
@Test
@DisplayName("FanTalk 탭 page 정책은 null 요청을 기본값으로 fallback하고 fetch limit을 계산한다")
fun shouldFallbackNullPageAndSizeForFanTalkTab() {
val page = policy.createPage(page = null, size = null)
assertEquals(0, page.page)
assertEquals(20, page.size)
assertEquals(0L, page.offset)
assertEquals(21, page.fetchLimit)
}
@Test
@DisplayName("FanTalk 탭 page 정책은 최소/최대 범위로 fallback하고 fetch limit을 계산한다")
fun shouldFallbackPageAndSizeForFanTalkTab() {
val minimumPage = policy.createPage(page = -1, size = 10)
val maximumPage = policy.createPage(page = 2, size = 100)
assertEquals(0, minimumPage.page)
assertEquals(20, minimumPage.size)
assertEquals(0L, minimumPage.offset)
assertEquals(21, minimumPage.fetchLimit)
assertEquals(2, maximumPage.page)
assertEquals(50, maximumPage.size)
assertEquals(100L, maximumPage.offset)
assertEquals(51, maximumPage.fetchLimit)
}
@Test
@DisplayName("FanTalk 탭 목록 정책은 요청 size만 남기고 다음 페이지 여부를 계산한다")
fun shouldLimitItemsAndCalculateHasNext() {
val page = policy.createPage(page = 0, size = 20)
val fetched = (1..21).toList()
val items = policy.limitItems(fetched, page)
assertEquals((1..20).toList(), items)
assertTrue(policy.hasNext(fetched, page))
assertFalse(policy.hasNext((1..20).toList(), page))
assertFalse(policy.hasNext(emptyList<Int>(), page))
}
@Test
@DisplayName("FanTalk 탭 domain model과 port record는 Phase 1 계약 필드를 유지한다")
fun shouldKeepDomainAndPortContract() {
val createdAt = LocalDateTime.of(2026, 6, 22, 10, 0)
val page = policy.createPage(page = 0, size = 20)
val reply = CreatorChannelFanTalkReply(
fanTalkId = 11L,
writerId = 1L,
writerNickname = "creator",
writerProfileImageUrl = "https://cdn.test/creator.png",
content = "reply",
createdAt = createdAt.plusMinutes(1)
)
val tab = CreatorChannelFanTalkTab(
fanTalkCount = 1,
fanTalks = listOf(
CreatorChannelFanTalk(
fanTalkId = 10L,
writerId = 2L,
writerNickname = "fan",
writerProfileImageUrl = "https://cdn.test/fan.png",
content = "fan talk",
createdAt = createdAt,
creatorReplies = listOf(reply)
)
),
page = page,
hasNext = false
)
val creatorRecord = CreatorChannelFanTalkCreatorRecord(
creatorId = 1L,
role = MemberRole.CREATOR,
nickname = "creator"
)
val fanTalkRecord = CreatorChannelFanTalkRecord(
fanTalkId = 10L,
writerId = 2L,
writerNickname = "fan",
writerProfileImagePath = null,
content = "fan talk",
createdAt = createdAt
)
val replyRecord = CreatorChannelFanTalkReplyRecord(
fanTalkId = 11L,
parentFanTalkId = 10L,
writerId = 1L,
writerNickname = "creator",
writerProfileImagePath = null,
content = "reply",
createdAt = createdAt.plusMinutes(1)
)
assertEquals(1, tab.fanTalkCount)
assertEquals("fan", tab.fanTalks.first().writerNickname)
assertEquals(reply, tab.fanTalks.first().creatorReplies.first())
assertEquals(page, tab.page)
assertFalse(tab.hasNext)
assertEquals(MemberRole.CREATOR, creatorRecord.role)
assertNull(fanTalkRecord.writerProfileImagePath)
assertEquals(10L, replyRecord.parentFanTalkId)
}
}