feat(creator-channel): 커뮤니티 조회에 본문 번역을 적용한다
This commit is contained in:
+23
-4
@@ -21,6 +21,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorCha
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityQueryPort
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityQueryPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
|
||||||
import org.springframework.beans.factory.ObjectProvider
|
import org.springframework.beans.factory.ObjectProvider
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -36,6 +37,7 @@ class CreatorChannelCommunityQueryService(
|
|||||||
private val audioContentCloudFront: AudioContentCloudFront,
|
private val audioContentCloudFront: AudioContentCloudFront,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
private val langContext: LangContext,
|
||||||
|
private val creatorCommunityTranslationService: CreatorCommunityTranslationService,
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
@@ -69,6 +71,9 @@ class CreatorChannelCommunityQueryService(
|
|||||||
offset = communityPage.offset,
|
offset = communityPage.offset,
|
||||||
limit = communityPage.fetchLimit
|
limit = communityPage.fetchLimit
|
||||||
)
|
)
|
||||||
|
val communityPosts = queryPolicy.limitItems(fetchedPosts, communityPage)
|
||||||
|
val displayContents = creatorCommunityTranslationService
|
||||||
|
.findDisplayContents(communityPosts.map { it.postId }, langContext.lang.code)
|
||||||
|
|
||||||
return CreatorChannelCommunityTab(
|
return CreatorChannelCommunityTab(
|
||||||
communityPostCount = queryPort.countCommunityPosts(
|
communityPostCount = queryPort.countCommunityPosts(
|
||||||
@@ -76,7 +81,9 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
canViewAdultContent = canViewAdultContent
|
canViewAdultContent = canViewAdultContent
|
||||||
),
|
),
|
||||||
communityPosts = queryPolicy.limitItems(fetchedPosts, communityPage).map { it.toDomain(viewerId) },
|
communityPosts = communityPosts.map {
|
||||||
|
it.copy(content = displayContents[it.postId]?.content ?: it.content).toDomain(viewerId)
|
||||||
|
},
|
||||||
page = communityPage,
|
page = communityPage,
|
||||||
hasNext = queryPolicy.hasNext(fetchedPosts, communityPage)
|
hasNext = queryPolicy.hasNext(fetchedPosts, communityPage)
|
||||||
)
|
)
|
||||||
@@ -89,7 +96,7 @@ class CreatorChannelCommunityQueryService(
|
|||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
limit: Int
|
limit: Int
|
||||||
): List<CreatorChannelCommunityPost> {
|
): List<CreatorChannelCommunityPost> {
|
||||||
return queryPortProvider.getObject()
|
val posts = queryPortProvider.getObject()
|
||||||
.findHomeCommunityPosts(
|
.findHomeCommunityPosts(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
@@ -97,7 +104,12 @@ class CreatorChannelCommunityQueryService(
|
|||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
.map { it.toDomain(viewerId) }
|
val displayContents = creatorCommunityTranslationService
|
||||||
|
.findDisplayContents(posts.map { it.postId }, langContext.lang.code)
|
||||||
|
|
||||||
|
return posts.map {
|
||||||
|
it.copy(content = displayContents[it.postId]?.content ?: it.content).toDomain(viewerId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCommunityPostDetail(
|
fun getCommunityPostDetail(
|
||||||
@@ -113,8 +125,15 @@ class CreatorChannelCommunityQueryService(
|
|||||||
|
|
||||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||||
|
|
||||||
|
val locale = langContext.lang.code
|
||||||
|
val displayContent = creatorCommunityTranslationService
|
||||||
|
.findDisplayContents(listOf(postRecord.postId), locale)[postRecord.postId]
|
||||||
|
if (displayContent?.hasValidTranslation != true) {
|
||||||
|
creatorCommunityTranslationService.requestTranslations(postRecord.postId, locale)
|
||||||
|
}
|
||||||
|
|
||||||
return CreatorChannelCommunityPostDetail(
|
return CreatorChannelCommunityPostDetail(
|
||||||
post = postRecord.toDomain(viewerId),
|
post = postRecord.copy(content = displayContent?.content ?: postRecord.content).toDomain(viewerId),
|
||||||
comments = getCommunityComments(postId, viewer, page = 0, size = 20, now = now)
|
comments = getCommunityComments(postId, viewer, page = 0, size = 20, now = now)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+152
@@ -13,6 +13,8 @@ import kr.co.vividnext.sodalive.member.MemberRole
|
|||||||
import kr.co.vividnext.sodalive.member.block.BlockMember
|
import kr.co.vividnext.sodalive.member.block.BlockMember
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreference
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreference
|
||||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityDisplayContent
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@@ -47,6 +49,9 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var audioContentCloudFront: AudioContentCloudFront
|
private lateinit var audioContentCloudFront: AudioContentCloudFront
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var creatorCommunityTranslationService: CreatorCommunityTranslationService
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("커뮤니티 탭 API는 E2E로 정렬, fallback, 성인 필터, 유료 미디어 접근 정책을 반환한다")
|
@DisplayName("커뮤니티 탭 API는 E2E로 정렬, fallback, 성인 필터, 유료 미디어 접근 정책을 반환한다")
|
||||||
fun shouldReturnCommunityTabThroughControllerServiceAndRepository() {
|
fun shouldReturnCommunityTabThroughControllerServiceAndRepository() {
|
||||||
@@ -97,6 +102,41 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
Mockito.verifyNoMoreInteractions(audioContentCloudFront)
|
Mockito.verifyNoMoreInteractions(audioContentCloudFront)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 탭 API는 저장 번역을 페이지 단위로 선택하고 유료 미리보기를 유지한다")
|
||||||
|
fun shouldUseStoredTranslationsForCommunityTabWithoutScheduling() {
|
||||||
|
val fixture = createFixture()
|
||||||
|
val postIds = listOf(
|
||||||
|
fixture.pinnedPostId,
|
||||||
|
fixture.purchasedPaidPostId,
|
||||||
|
fixture.unpurchasedPaidPostId,
|
||||||
|
fixture.noImagePostId
|
||||||
|
)
|
||||||
|
Mockito.`when`(creatorCommunityTranslationService.findDisplayContents(postIds, "ja"))
|
||||||
|
.thenReturn(
|
||||||
|
mapOf(
|
||||||
|
fixture.pinnedPostId to CreatorCommunityDisplayContent("translated pinned", true),
|
||||||
|
fixture.purchasedPaidPostId to CreatorCommunityDisplayContent("translated purchased", true),
|
||||||
|
fixture.unpurchasedPaidPostId to CreatorCommunityDisplayContent("가나다라마바사아자차카타파하🙂끝", true),
|
||||||
|
fixture.noImagePostId to CreatorCommunityDisplayContent("no image community", false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/${fixture.creatorId}/community")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.communityPosts[0].content").value("translated pinned"))
|
||||||
|
.andExpect(jsonPath("$.data.communityPosts[1].content").value("translated purchased"))
|
||||||
|
.andExpect(jsonPath("$.data.communityPosts[2].content").value("가나다라마바사아자차카타파하🙂..."))
|
||||||
|
.andExpect(jsonPath("$.data.communityPosts[3].content").value("no image community"))
|
||||||
|
|
||||||
|
Mockito.verify(creatorCommunityTranslationService).findDisplayContents(postIds, "ja")
|
||||||
|
Mockito.verifyNoMoreInteractions(creatorCommunityTranslationService)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("커뮤니티 상세 API는 초기 댓글 20개와 최신 답글을 반환한다")
|
@DisplayName("커뮤니티 상세 API는 초기 댓글 20개와 최신 답글을 반환한다")
|
||||||
fun shouldReturnCommunityPostDetailWithInitialComments() {
|
fun shouldReturnCommunityPostDetailWithInitialComments() {
|
||||||
@@ -115,6 +155,74 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
.andExpect(jsonPath("$.data.comments.comments[0].createdAtUtc").value(org.hamcrest.Matchers.endsWith("Z")))
|
.andExpect(jsonPath("$.data.comments.comments[0].createdAtUtc").value(org.hamcrest.Matchers.endsWith("Z")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 상세 API는 저장 번역문에 유료 미리보기를 적용하고 요청 언어만 예약한다")
|
||||||
|
fun shouldSelectTranslatedContentBeforePaidMaskingAndRequestCurrentLanguage() {
|
||||||
|
val fixture = createTranslatedPaidDetailFixture(purchased = false)
|
||||||
|
Mockito.`when`(creatorCommunityTranslationService.findDisplayContents(listOf(fixture.postId), "ja"))
|
||||||
|
.thenReturn(mapOf(fixture.postId to CreatorCommunityDisplayContent("translated paid content", true)))
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/community-posts/${fixture.postId}")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.content").value("translated paid..."))
|
||||||
|
|
||||||
|
Mockito.verify(creatorCommunityTranslationService, Mockito.never()).requestTranslations(fixture.postId, "ja")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 상세 API는 구매자에게 선택한 유료 번역문 전체를 반환한다")
|
||||||
|
fun shouldReturnFullTranslatedContentToBuyer() {
|
||||||
|
val fixture = createTranslatedPaidDetailFixture(purchased = true)
|
||||||
|
Mockito.`when`(creatorCommunityTranslationService.findDisplayContents(listOf(fixture.postId), "ja"))
|
||||||
|
.thenReturn(mapOf(fixture.postId to CreatorCommunityDisplayContent("translated paid content", true)))
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/community-posts/${fixture.postId}")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.content").value("translated paid content"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 상세 API는 소유자에게 선택한 유료 번역문 전체를 반환한다")
|
||||||
|
fun shouldReturnFullTranslatedContentToOwner() {
|
||||||
|
val fixture = createTranslatedPaidOwnerDetailFixture()
|
||||||
|
Mockito.`when`(creatorCommunityTranslationService.findDisplayContents(listOf(fixture.postId), "ja"))
|
||||||
|
.thenReturn(mapOf(fixture.postId to CreatorCommunityDisplayContent("translated paid content", true)))
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/community-posts/${fixture.postId}")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.content").value("translated paid content"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 상세 API는 반복 요청을 모두 성공시키고 같은 요청 언어만 전달한다")
|
||||||
|
fun shouldKeepRepeatedCommunityDetailRequestsSuccessful() {
|
||||||
|
val fixture = createTranslatedPaidDetailFixture(purchased = false)
|
||||||
|
Mockito.`when`(creatorCommunityTranslationService.findDisplayContents(listOf(fixture.postId), "ja"))
|
||||||
|
.thenReturn(emptyMap())
|
||||||
|
|
||||||
|
repeat(2) {
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/community-posts/${fixture.postId}")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
).andExpect(status().isOk)
|
||||||
|
}
|
||||||
|
|
||||||
|
Mockito.verify(creatorCommunityTranslationService, Mockito.times(2)).requestTranslations(fixture.postId, "ja")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("커뮤니티 댓글 API는 댓글 페이지와 최신 답글을 반환한다")
|
@DisplayName("커뮤니티 댓글 API는 댓글 페이지와 최신 답글을 반환한다")
|
||||||
fun shouldReturnPagedCommunityCommentsWithLatestReply() {
|
fun shouldReturnPagedCommunityCommentsWithLatestReply() {
|
||||||
@@ -131,6 +239,8 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
.andExpect(jsonPath("$.data.comments.length()").value(20))
|
.andExpect(jsonPath("$.data.comments.length()").value(20))
|
||||||
.andExpect(jsonPath("$.data.hasNext").value(true))
|
.andExpect(jsonPath("$.data.hasNext").value(true))
|
||||||
.andExpect(jsonPath("$.data.comments[0].latestReply.commentId").value(fixture.latestReplyId))
|
.andExpect(jsonPath("$.data.comments[0].latestReply.commentId").value(fixture.latestReplyId))
|
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(creatorCommunityTranslationService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -148,6 +258,8 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
.andExpect(jsonPath("$.data.replyCount").value(1))
|
.andExpect(jsonPath("$.data.replyCount").value(1))
|
||||||
.andExpect(jsonPath("$.data.replies[0].commentId").value(fixture.latestReplyId))
|
.andExpect(jsonPath("$.data.replies[0].commentId").value(fixture.latestReplyId))
|
||||||
.andExpect(jsonPath("$.data.replies[0].latestReply").doesNotExist())
|
.andExpect(jsonPath("$.data.replies[0].latestReply").doesNotExist())
|
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(creatorCommunityTranslationService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -176,6 +288,8 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
)
|
)
|
||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andExpect(jsonPath("$.success").value(false))
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(creatorCommunityTranslationService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -189,6 +303,8 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
)
|
)
|
||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andExpect(jsonPath("$.success").value(false))
|
.andExpect(jsonPath("$.success").value(false))
|
||||||
|
|
||||||
|
Mockito.verifyNoInteractions(creatorCommunityTranslationService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -435,6 +551,42 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
}!!
|
}!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createTranslatedPaidDetailFixture(purchased: Boolean): PostFixture {
|
||||||
|
return transactionTemplate.execute {
|
||||||
|
val viewer = saveMember("community-translated-viewer-$purchased", MemberRole.USER)
|
||||||
|
val creator = saveMember("community-translated-creator-$purchased", MemberRole.CREATOR)
|
||||||
|
savePreference(viewer, isAdultContentVisible = true)
|
||||||
|
val post = saveCommunity(
|
||||||
|
creator = creator,
|
||||||
|
isFixed = false,
|
||||||
|
price = 100,
|
||||||
|
content = "source paid content"
|
||||||
|
)
|
||||||
|
if (purchased) {
|
||||||
|
saveCommunityOrder(viewer, post)
|
||||||
|
}
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
PostFixture(viewer = viewer, postId = post.id!!)
|
||||||
|
}!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTranslatedPaidOwnerDetailFixture(): PostFixture {
|
||||||
|
return transactionTemplate.execute {
|
||||||
|
val creator = saveMember("community-translated-owner", MemberRole.CREATOR)
|
||||||
|
savePreference(creator, isAdultContentVisible = true)
|
||||||
|
val post = saveCommunity(
|
||||||
|
creator = creator,
|
||||||
|
isFixed = false,
|
||||||
|
price = 100,
|
||||||
|
content = "source paid content"
|
||||||
|
)
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
PostFixture(viewer = creator, postId = post.id!!)
|
||||||
|
}!!
|
||||||
|
}
|
||||||
|
|
||||||
private fun createCommentUnavailableFixture(): PostFixture {
|
private fun createCommentUnavailableFixture(): PostFixture {
|
||||||
return transactionTemplate.execute {
|
return transactionTemplate.execute {
|
||||||
val viewer = saveMember("community-unavailable-viewer", MemberRole.USER)
|
val viewer = saveMember("community-unavailable-viewer", MemberRole.USER)
|
||||||
|
|||||||
+108
-1
@@ -15,6 +15,8 @@ import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorCha
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityQueryPort
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityQueryPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityDisplayContent
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertNull
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
@@ -175,6 +177,92 @@ class CreatorChannelCommunityQueryServiceTest {
|
|||||||
assertEquals(true, posts.single().isLiked)
|
assertEquals(true, posts.single().isLiked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 탭은 페이지 본문을 한 번에 번역 선택한 뒤 유료 미리보기를 적용한다")
|
||||||
|
fun shouldUseStoredTranslationsOnceForCommunityTabBeforePaidMasking() {
|
||||||
|
val port = FakeCreatorChannelCommunityQueryPort().apply {
|
||||||
|
communityPosts = listOf(
|
||||||
|
communityPostRecord(11L, price = 0).copy(content = "source free"),
|
||||||
|
communityPostRecord(12L, price = 100).copy(content = "source paid"),
|
||||||
|
communityPostRecord(13L, price = 100, existOrdered = true).copy(content = "source buyer"),
|
||||||
|
communityPostRecord(14L, creatorId = 10L, price = 100).copy(content = "source owner"),
|
||||||
|
communityPostRecord(15L, price = 0).copy(content = "source stale")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val translationService = Mockito.mock(CreatorCommunityTranslationService::class.java)
|
||||||
|
val service = createService(port, creatorCommunityTranslationService = translationService)
|
||||||
|
val postIds = port.communityPosts.map { it.postId }
|
||||||
|
Mockito.`when`(translationService.findDisplayContents(postIds, "en"))
|
||||||
|
.thenReturn(
|
||||||
|
mapOf(
|
||||||
|
11L to CreatorCommunityDisplayContent("translated free", true),
|
||||||
|
12L to CreatorCommunityDisplayContent("가나다라마바사아자차카타파하🙂끝", true),
|
||||||
|
13L to CreatorCommunityDisplayContent("translated buyer", true),
|
||||||
|
14L to CreatorCommunityDisplayContent("translated owner", true),
|
||||||
|
15L to CreatorCommunityDisplayContent("source stale", false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val posts = service.getCommunityTab(1L, createMember(id = 10L), 0, 20).communityPosts
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf(
|
||||||
|
"translated free",
|
||||||
|
"가나다라마바사아자차카타파하🙂...",
|
||||||
|
"translated buyer",
|
||||||
|
"translated owner",
|
||||||
|
"source stale"
|
||||||
|
),
|
||||||
|
posts.map { it.content }
|
||||||
|
)
|
||||||
|
Mockito.verify(translationService).findDisplayContents(postIds, "en")
|
||||||
|
Mockito.verifyNoMoreInteractions(translationService)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 탭 번역 조회는 fetchLimit 다음 게시글을 제외한 반환 페이지 ID만 사용한다")
|
||||||
|
fun shouldExcludeFetchLimitPlusOnePostFromCommunityTabTranslationLookup() {
|
||||||
|
val port = FakeCreatorChannelCommunityQueryPort().apply {
|
||||||
|
communityPosts = (1L..51L).map { communityPostRecord(it, price = 0) }
|
||||||
|
}
|
||||||
|
val translationService = Mockito.mock(CreatorCommunityTranslationService::class.java)
|
||||||
|
val service = createService(port, creatorCommunityTranslationService = translationService)
|
||||||
|
val returnedPostIds = (1L..50L).toList()
|
||||||
|
Mockito.`when`(translationService.findDisplayContents(returnedPostIds, "en")).thenReturn(emptyMap())
|
||||||
|
|
||||||
|
val tab = service.getCommunityTab(1L, createMember(id = 10L), page = 0, size = 50)
|
||||||
|
|
||||||
|
assertEquals(50, tab.communityPosts.size)
|
||||||
|
assertTrue(tab.hasNext)
|
||||||
|
Mockito.verify(translationService).findDisplayContents(returnedPostIds, "en")
|
||||||
|
Mockito.verifyNoMoreInteractions(translationService)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("채널 홈의 고정과 일반 본문은 각 그룹에서 번역 선택만 하고 작업을 만들지 않는다")
|
||||||
|
fun shouldUseStoredTranslationsForPinnedAndNormalHomeCommunityPostsWithoutScheduling() {
|
||||||
|
val port = FakeCreatorChannelCommunityQueryPort()
|
||||||
|
val translationService = Mockito.mock(CreatorCommunityTranslationService::class.java)
|
||||||
|
val service = createService(port, creatorCommunityTranslationService = translationService)
|
||||||
|
port.homeCommunityPosts = listOf(communityPostRecord(21L, price = 0).copy(content = "source pinned"))
|
||||||
|
Mockito.`when`(translationService.findDisplayContents(listOf(21L), "en"))
|
||||||
|
.thenReturn(mapOf(21L to CreatorCommunityDisplayContent("translated pinned", true)))
|
||||||
|
|
||||||
|
val pinnedPosts = service.findHomeCommunityPosts(1L, 10L, true, false, 3)
|
||||||
|
|
||||||
|
port.homeCommunityPosts = listOf(communityPostRecord(22L, price = 0).copy(content = "source normal"))
|
||||||
|
Mockito.`when`(translationService.findDisplayContents(listOf(22L), "en"))
|
||||||
|
.thenReturn(mapOf(22L to CreatorCommunityDisplayContent("translated normal", true)))
|
||||||
|
|
||||||
|
val normalPosts = service.findHomeCommunityPosts(1L, 10L, false, false, 3)
|
||||||
|
|
||||||
|
assertEquals("translated pinned", pinnedPosts.single().content)
|
||||||
|
assertEquals("translated normal", normalPosts.single().content)
|
||||||
|
Mockito.verify(translationService).findDisplayContents(listOf(21L), "en")
|
||||||
|
Mockito.verify(translationService).findDisplayContents(listOf(22L), "en")
|
||||||
|
Mockito.verifyNoMoreInteractions(translationService)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("커뮤니티 상세는 게시글과 초기 댓글 및 최신 답글을 조립한다")
|
@DisplayName("커뮤니티 상세는 게시글과 초기 댓글 및 최신 답글을 조립한다")
|
||||||
fun shouldAssembleCommunityPostDetailWithInitialComments() {
|
fun shouldAssembleCommunityPostDetailWithInitialComments() {
|
||||||
@@ -201,6 +289,20 @@ class CreatorChannelCommunityQueryServiceTest {
|
|||||||
assertEquals("https://cdn.test/profile/default-profile.png", detail.comments.comments.first().writerProfileImageUrl)
|
assertEquals("https://cdn.test/profile/default-profile.png", detail.comments.comments.first().writerProfileImageUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("v2 상세는 원문과 같은 유효 번역문에도 누락 예약을 요청하지 않는다")
|
||||||
|
fun shouldNotRequestTranslationsWhenV2DetailHasValidTranslationMatchingSourceText() {
|
||||||
|
val port = FakeCreatorChannelCommunityQueryPort()
|
||||||
|
val translationService = Mockito.mock(CreatorCommunityTranslationService::class.java)
|
||||||
|
val service = createService(port, creatorCommunityTranslationService = translationService)
|
||||||
|
Mockito.`when`(translationService.findDisplayContents(listOf(1L), "en"))
|
||||||
|
.thenReturn(mapOf(1L to CreatorCommunityDisplayContent("content-1", true)))
|
||||||
|
|
||||||
|
service.getCommunityPostDetail(1L, createMember(id = 10L), LocalDateTime.of(2026, 7, 6, 10, 0))
|
||||||
|
|
||||||
|
Mockito.verify(translationService, Mockito.never()).requestTranslations(1L, "en")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("댓글 불가 게시글은 댓글 port를 호출하지 않고 빈 댓글 응답을 반환한다")
|
@DisplayName("댓글 불가 게시글은 댓글 port를 호출하지 않고 빈 댓글 응답을 반환한다")
|
||||||
fun shouldReturnEmptyCommentsWhenCommentUnavailable() {
|
fun shouldReturnEmptyCommentsWhenCommentUnavailable() {
|
||||||
@@ -298,7 +400,9 @@ class CreatorChannelCommunityQueryServiceTest {
|
|||||||
private fun createService(
|
private fun createService(
|
||||||
port: FakeCreatorChannelCommunityQueryPort,
|
port: FakeCreatorChannelCommunityQueryPort,
|
||||||
audioContentCloudFront: AudioContentCloudFront = Mockito.mock(AudioContentCloudFront::class.java),
|
audioContentCloudFront: AudioContentCloudFront = Mockito.mock(AudioContentCloudFront::class.java),
|
||||||
canViewAdultContent: Boolean = true
|
canViewAdultContent: Boolean = true,
|
||||||
|
creatorCommunityTranslationService: CreatorCommunityTranslationService =
|
||||||
|
Mockito.mock(CreatorCommunityTranslationService::class.java)
|
||||||
): CreatorChannelCommunityQueryService {
|
): CreatorChannelCommunityQueryService {
|
||||||
val preferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
val preferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||||
Mockito.`when`(
|
Mockito.`when`(
|
||||||
@@ -306,6 +410,8 @@ class CreatorChannelCommunityQueryServiceTest {
|
|||||||
).thenReturn(canViewAdultContent)
|
).thenReturn(canViewAdultContent)
|
||||||
val langContext = LangContext()
|
val langContext = LangContext()
|
||||||
langContext.setLang(Lang.EN)
|
langContext.setLang(Lang.EN)
|
||||||
|
Mockito.`when`(creatorCommunityTranslationService.findDisplayContents(Mockito.anyList(), Mockito.anyString()))
|
||||||
|
.thenReturn(emptyMap())
|
||||||
return CreatorChannelCommunityQueryService(
|
return CreatorChannelCommunityQueryService(
|
||||||
queryPortProvider = FixedCreatorChannelCommunityQueryPortProvider(port),
|
queryPortProvider = FixedCreatorChannelCommunityQueryPortProvider(port),
|
||||||
queryPolicy = CreatorChannelCommunityQueryPolicy(),
|
queryPolicy = CreatorChannelCommunityQueryPolicy(),
|
||||||
@@ -313,6 +419,7 @@ class CreatorChannelCommunityQueryServiceTest {
|
|||||||
audioContentCloudFront = audioContentCloudFront,
|
audioContentCloudFront = audioContentCloudFront,
|
||||||
messageSource = SodaMessageSource(),
|
messageSource = SodaMessageSource(),
|
||||||
langContext = langContext,
|
langContext = langContext,
|
||||||
|
creatorCommunityTranslationService = creatorCommunityTranslationService,
|
||||||
cloudFrontHost = "https://cdn.test"
|
cloudFrontHost = "https://cdn.test"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user