feat(creator-channel): 커뮤니티 상세 조회 계약을 추가한다
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityPostDetailResponse(
|
||||||
|
@SerializedName("postId") val postId: Long,
|
||||||
|
@SerializedName("creatorId") val creatorId: Long,
|
||||||
|
@SerializedName("creatorNickname") val creatorNickname: String,
|
||||||
|
@SerializedName("creatorProfileUrl") val creatorProfileUrl: String?,
|
||||||
|
@SerializedName("createdAtUtc") val createdAtUtc: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("imageUrl") val imageUrl: String?,
|
||||||
|
@SerializedName("audioUrl") val audioUrl: String?,
|
||||||
|
@SerializedName("price") val price: Int,
|
||||||
|
@SerializedName("existOrdered") val existOrdered: Boolean,
|
||||||
|
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
|
||||||
|
@SerializedName("likeCount") val likeCount: Int,
|
||||||
|
@SerializedName("commentCount") val commentCount: Int,
|
||||||
|
@SerializedName("isLiked") val isLiked: Boolean,
|
||||||
|
@SerializedName("isPinned") val isPinned: Boolean,
|
||||||
|
@SerializedName("comments") val comments: CreatorChannelCommunityCommentsResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityCommentsResponse(
|
||||||
|
@SerializedName("commentCount") val commentCount: Int,
|
||||||
|
@SerializedName("comments") val comments: List<CreatorChannelCommunityCommentResponse>,
|
||||||
|
@SerializedName("page") val page: Int,
|
||||||
|
@SerializedName("size") val size: Int,
|
||||||
|
@SerializedName("hasNext") val hasNext: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityCommentResponse(
|
||||||
|
@SerializedName("commentId") val commentId: Long,
|
||||||
|
@SerializedName("writerId") val writerId: Long,
|
||||||
|
@SerializedName("writerProfileImageUrl") val writerProfileImageUrl: String,
|
||||||
|
@SerializedName("writerNickname") val writerNickname: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("isSecret") val isSecret: Boolean,
|
||||||
|
@SerializedName("createdAtUtc") val createdAtUtc: String,
|
||||||
|
@SerializedName("latestReply") val latestReply: CreatorChannelCommunityReplyResponse?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityReplyResponse(
|
||||||
|
@SerializedName("commentId") val commentId: Long,
|
||||||
|
@SerializedName("writerId") val writerId: Long,
|
||||||
|
@SerializedName("writerProfileImageUrl") val writerProfileImageUrl: String,
|
||||||
|
@SerializedName("writerNickname") val writerNickname: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("createdAtUtc") val createdAtUtc: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class CreatorChannelCommunityRepliesResponse(
|
||||||
|
@SerializedName("replies") val replies: List<CreatorChannelCommunityReplyResponse>,
|
||||||
|
@SerializedName("page") val page: Int,
|
||||||
|
@SerializedName("size") val size: Int,
|
||||||
|
@SerializedName("hasNext") val hasNext: Boolean
|
||||||
|
)
|
||||||
@@ -5,6 +5,9 @@ import kr.co.vividnext.sodalive.common.ApiResponse
|
|||||||
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.data.CreatorChannelAudioTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.data.CreatorChannelAudioTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.data.CreatorChannelCommunityTabResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentsResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityPostDetailResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityRepliesResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.data.CreatorChannelDonationTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.data.CreatorChannelLiveTabResponse
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.data.CreatorChannelLiveTabResponse
|
||||||
@@ -57,6 +60,28 @@ interface CreatorChannelApi {
|
|||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<CreatorChannelCommunityTabResponse>>
|
): Single<ApiResponse<CreatorChannelCommunityTabResponse>>
|
||||||
|
|
||||||
|
@GET("/api/v2/creator-channels/community-posts/{postId}")
|
||||||
|
fun getCommunityPostDetail(
|
||||||
|
@Path("postId") postId: Long,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<CreatorChannelCommunityPostDetailResponse>>
|
||||||
|
|
||||||
|
@GET("/api/v2/creator-channels/community-posts/{postId}/comments")
|
||||||
|
fun getCommunityPostComments(
|
||||||
|
@Path("postId") postId: Long,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<CreatorChannelCommunityCommentsResponse>>
|
||||||
|
|
||||||
|
@GET("/api/v2/creator-channels/community-comments/{commentId}/replies")
|
||||||
|
fun getCommunityCommentReplies(
|
||||||
|
@Path("commentId") commentId: Long,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<CreatorChannelCommunityRepliesResponse>>
|
||||||
|
|
||||||
@GET("/api/v2/creator-channels/{creatorId}/fan-talks")
|
@GET("/api/v2/creator-channels/{creatorId}/fan-talks")
|
||||||
fun getFanTalks(
|
fun getFanTalks(
|
||||||
@Path("creatorId") creatorId: Long,
|
@Path("creatorId") creatorId: Long,
|
||||||
|
|||||||
@@ -79,6 +79,35 @@ class CreatorChannelRepository(
|
|||||||
authHeader = token
|
authHeader = token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getCommunityPostDetail(postId: Long, token: String) = api.getCommunityPostDetail(
|
||||||
|
postId = postId,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getCommunityPostComments(
|
||||||
|
postId: Long,
|
||||||
|
page: Int = 0,
|
||||||
|
size: Int = 20,
|
||||||
|
token: String
|
||||||
|
) = api.getCommunityPostComments(
|
||||||
|
postId = postId,
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getCommunityCommentReplies(
|
||||||
|
commentId: Long,
|
||||||
|
page: Int = 0,
|
||||||
|
size: Int = 20,
|
||||||
|
token: String
|
||||||
|
) = api.getCommunityCommentReplies(
|
||||||
|
commentId = commentId,
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
fun getFanTalks(
|
fun getFanTalks(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
page: Int,
|
page: Int,
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.community
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityCommentsResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityPostDetailResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityRepliesResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.data.CreatorChannelCommunityReplyResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelApi
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class CreatorChannelCommunityDetailContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `커뮤니티 상세 API source는 신규 v2 endpoint와 ApiResponse Single 계약을 제공한다`() {
|
||||||
|
val api = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertTrue(api.contains("@GET(\"/api/v2/creator-channels/community-posts/{postId}\")"))
|
||||||
|
assertTrue(api.contains("@GET(\"/api/v2/creator-channels/community-posts/{postId}/comments\")"))
|
||||||
|
assertTrue(api.contains("@GET(\"/api/v2/creator-channels/community-comments/{commentId}/replies\")"))
|
||||||
|
assertTrue(api.contains("Single<ApiResponse<CreatorChannelCommunityPostDetailResponse>>"))
|
||||||
|
assertTrue(api.contains("Single<ApiResponse<CreatorChannelCommunityCommentsResponse>>"))
|
||||||
|
assertTrue(api.contains("Single<ApiResponse<CreatorChannelCommunityRepliesResponse>>"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `커뮤니티 상세 repository는 댓글과 답글 기본 page 0 size 20 계약을 제공한다`() {
|
||||||
|
val api = mock<CreatorChannelApi>()
|
||||||
|
val repository = CreatorChannelRepository(api, mock(), mock(), mock(), mock())
|
||||||
|
whenever(api.getCommunityPostDetail(11L, "Bearer token")).thenReturn(Single.never())
|
||||||
|
whenever(api.getCommunityPostComments(11L, 0, 20, "Bearer token")).thenReturn(Single.never())
|
||||||
|
whenever(api.getCommunityCommentReplies(22L, 0, 20, "Bearer token")).thenReturn(Single.never())
|
||||||
|
|
||||||
|
repository.getCommunityPostDetail(11L, "Bearer token")
|
||||||
|
repository.getCommunityPostComments(11L, token = "Bearer token")
|
||||||
|
repository.getCommunityCommentReplies(22L, token = "Bearer token")
|
||||||
|
|
||||||
|
verify(api).getCommunityPostDetail(11L, "Bearer token")
|
||||||
|
verify(api).getCommunityPostComments(11L, 0, 20, "Bearer token")
|
||||||
|
verify(api).getCommunityCommentReplies(22L, 0, 20, "Bearer token")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `커뮤니티 상세 응답 모델은 SerializedName과 nullable latestReply 계약을 제공한다`() {
|
||||||
|
val reply = CreatorChannelCommunityReplyResponse(
|
||||||
|
commentId = 44L,
|
||||||
|
writerId = 55L,
|
||||||
|
writerProfileImageUrl = "reply.png",
|
||||||
|
writerNickname = "reply member",
|
||||||
|
content = "reply",
|
||||||
|
createdAtUtc = "2026-07-08T00:02:00Z"
|
||||||
|
)
|
||||||
|
val comment = CreatorChannelCommunityCommentResponse(
|
||||||
|
commentId = 22L,
|
||||||
|
writerId = 33L,
|
||||||
|
writerProfileImageUrl = "member.png",
|
||||||
|
writerNickname = "member",
|
||||||
|
content = "comment",
|
||||||
|
isSecret = false,
|
||||||
|
createdAtUtc = "2026-07-08T00:01:00Z",
|
||||||
|
latestReply = reply
|
||||||
|
)
|
||||||
|
val comments = CreatorChannelCommunityCommentsResponse(
|
||||||
|
commentCount = 1,
|
||||||
|
comments = listOf(comment),
|
||||||
|
page = 0,
|
||||||
|
size = 20,
|
||||||
|
hasNext = false
|
||||||
|
)
|
||||||
|
val detail = CreatorChannelCommunityPostDetailResponse(
|
||||||
|
postId = 11L,
|
||||||
|
creatorId = 100L,
|
||||||
|
creatorNickname = "creator",
|
||||||
|
creatorProfileUrl = null,
|
||||||
|
createdAtUtc = "2026-07-08T00:00:00Z",
|
||||||
|
content = "content",
|
||||||
|
imageUrl = null,
|
||||||
|
audioUrl = null,
|
||||||
|
price = 0,
|
||||||
|
existOrdered = true,
|
||||||
|
isCommentAvailable = true,
|
||||||
|
likeCount = 1,
|
||||||
|
commentCount = 2,
|
||||||
|
isLiked = false,
|
||||||
|
isPinned = false,
|
||||||
|
comments = comments
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(11L, detail.postId)
|
||||||
|
assertEquals(comments, detail.comments)
|
||||||
|
assertEquals(reply, comment.latestReply)
|
||||||
|
assertEquals(44L, reply.commentId)
|
||||||
|
assertNotNull(
|
||||||
|
CreatorChannelCommunityPostDetailResponse::class.java.getDeclaredField("postId")
|
||||||
|
.getAnnotation(SerializedName::class.java)
|
||||||
|
)
|
||||||
|
assertNotNull(
|
||||||
|
CreatorChannelCommunityPostDetailResponse::class.java.getDeclaredField("comments")
|
||||||
|
.getAnnotation(SerializedName::class.java)
|
||||||
|
)
|
||||||
|
assertNotNull(
|
||||||
|
CreatorChannelCommunityCommentResponse::class.java.getDeclaredField("latestReply")
|
||||||
|
.getAnnotation(SerializedName::class.java)
|
||||||
|
)
|
||||||
|
assertNotNull(
|
||||||
|
CreatorChannelCommunityCommentResponse::class.java.getDeclaredField("writerId")
|
||||||
|
.getAnnotation(SerializedName::class.java)
|
||||||
|
)
|
||||||
|
assertNotNull(
|
||||||
|
CreatorChannelCommunityReplyResponse::class.java.getDeclaredField("writerId")
|
||||||
|
.getAnnotation(SerializedName::class.java)
|
||||||
|
)
|
||||||
|
assertEquals(CreatorChannelCommunityCommentsResponse(1, listOf(comment), 0, 20, false).comments.first(), comment)
|
||||||
|
assertEquals(CreatorChannelCommunityRepliesResponse(listOf(reply), 0, 20, false).replies.first(), reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun projectFile(path: String): File {
|
||||||
|
var current = File(checkNotNull(System.getProperty("user.dir"))).absoluteFile
|
||||||
|
while (current.parentFile != null && !File(current, "settings.gradle").exists()) {
|
||||||
|
current = checkNotNull(current.parentFile)
|
||||||
|
}
|
||||||
|
return File(current, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user