test #432
@@ -0,0 +1,139 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import kr.co.vividnext.sodalive.extensions.toUtcIso
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComment
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComments
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityPostDetail
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReplies
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReply
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityPostDetailResponse(
|
||||||
|
val postId: Long,
|
||||||
|
val creatorId: Long,
|
||||||
|
val creatorNickname: String,
|
||||||
|
val creatorProfileUrl: String,
|
||||||
|
val createdAtUtc: String,
|
||||||
|
val content: String,
|
||||||
|
val imageUrl: String?,
|
||||||
|
val audioUrl: String?,
|
||||||
|
val price: Int,
|
||||||
|
@JsonProperty("isCommentAvailable")
|
||||||
|
val isCommentAvailable: Boolean,
|
||||||
|
val existOrdered: Boolean,
|
||||||
|
val likeCount: Int,
|
||||||
|
val commentCount: Int,
|
||||||
|
@JsonProperty("isPinned")
|
||||||
|
val isPinned: Boolean,
|
||||||
|
@JsonProperty("isLiked")
|
||||||
|
val isLiked: Boolean,
|
||||||
|
val comments: CreatorChannelCommunityCommentsResponse
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(detail: CreatorChannelCommunityPostDetail): CreatorChannelCommunityPostDetailResponse {
|
||||||
|
val post = detail.post
|
||||||
|
return CreatorChannelCommunityPostDetailResponse(
|
||||||
|
postId = post.postId,
|
||||||
|
creatorId = post.creatorId,
|
||||||
|
creatorNickname = post.creatorNickname,
|
||||||
|
creatorProfileUrl = post.creatorProfileUrl,
|
||||||
|
createdAtUtc = post.createdAt.toUtcIso(),
|
||||||
|
content = post.content,
|
||||||
|
imageUrl = post.imageUrl,
|
||||||
|
audioUrl = post.audioUrl,
|
||||||
|
price = post.price,
|
||||||
|
isCommentAvailable = post.isCommentAvailable,
|
||||||
|
existOrdered = post.existOrdered,
|
||||||
|
likeCount = post.likeCount,
|
||||||
|
commentCount = post.commentCount,
|
||||||
|
isPinned = post.isPinned,
|
||||||
|
isLiked = post.isLiked,
|
||||||
|
comments = CreatorChannelCommunityCommentsResponse.from(detail.comments)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityCommentsResponse(
|
||||||
|
val commentCount: Int,
|
||||||
|
val comments: List<CreatorChannelCommunityCommentResponse>,
|
||||||
|
val page: Int,
|
||||||
|
val size: Int,
|
||||||
|
@JsonProperty("hasNext")
|
||||||
|
val hasNext: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(comments: CreatorChannelCommunityComments): CreatorChannelCommunityCommentsResponse {
|
||||||
|
return CreatorChannelCommunityCommentsResponse(
|
||||||
|
commentCount = comments.commentCount,
|
||||||
|
comments = comments.comments.map(CreatorChannelCommunityCommentResponse::from),
|
||||||
|
page = comments.page.page,
|
||||||
|
size = comments.page.size,
|
||||||
|
hasNext = comments.hasNext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityCommentResponse(
|
||||||
|
val commentId: Long,
|
||||||
|
val writerProfileImageUrl: String,
|
||||||
|
val writerNickname: String,
|
||||||
|
val content: String,
|
||||||
|
val createdAtUtc: String,
|
||||||
|
val latestReply: CreatorChannelCommunityReplyResponse?
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(comment: CreatorChannelCommunityComment): CreatorChannelCommunityCommentResponse {
|
||||||
|
return CreatorChannelCommunityCommentResponse(
|
||||||
|
commentId = comment.commentId,
|
||||||
|
writerProfileImageUrl = comment.writerProfileImageUrl,
|
||||||
|
writerNickname = comment.writerNickname,
|
||||||
|
content = comment.content,
|
||||||
|
createdAtUtc = comment.createdAt.toUtcIso(),
|
||||||
|
latestReply = comment.latestReply?.let(CreatorChannelCommunityReplyResponse::from)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityRepliesResponse(
|
||||||
|
val replyCount: Int,
|
||||||
|
val replies: List<CreatorChannelCommunityReplyResponse>,
|
||||||
|
val page: Int,
|
||||||
|
val size: Int,
|
||||||
|
@JsonProperty("hasNext")
|
||||||
|
val hasNext: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(replies: CreatorChannelCommunityReplies): CreatorChannelCommunityRepliesResponse {
|
||||||
|
return CreatorChannelCommunityRepliesResponse(
|
||||||
|
replyCount = replies.replyCount,
|
||||||
|
replies = replies.replies.map(CreatorChannelCommunityReplyResponse::from),
|
||||||
|
page = replies.page.page,
|
||||||
|
size = replies.page.size,
|
||||||
|
hasNext = replies.hasNext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityReplyResponse(
|
||||||
|
val commentId: Long,
|
||||||
|
val writerProfileImageUrl: String,
|
||||||
|
val writerNickname: String,
|
||||||
|
val content: String,
|
||||||
|
val createdAtUtc: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(reply: CreatorChannelCommunityReply): CreatorChannelCommunityReplyResponse {
|
||||||
|
return CreatorChannelCommunityReplyResponse(
|
||||||
|
commentId = reply.commentId,
|
||||||
|
writerProfileImageUrl = reply.writerProfileImageUrl,
|
||||||
|
writerNickname = reply.writerNickname,
|
||||||
|
content = reply.content,
|
||||||
|
createdAtUtc = reply.createdAt.toUtcIso()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.domain
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityPostDetail(
|
||||||
|
val post: CreatorChannelCommunityPost,
|
||||||
|
val comments: CreatorChannelCommunityComments
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityComments(
|
||||||
|
val commentCount: Int,
|
||||||
|
val comments: List<CreatorChannelCommunityComment>,
|
||||||
|
val page: CreatorChannelPage,
|
||||||
|
val hasNext: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityComment(
|
||||||
|
val commentId: Long,
|
||||||
|
val writerProfileImageUrl: String,
|
||||||
|
val writerNickname: String,
|
||||||
|
val content: String,
|
||||||
|
val createdAt: LocalDateTime,
|
||||||
|
val latestReply: CreatorChannelCommunityReply?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityReplies(
|
||||||
|
val replyCount: Int,
|
||||||
|
val replies: List<CreatorChannelCommunityReply>,
|
||||||
|
val page: CreatorChannelPage,
|
||||||
|
val hasNext: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CreatorChannelCommunityReply(
|
||||||
|
val commentId: Long,
|
||||||
|
val writerProfileImageUrl: String,
|
||||||
|
val writerNickname: String,
|
||||||
|
val content: String,
|
||||||
|
val createdAt: LocalDateTime
|
||||||
|
)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComment
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComments
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityPost
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityPostDetail
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReplies
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReply
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class CreatorChannelCommunityPostDetailResponseTest {
|
||||||
|
@Test
|
||||||
|
@DisplayName("상세 응답 DTO는 is prefix boolean 필드명과 댓글/답글 구조를 유지한다")
|
||||||
|
fun shouldSerializeBooleanFieldsWithIsPrefixAndReuseCommentsResponse() {
|
||||||
|
val mapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
|
||||||
|
val response = CreatorChannelCommunityPostDetailResponse.from(createDetail())
|
||||||
|
val replies = CreatorChannelCommunityRepliesResponse.from(createReplies())
|
||||||
|
|
||||||
|
val detailJson = mapper.readTree(mapper.writeValueAsString(response))
|
||||||
|
val repliesJson = mapper.readTree(mapper.writeValueAsString(replies))
|
||||||
|
|
||||||
|
assertTrue(detailJson["isCommentAvailable"].asBoolean())
|
||||||
|
assertTrue(detailJson["isPinned"].asBoolean())
|
||||||
|
assertTrue(detailJson["isLiked"].asBoolean())
|
||||||
|
assertTrue(detailJson["comments"]["hasNext"].asBoolean())
|
||||||
|
assertTrue(detailJson["comments"]["comments"][0].has("latestReply"))
|
||||||
|
assertTrue(detailJson["comments"]["comments"][0]["latestReply"].has("commentId"))
|
||||||
|
assertTrue(repliesJson["hasNext"].asBoolean())
|
||||||
|
assertFalse(repliesJson["replies"][0].has("latestReply"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDetail(): CreatorChannelCommunityPostDetail {
|
||||||
|
val reply = CreatorChannelCommunityReply(
|
||||||
|
commentId = 2L,
|
||||||
|
writerProfileImageUrl = "https://cdn.test/reply.png",
|
||||||
|
writerNickname = "reply-writer",
|
||||||
|
content = "reply",
|
||||||
|
createdAt = LocalDateTime.of(2026, 7, 6, 3, 1)
|
||||||
|
)
|
||||||
|
return CreatorChannelCommunityPostDetail(
|
||||||
|
post = CreatorChannelCommunityPost(
|
||||||
|
postId = 1L,
|
||||||
|
creatorId = 10L,
|
||||||
|
creatorNickname = "creator",
|
||||||
|
creatorProfileUrl = "https://cdn.test/profile.png",
|
||||||
|
imageUrl = "https://cdn.test/image.png",
|
||||||
|
audioUrl = null,
|
||||||
|
content = "content",
|
||||||
|
price = 0,
|
||||||
|
createdAt = LocalDateTime.of(2026, 7, 6, 3, 0),
|
||||||
|
existOrdered = false,
|
||||||
|
isCommentAvailable = true,
|
||||||
|
likeCount = 1,
|
||||||
|
commentCount = 1,
|
||||||
|
isPinned = true,
|
||||||
|
isLiked = true
|
||||||
|
),
|
||||||
|
comments = CreatorChannelCommunityComments(
|
||||||
|
commentCount = 1,
|
||||||
|
comments = listOf(
|
||||||
|
CreatorChannelCommunityComment(
|
||||||
|
commentId = 1L,
|
||||||
|
writerProfileImageUrl = "https://cdn.test/comment.png",
|
||||||
|
writerNickname = "comment-writer",
|
||||||
|
content = "comment",
|
||||||
|
createdAt = LocalDateTime.of(2026, 7, 6, 3, 0),
|
||||||
|
latestReply = reply
|
||||||
|
)
|
||||||
|
),
|
||||||
|
page = CreatorChannelPage(0, 20),
|
||||||
|
hasNext = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createReplies(): CreatorChannelCommunityReplies {
|
||||||
|
return CreatorChannelCommunityReplies(
|
||||||
|
replyCount = 1,
|
||||||
|
replies = listOf(
|
||||||
|
CreatorChannelCommunityReply(
|
||||||
|
commentId = 2L,
|
||||||
|
writerProfileImageUrl = "https://cdn.test/reply.png",
|
||||||
|
writerNickname = "reply-writer",
|
||||||
|
content = "reply",
|
||||||
|
createdAt = LocalDateTime.of(2026, 7, 6, 3, 1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
page = CreatorChannelPage(0, 20),
|
||||||
|
hasNext = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,20 @@ class CreatorChannelCommunityQueryPolicyTest {
|
|||||||
assertEquals(51, maximumPage.fetchLimit)
|
assertEquals(51, maximumPage.fetchLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 댓글도 커뮤니티 탭과 같은 page fallback 정책을 사용한다")
|
||||||
|
fun shouldUseSamePageFallbackForCommunityComments() {
|
||||||
|
val defaultPage = policy.createPage(page = null, size = null)
|
||||||
|
val boundedPage = policy.createPage(page = -1, size = 100)
|
||||||
|
|
||||||
|
assertEquals(0, defaultPage.page)
|
||||||
|
assertEquals(20, defaultPage.size)
|
||||||
|
assertEquals(21, defaultPage.fetchLimit)
|
||||||
|
assertEquals(0, boundedPage.page)
|
||||||
|
assertEquals(50, boundedPage.size)
|
||||||
|
assertEquals(51, boundedPage.fetchLimit)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("커뮤니티 탭 목록 정책은 요청 size만 남기고 다음 페이지 여부를 계산한다")
|
@DisplayName("커뮤니티 탭 목록 정책은 요청 size만 남기고 다음 페이지 여부를 계산한다")
|
||||||
fun shouldLimitItemsAndCalculateHasNext() {
|
fun shouldLimitItemsAndCalculateHasNext() {
|
||||||
|
|||||||
Reference in New Issue
Block a user