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)
|
||||
}
|
||||
|
||||
@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
|
||||
@DisplayName("커뮤니티 탭 목록 정책은 요청 size만 남기고 다음 페이지 여부를 계산한다")
|
||||
fun shouldLimitItemsAndCalculateHasNext() {
|
||||
|
||||
Reference in New Issue
Block a user