feat(channel-home): 비밀 후원을 권한에 따라 익명 표시한다
This commit is contained in:
+185
-3
@@ -1,5 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.home
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
|
||||
import kr.co.vividnext.sodalive.common.CountryContext
|
||||
import kr.co.vividnext.sodalive.content.AudioContent
|
||||
@@ -11,6 +12,7 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
|
||||
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
|
||||
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationMessage
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.translation.SourceTextNormalizer
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisit
|
||||
@@ -19,8 +21,14 @@ import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.adapter.out.persistence.CreatorCommunityTranslation
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorLocalizedNameEntity
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
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 org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
@@ -49,7 +57,8 @@ import javax.persistence.EntityManager
|
||||
class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
||||
private val mockMvc: MockMvc,
|
||||
private val entityManager: EntityManager,
|
||||
private val transactionTemplate: TransactionTemplate
|
||||
private val transactionTemplate: TransactionTemplate,
|
||||
private val objectMapper: ObjectMapper
|
||||
) {
|
||||
@MockBean
|
||||
private lateinit var countryContext: CountryContext
|
||||
@@ -67,6 +76,7 @@ class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
||||
.andExpect(jsonPath("$.data.currentLive.liveId").value(fixture.currentLiveId))
|
||||
.andExpect(jsonPath("$.data.latestAudioContent.audioContentId").value(fixture.latestAudioId))
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].can").value(500))
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].isSecret").value(false))
|
||||
.andExpect(jsonPath("$.data.notices[0].postId").value(fixture.noticeId))
|
||||
.andExpect(jsonPath("$.data.notices[0].isPinned").value(true))
|
||||
.andExpect(jsonPath("$.data.notices[0].isCommentAvailable").value(true))
|
||||
@@ -106,6 +116,164 @@ class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
||||
.andExpect(jsonPath("$.data.communities[0].content").value("translated community"))
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}, {1}")
|
||||
@CsvSource(
|
||||
"creator, ko", "donor, ko", "defaultDonor, ko", "outsider, ko", "otherCreator, ko",
|
||||
"creator, en", "donor, en", "defaultDonor, en", "outsider, en", "otherCreator, en",
|
||||
"creator, ja", "donor, ja", "defaultDonor, ja", "outsider, ja", "otherCreator, ja"
|
||||
)
|
||||
@DisplayName("홈 비밀 후원은 본인과 채널 주인만 원본을 보고 타인은 요청 언어로 익명 표시한다")
|
||||
fun shouldMaskOnlyOtherMembersSecretDonationsThroughHttp(viewerRole: String, language: String) {
|
||||
val fixture = transactionTemplate.execute {
|
||||
val creator = saveMember("secret-home-creator", MemberRole.CREATOR)
|
||||
val donor = saveMember("deleted_secret-home-donor", MemberRole.CREATOR)
|
||||
val defaultDonor = saveMember("secret-home-default-donor", MemberRole.USER).apply { profileImage = null }
|
||||
val viewers = mapOf(
|
||||
"creator" to creator,
|
||||
"donor" to donor,
|
||||
"defaultDonor" to defaultDonor,
|
||||
"outsider" to saveMember("secret-home-outsider", MemberRole.USER),
|
||||
"otherCreator" to saveMember("secret-home-other-creator", MemberRole.CREATOR)
|
||||
)
|
||||
listOf(Lang.EN, Lang.JA).forEach { lang ->
|
||||
entityManager.persist(CreatorLocalizedNameEntity(donor.id!!, lang, "deleted_localized-${lang.code}"))
|
||||
}
|
||||
val start = currentKstMonthStartUtc()
|
||||
val rows = listOf(
|
||||
saveDonation(creator, donor, start.plusMinutes(1), 100, false, "public thanks"),
|
||||
saveDonation(creator, defaultDonor, start.plusMinutes(2), 200, false, null),
|
||||
saveDonation(creator, donor, start.plusMinutes(3), 300, true, "private thanks"),
|
||||
saveDonation(creator, donor, start.plusMinutes(4), 400, true, null),
|
||||
saveDonation(creator, donor, start.plusMinutes(5), 500, true, ""),
|
||||
saveDonation(creator, defaultDonor, start.plusMinutes(6), 600, true, "other private thanks")
|
||||
)
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
val persistedRows = rows.map { entityManager.find(ChannelDonationMessage::class.java, it.id!!) }
|
||||
persistedRows.forEach { row -> assertEquals(start.plusMinutes((row.can / 100).toLong()), row.createdAt) }
|
||||
DonationFixture(creator.id!!, donor.id!!, viewers.getValue(viewerRole), persistedRows)
|
||||
} ?: throw IllegalStateException("donation fixture creation failed")
|
||||
val result = mockMvc.perform(
|
||||
get("/api/v2/creator-channels/${fixture.creatorId}/home")
|
||||
.header("Accept-Language", language)
|
||||
.with(user(MemberAdapter(fixture.viewer)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.data.donationCount").doesNotExist())
|
||||
.andReturn()
|
||||
val donations = objectMapper.readTree(result.response.contentAsByteArray)["data"]["channelDonations"]
|
||||
assertEquals(listOf(600, 500, 400, 300, 200, 100), donations.map { it["can"].asInt() })
|
||||
val anonymousNickname = mapOf("ko" to "익명", "en" to "Anonymous", "ja" to "匿名").getValue(language)
|
||||
val anonymousMessage = mapOf(
|
||||
"ko" to "크리에이터에게만 보여요",
|
||||
"en" to "Only visible to the creator",
|
||||
"ja" to "クリエイターにのみ表示されます"
|
||||
).getValue(language)
|
||||
fixture.donations.sortedByDescending { it.can }.zip(donations).forEach { (stored, item) ->
|
||||
assertTrue(item.path("isSecret").isBoolean)
|
||||
assertEquals(stored.isSecret, item["isSecret"].asBoolean())
|
||||
assertEquals(stored.can, item["can"].asInt())
|
||||
assertEquals(
|
||||
requireNotNull(stored.createdAt).atOffset(ZoneOffset.UTC).toInstant().toString(),
|
||||
item["createdAtUtc"].asText()
|
||||
)
|
||||
assertEquals(
|
||||
setOf("nickname", "profileImageUrl", "can", "message", "createdAtUtc", "isSecret"),
|
||||
item.fieldNames().asSequence().toSet()
|
||||
)
|
||||
val masked = stored.isSecret && fixture.viewer.id != fixture.creatorId && fixture.viewer.id != stored.member?.id
|
||||
if (masked) {
|
||||
assertEquals(anonymousNickname, item["nickname"].asText())
|
||||
assertEquals(anonymousMessage, item["message"].asText())
|
||||
assertEquals("https://cdn.test/profile/anonymous-profile.png", item["profileImageUrl"].asText())
|
||||
assertFalse(item.toString().contains("secret-home-donor"))
|
||||
assertFalse(item.toString().contains("localized-"))
|
||||
assertFalse(item.toString().contains("private thanks"))
|
||||
} else {
|
||||
val isDonor = stored.member?.id == fixture.donorId
|
||||
val nickname = if (isDonor) {
|
||||
if (language == "ko") "secret-home-donor" else "deleted_localized-$language"
|
||||
} else {
|
||||
"secret-home-default-donor"
|
||||
}
|
||||
val profile = if (isDonor) "deleted_secret-home-donor.png" else "profile/default-profile.png"
|
||||
assertEquals(nickname, item["nickname"].asText())
|
||||
assertEquals(stored.additionalMessage.orEmpty(), item["message"].asText())
|
||||
assertEquals("https://cdn.test/$profile", item["profileImageUrl"].asText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("홈은 KST 월 시작을 포함하고 다음 월을 제외하며 공개·비밀 전체에서 최신 8건을 반환한다")
|
||||
fun shouldKeepKstMonthBoundsAndLatestEightMixedDonationsThroughHttp() {
|
||||
fun nextMonthStartUtc(monthStartUtc: LocalDateTime): LocalDateTime =
|
||||
monthStartUtc.plusHours(9).plusMonths(1).minusHours(9)
|
||||
|
||||
assertEquals(
|
||||
LocalDateTime.of(2026, 12, 31, 15, 0),
|
||||
nextMonthStartUtc(LocalDateTime.of(2026, 11, 30, 15, 0))
|
||||
)
|
||||
val start = currentKstMonthStartUtc()
|
||||
val end = nextMonthStartUtc(start)
|
||||
val members = transactionTemplate.execute {
|
||||
val creator = saveMember("month-home-creator", MemberRole.CREATOR)
|
||||
val donor = saveMember("month-home-donor", MemberRole.USER)
|
||||
val viewer = saveMember("month-home-viewer", MemberRole.USER)
|
||||
saveDonation(creator, donor, start.minusSeconds(1), 900, true)
|
||||
saveDonation(creator, donor, start, 901, true)
|
||||
saveDonation(creator, donor, end, 902, true)
|
||||
saveDonation(creator, donor, end.minusSeconds(1), 903, false)
|
||||
Triple(creator, donor, viewer)
|
||||
} ?: throw IllegalStateException("month fixture creation failed")
|
||||
|
||||
val boundaryResult = mockMvc.perform(
|
||||
get("/api/v2/creator-channels/${members.first.id}/home").with(user(MemberAdapter(members.third)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].createdAtUtc").value("${end.minusSeconds(1)}Z"))
|
||||
.andExpect(jsonPath("$.data.channelDonations[1].createdAtUtc").value("$start:00Z"))
|
||||
.andExpect(jsonPath("$.data.channelDonations[1].isSecret").value(true))
|
||||
.andReturn()
|
||||
val boundaryItems = objectMapper.readTree(boundaryResult.response.contentAsByteArray)["data"]["channelDonations"]
|
||||
assertEquals(listOf(903, 901), boundaryItems.map { it["can"].asInt() })
|
||||
|
||||
transactionTemplate.execute {
|
||||
val creator = entityManager.find(Member::class.java, members.first.id)
|
||||
val donor = entityManager.find(Member::class.java, members.second.id)
|
||||
(1..10).forEach { index ->
|
||||
saveDonation(creator, donor, start.plusHours((index / 2).toLong()), index, index % 2 == 1)
|
||||
}
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
val result = mockMvc.perform(
|
||||
get("/api/v2/creator-channels/${members.first.id}/home").with(user(MemberAdapter(members.third)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andReturn()
|
||||
val items = objectMapper.readTree(result.response.contentAsByteArray)["data"]["channelDonations"]
|
||||
assertEquals(listOf(903, 10, 9, 8, 7, 6, 5, 4), items.map { it["can"].asInt() })
|
||||
assertEquals(listOf(false, false, true, false, true, false, true, false), items.map { it["isSecret"].asBoolean() })
|
||||
val persisted = transactionTemplate.execute {
|
||||
entityManager.createQuery(
|
||||
"select d from ChannelDonationMessage d where d.creator.id = :creatorId order by d.createdAt desc, d.id desc",
|
||||
ChannelDonationMessage::class.java
|
||||
).setParameter("creatorId", members.first.id).resultList.associateBy { it.can }
|
||||
}!!
|
||||
items.forEach { item ->
|
||||
val stored = persisted.getValue(item["can"].asInt())
|
||||
assertEquals(
|
||||
requireNotNull(stored.createdAt).atOffset(ZoneOffset.UTC).toInstant().toString(),
|
||||
item["createdAtUtc"].asText()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun currentKstMonthStartUtc(): LocalDateTime =
|
||||
LocalDateTime.now(ZoneOffset.UTC).plusHours(9).toLocalDate().withDayOfMonth(1).atStartOfDay().minusHours(9)
|
||||
|
||||
private fun createFixture(): Fixture {
|
||||
return transactionTemplate.execute {
|
||||
val now = LocalDateTime.now(ZoneOffset.UTC)
|
||||
@@ -231,8 +399,15 @@ class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
||||
return order
|
||||
}
|
||||
|
||||
private fun saveDonation(creator: Member, donor: Member, createdAt: LocalDateTime): ChannelDonationMessage {
|
||||
val donation = ChannelDonationMessage(can = 500, isSecret = false, additionalMessage = "e2e thanks")
|
||||
private fun saveDonation(
|
||||
creator: Member,
|
||||
donor: Member,
|
||||
createdAt: LocalDateTime,
|
||||
can: Int = 500,
|
||||
isSecret: Boolean = false,
|
||||
message: String? = "e2e thanks"
|
||||
): ChannelDonationMessage {
|
||||
val donation = ChannelDonationMessage(can = can, isSecret = isSecret, additionalMessage = message)
|
||||
donation.creator = creator
|
||||
donation.member = donor
|
||||
entityManager.persist(donation)
|
||||
@@ -301,6 +476,13 @@ class CreatorChannelHomeEndToEndTest @Autowired constructor(
|
||||
.executeUpdate()
|
||||
}
|
||||
|
||||
private data class DonationFixture(
|
||||
val creatorId: Long,
|
||||
val donorId: Long,
|
||||
val viewer: Member,
|
||||
val donations: List<ChannelDonationMessage>
|
||||
)
|
||||
|
||||
private data class Fixture(
|
||||
val viewer: Member,
|
||||
val creatorId: Long,
|
||||
|
||||
+3
-2
@@ -139,7 +139,7 @@ class CreatorChannelHomeControllerTest @Autowired constructor(
|
||||
.andExpect(jsonPath("$.data.schedules[0].isAdult").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].donationId").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].memberId").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].isSecret").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.channelDonations[0].isSecret").value(true))
|
||||
.andExpect(jsonPath("$.data.notices[0].dateUtc").value("2026-06-12T04:00:00Z"))
|
||||
.andExpect(jsonPath("$.data.notices[0].imageUrl").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.notices[0].audioUrl").doesNotExist())
|
||||
@@ -259,7 +259,8 @@ class CreatorChannelHomeControllerTest @Autowired constructor(
|
||||
profileImageUrl = "fan.png",
|
||||
can = 50,
|
||||
message = "thanks",
|
||||
createdAt = LocalDateTime.of(2026, 6, 12, 2, 0)
|
||||
createdAt = LocalDateTime.of(2026, 6, 12, 2, 0),
|
||||
isSecret = true
|
||||
)
|
||||
),
|
||||
notices = listOf(post),
|
||||
|
||||
+3
-1
@@ -64,6 +64,7 @@ class CreatorChannelHomeFacadeTest {
|
||||
assertTrue(response.latestAudioContent?.isOwned == true)
|
||||
assertFalse(response.latestAudioContent?.isRented == true)
|
||||
assertEquals("thanks", response.channelDonations.first().message)
|
||||
assertTrue(response.channelDonations.first().isSecret)
|
||||
assertEquals(301L, response.notices.first().postId)
|
||||
assertEquals("2026-06-12T04:00:00Z", response.notices.first().dateUtc)
|
||||
assertNull(response.notices.first().imageUrl)
|
||||
@@ -190,7 +191,8 @@ class CreatorChannelHomeFacadeTest {
|
||||
profileImageUrl = "fan.png",
|
||||
can = 50,
|
||||
message = "thanks",
|
||||
createdAt = LocalDateTime.of(2026, 6, 12, 2, 0)
|
||||
createdAt = LocalDateTime.of(2026, 6, 12, 2, 0),
|
||||
isSecret = true
|
||||
)
|
||||
),
|
||||
notices = listOf(post),
|
||||
|
||||
+28
-5
@@ -779,13 +779,16 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("채널 후원은 KST 기준 이번 달과 크리에이터의 비밀 후원 열람을 반영한다")
|
||||
fun shouldFindKstMonthDonationsAndExposeSecretDonationToCreator() {
|
||||
@DisplayName("채널 후원은 KST 기준 이번 달 공개·비밀 후원을 조회자와 무관하게 포함한다")
|
||||
fun shouldFindAllKstMonthDonationsForEveryViewer() {
|
||||
val now = LocalDateTime.of(2026, 5, 31, 15, 30)
|
||||
val creator = saveMember("kst-donation-creator", MemberRole.CREATOR)
|
||||
val donor = saveMember("kst-donation-donor", MemberRole.USER)
|
||||
saveDonation(creator, donor, 300, LocalDateTime.of(2026, 5, 31, 15, 10), isSecret = true)
|
||||
saveDonation(creator, donor, 100, LocalDateTime.of(2026, 5, 31, 14, 50), isSecret = false)
|
||||
saveDonation(creator, donor, 200, LocalDateTime.of(2026, 5, 31, 15, 0), isSecret = true)
|
||||
saveDonation(creator, donor, 400, LocalDateTime.of(2026, 6, 30, 14, 59, 59), isSecret = true)
|
||||
saveDonation(creator, donor, 500, LocalDateTime.of(2026, 6, 30, 15, 0), isSecret = true)
|
||||
val thirdParty = saveMember("kst-donation-third-party", MemberRole.USER)
|
||||
flushAndClear()
|
||||
|
||||
@@ -793,9 +796,29 @@ class DefaultCreatorChannelHomeQueryRepositoryTest @Autowired constructor(
|
||||
val donorView = repository.findChannelDonations(creator.id!!, donor.id!!, now, limit = 8, lang = Lang.KO)
|
||||
val thirdPartyView = repository.findChannelDonations(creator.id!!, thirdParty.id!!, now, limit = 8, lang = Lang.KO)
|
||||
|
||||
assertEquals(listOf(300), creatorView.map { it.can })
|
||||
assertEquals(listOf(300), donorView.map { it.can })
|
||||
assertEquals(emptyList<Int>(), thirdPartyView.map { it.can })
|
||||
assertEquals(listOf(400, 300, 200), creatorView.map { it.can })
|
||||
assertEquals(listOf(400, 300, 200), donorView.map { it.can })
|
||||
assertEquals(listOf(400, 300, 200), thirdPartyView.map { it.can })
|
||||
assertEquals(listOf(true, true, true), thirdPartyView.map { it.isSecret })
|
||||
assertEquals(LocalDateTime.of(2026, 5, 31, 15, 0), thirdPartyView.last().createdAt)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("홈 후원은 공개·비밀 후보 전체 중 생성 시각과 id 내림차순으로 최신 8건을 조회한다")
|
||||
fun shouldFindLatestEightAcrossPublicAndSecretDonations() {
|
||||
val now = LocalDateTime.of(2026, 6, 12, 12, 0)
|
||||
val creator = saveMember("mixed-home-creator", MemberRole.CREATOR)
|
||||
val donor = saveMember("mixed-home-donor", MemberRole.USER)
|
||||
val viewer = saveMember("mixed-home-viewer", MemberRole.USER)
|
||||
(1..10).forEach { index ->
|
||||
saveDonation(creator, donor, index, now.minusMinutes((index / 2).toLong()), isSecret = index % 2 == 1)
|
||||
}
|
||||
flushAndClear()
|
||||
|
||||
val records = repository.findChannelDonations(creator.id!!, viewer.id!!, now, limit = 8, lang = Lang.KO)
|
||||
|
||||
assertEquals(listOf(1, 3, 2, 5, 4, 7, 6, 9), records.map { it.can })
|
||||
assertEquals(listOf(true, true, false, true, false, true, false, true), records.map { it.isSecret })
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+59
-6
@@ -54,6 +54,8 @@ import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import java.time.LocalDateTime
|
||||
@@ -196,6 +198,49 @@ class CreatorChannelHomeQueryServiceTest {
|
||||
assertEquals("donor", home.channelDonations.first().nickname)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(
|
||||
"1, CREATOR, true",
|
||||
"20, USER, true",
|
||||
"10, USER, true",
|
||||
"30, CREATOR, true",
|
||||
"1, CREATOR, false",
|
||||
"20, USER, false",
|
||||
"10, USER, false",
|
||||
"30, CREATOR, false"
|
||||
)
|
||||
@DisplayName("홈 mapper는 비밀 여부와 조회자 ID로만 익명화하며 원본 이름과 프로필 기본값을 보존한다")
|
||||
fun shouldMaskSecretDonationOnlyForOtherViewers(viewerId: Long, role: MemberRole, isSecret: Boolean) {
|
||||
listOf(false, true).forEach { isLocalized ->
|
||||
listOf(null, "profile/fan.png").forEach { profilePath ->
|
||||
val port = FakeCreatorChannelHomeQueryPort(donorId = 20L).apply {
|
||||
channelDonationNickname = "deleted_donor"
|
||||
channelDonationIsSecret = isSecret
|
||||
channelDonationIsLocalizedName = isLocalized
|
||||
channelDonationProfilePath = profilePath
|
||||
}
|
||||
val service = createService(port)
|
||||
val home = service.getHome(
|
||||
1L,
|
||||
createMember(viewerId, role = role),
|
||||
LocalDateTime.of(2026, 6, 13, 10, 0),
|
||||
Lang.KO
|
||||
)
|
||||
val donation = home.channelDonations.single()
|
||||
val masked = isSecret && viewerId != 1L && viewerId != 20L
|
||||
val expectedNickname = if (isLocalized) "deleted_donor" else "donor"
|
||||
assertEquals(if (masked) "익명" else expectedNickname, donation.nickname)
|
||||
assertEquals(if (masked) "크리에이터에게만 보여요" else "thanks", donation.message)
|
||||
val originalProfile = profilePath ?: "profile/default-profile.png"
|
||||
val expectedProfile = if (masked) "profile/anonymous-profile.png" else originalProfile
|
||||
assertEquals("https://cdn.test/$expectedProfile", donation.profileImageUrl)
|
||||
assertEquals(isSecret, donation.isSecret)
|
||||
assertEquals(30, donation.can)
|
||||
assertEquals(LocalDateTime.of(2026, 6, 13, 8, 0), donation.createdAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("creatorId에 해당하는 회원이 없으면 user_not_found 예외를 던진다")
|
||||
fun shouldThrowUserNotFoundWhenCreatorMemberDoesNotExist() {
|
||||
@@ -350,7 +395,9 @@ class CreatorChannelHomeQueryServiceTest {
|
||||
assertEquals("thanks", json["channelDonations"][0]["message"].asText())
|
||||
assertFalse(json["channelDonations"][0].has("donation" + "Id"))
|
||||
assertFalse(json["channelDonations"][0].has("member" + "Id"))
|
||||
assertFalse(json["channelDonations"][0].has("is" + "Secret"))
|
||||
assertTrue(json["channelDonations"][0]["isSecret"].isBoolean)
|
||||
assertFalse(json["channelDonations"][0]["isSecret"].asBoolean())
|
||||
assertFalse(json["channelDonations"][0].has("secret"))
|
||||
}
|
||||
|
||||
private fun createHome(): CreatorChannelHome {
|
||||
@@ -413,7 +460,8 @@ class CreatorChannelHomeQueryServiceTest {
|
||||
profileImageUrl = "fan.png",
|
||||
can = 50,
|
||||
message = "thanks",
|
||||
createdAt = LocalDateTime.of(2026, 6, 12, 2, 0)
|
||||
createdAt = LocalDateTime.of(2026, 6, 12, 2, 0),
|
||||
isSecret = false
|
||||
)
|
||||
),
|
||||
notices = listOf(post),
|
||||
@@ -564,7 +612,7 @@ class CreatorChannelHomeQueryServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeCreatorChannelHomeQueryPort : CreatorChannelHomeQueryPort {
|
||||
private class FakeCreatorChannelHomeQueryPort(private val donorId: Long = 20L) : CreatorChannelHomeQueryPort {
|
||||
var creator: CreatorChannelCreatorRecord? = CreatorChannelCreatorRecord(
|
||||
creatorId = 1L,
|
||||
role = MemberRole.CREATOR,
|
||||
@@ -593,6 +641,9 @@ private class FakeCreatorChannelHomeQueryPort : CreatorChannelHomeQueryPort {
|
||||
var audioContentsLocale: String? = null
|
||||
var seriesLocale: String? = null
|
||||
var channelDonationNickname: String = "fan"
|
||||
var channelDonationIsSecret: Boolean = false
|
||||
var channelDonationIsLocalizedName: Boolean = false
|
||||
var channelDonationProfilePath: String? = "profile/fan.png"
|
||||
|
||||
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCreatorRecord? = creator
|
||||
|
||||
@@ -639,12 +690,14 @@ private class FakeCreatorChannelHomeQueryPort : CreatorChannelHomeQueryPort {
|
||||
lang: Lang
|
||||
): List<CreatorChannelDonationRecord> = listOf(
|
||||
CreatorChannelDonationRecord(
|
||||
memberId = 20L,
|
||||
memberId = donorId,
|
||||
nickname = channelDonationNickname,
|
||||
profileImagePath = "profile/fan.png",
|
||||
profileImagePath = channelDonationProfilePath,
|
||||
can = 30,
|
||||
message = "thanks",
|
||||
createdAt = LocalDateTime.of(2026, 6, 13, 8, 0)
|
||||
createdAt = LocalDateTime.of(2026, 6, 13, 8, 0),
|
||||
isSecret = channelDonationIsSecret,
|
||||
isLocalizedName = channelDonationIsLocalizedName
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user