feat(creatorname): 레거시 조회와 검색에 언어별 이름을 적용한다

This commit is contained in:
2026-10-16 14:04:17 +09:00
parent 995b41e89d
commit eef2e2bf55
66 changed files with 3022 additions and 373 deletions
@@ -0,0 +1,259 @@
package kr.co.vividnext.sodalive.explorer
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
import kr.co.vividnext.sodalive.common.CountryContext
import kr.co.vividnext.sodalive.content.AudioContent
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberAdapter
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.transaction.TestTransaction
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
import javax.persistence.EntityManager
@SpringBootTest(
properties = [
"cloud.aws.cloud-front.host=https://cdn.test",
"spring.cache.type=none",
"spring.datasource.url=jdbc:h2:mem:legacy-creator-name-e2e;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
]
)
@AutoConfigureMockMvc
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
@Transactional
class ExplorerLocalizedNameEndToEndTest @Autowired constructor(
private val mockMvc: MockMvc,
private val entityManager: EntityManager,
private val jdbcTemplate: JdbcTemplate
) {
@MockBean
private lateinit var countryContext: CountryContext
@BeforeEach
fun setUp() {
Mockito.doReturn("US").`when`(countryContext).countryCode
}
@Test
@DisplayName("레거시 프로필 이름은 현지화하되 팬톡 작성자·답글은 활성 JA·KO 별칭이 있어도 원본을 유지한다")
fun shouldLocalizeProfileButKeepOriginalFanTalkWriterAndReplyNamesDespiteActiveAliases() {
val viewer = saveMember("profile-viewer", MemberRole.USER)
val creator = saveMember("profile-original", MemberRole.CREATOR).apply { isVisibleDonationRank = false }
val fan = saveMember("fan-original", MemberRole.USER)
val peer = saveMember("deleted_peer-original", MemberRole.CREATOR)
val fanTalk = saveCheers(fan, creator, "fan talk")
saveCheers(peer, creator, "creator fan talk")
saveCheers(creator, creator, "creator reply", parent = fanTalk)
saveAlias(creator, "Japanese profile")
saveAlias(peer, "Japanese peer")
saveAlias(fan, "Must not replace fan")
saveAlias(creator, "Korean profile", lang = "KO")
saveAlias(peer, "Korean peer", lang = "KO")
entityManager.clear()
commitFixture()
mockMvc.perform(
get("/explorer/profile/${creator.id}")
.param("timezone", "Asia/Seoul")
.header("Accept-Language", "ja")
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.creator.creatorId").value(creator.id!!))
.andExpect(jsonPath("$.data.creator.nickname").value("Japanese profile"))
.andExpect(jsonPath("$.data.cheers.totalCount").value(2))
.andExpect(jsonPath("$.data.cheers.cheers[0].memberId").value(peer.id!!))
.andExpect(jsonPath("$.data.cheers.cheers[0].nickname").value("peer-original"))
.andExpect(jsonPath("$.data.cheers.cheers[1].nickname").value("fan-original"))
.andExpect(jsonPath("$.data.cheers.cheers[1].replyList[0].memberId").value(creator.id!!))
.andExpect(jsonPath("$.data.cheers.cheers[1].replyList[0].nickname").value("profile-original"))
listOf("ko", "en").forEach { locale ->
mockMvc.perform(
get("/explorer/profile/${creator.id}")
.param("timezone", "Asia/Seoul")
.header("Accept-Language", locale)
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(
jsonPath("$.data.creator.nickname").value(if (locale == "ko") "Korean profile" else "profile-original")
)
.andExpect(jsonPath("$.data.cheers.cheers[0].nickname").value("peer-original"))
.andExpect(jsonPath("$.data.cheers.cheers[1].nickname").value("fan-original"))
.andExpect(jsonPath("$.data.cheers.cheers[1].replyList[0].nickname").value("profile-original"))
}
assertEquals("profile-original", entityManager.find(Member::class.java, creator.id).nickname)
}
@Test
@DisplayName("JA 상세는 원본 변경 후에도 별칭을 유지하고 해제·미등록이면 현재 원본을 반환한다")
fun shouldKeepAliasAfterOriginalRenameAndFallbackAfterSoftClearOrMissingAlias() {
val viewer = saveMember("detail-viewer", MemberRole.USER)
val creator = saveMember("before-rename", MemberRole.CREATOR)
val unregistered = saveMember("unregistered-original", MemberRole.CREATOR)
saveAlias(creator, "Retained profile alias")
creator.nickname = "renamed-original"
entityManager.flush()
entityManager.clear()
commitFixture()
mockMvc.perform(
get("/explorer/profile/${creator.id}/detail")
.header("Accept-Language", "ja")
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.nickname").value("Retained profile alias"))
jdbcTemplate.update(
"update creator_localized_name set is_active = false where member_id = ? and lang = 'JA'",
creator.id!!
)
entityManager.clear()
mockMvc.perform(
get("/explorer/profile/${creator.id}/detail")
.header("Accept-Language", "ja")
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.nickname").value("renamed-original"))
mockMvc.perform(
get("/explorer/profile/${unregistered.id}/detail")
.header("Accept-Language", "ja")
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.nickname").value("unregistered-original"))
}
@Test
@DisplayName("통합 검색은 creator 제목·닉네임만 JA 이름으로 바꾸고 콘텐츠·시리즈 제목과 소유자 ID를 구분한다")
fun shouldLocalizeCreatorSearchTitleAndAllOwnerNamesWithoutReplacingContentOrSeriesTitles() {
val viewer = saveMember("unified-viewer", MemberRole.USER)
saveMember("unused-member", MemberRole.USER)
val creator = saveMember("unified-original", MemberRole.CREATOR)
val theme = AudioContentTheme(theme = "unified-theme", image = "theme.png")
entityManager.persist(theme)
val content = AudioContent(
title = "Unchanged content title",
detail = "description",
languageCode = "ko",
releaseDate = LocalDateTime.now().minusDays(1)
).apply {
member = creator
this.theme = theme
isActive = true
coverImage = "audio.png"
duration = "00:10:00"
entityManager.persist(this)
}
val genre = SeriesGenre(genre = "unified-genre", isAdult = false, isActive = true)
entityManager.persist(genre)
val series = Series(title = "Unchanged series title", introduction = "introduction").apply {
member = creator
this.genre = genre
coverImage = "series.png"
entityManager.persist(this)
}
entityManager.persist(
SeriesContent().apply {
this.series = series
this.content = content
}
)
saveAlias(creator, "Japanese unified creator")
entityManager.clear()
assertNotEquals(creator.id, content.id)
assertNotEquals(creator.id, series.id)
commitFixture()
mockMvc.perform(
get("/search")
.param("keyword", "unified-original")
.header("Accept-Language", "ja")
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.creatorList[0].id").value(creator.id!!))
.andExpect(jsonPath("$.data.creatorList[0].title").value("Japanese unified creator"))
.andExpect(jsonPath("$.data.creatorList[0].nickname").value("Japanese unified creator"))
.andExpect(jsonPath("$.data.contentList[0].id").value(content.id!!))
.andExpect(jsonPath("$.data.contentList[0].title").value("Unchanged content title"))
.andExpect(jsonPath("$.data.contentList[0].nickname").value("Japanese unified creator"))
.andExpect(jsonPath("$.data.seriesList[0].id").value(series.id!!))
.andExpect(jsonPath("$.data.seriesList[0].title").value("Unchanged series title"))
.andExpect(jsonPath("$.data.seriesList[0].nickname").value("Japanese unified creator"))
mockMvc.perform(
get("/search/creators")
.param("keyword", "Japanese unified")
.header("Accept-Language", "ko")
.with(user(MemberAdapter(viewer)))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.totalCount").value(1))
.andExpect(jsonPath("$.data.items[0].title").value("unified-original"))
.andExpect(jsonPath("$.data.items[0].nickname").value("unified-original"))
}
private fun commitFixture() {
TestTransaction.flagForCommit()
TestTransaction.end()
TestTransaction.start()
}
private fun saveMember(nickname: String, role: MemberRole): Member {
return Member(
email = "$nickname@test.com",
password = "password",
nickname = nickname,
profileImage = "$nickname.png",
role = role
).also { entityManager.persist(it) }
}
private fun saveCheers(writer: Member, creator: Member, text: String, parent: CreatorCheers? = null): CreatorCheers {
return CreatorCheers(cheers = text, languageCode = "ko").apply {
member = writer
this.creator = creator
this.parent = parent
entityManager.persist(this)
}
}
private fun saveAlias(member: Member, displayName: String, lang: String = "JA") {
entityManager.flush()
jdbcTemplate.update(
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
"values (?, ?, ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
member.id!!,
lang,
displayName
)
}
}
@@ -0,0 +1,90 @@
package kr.co.vividnext.sodalive.explorer
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import org.springframework.jdbc.core.JdbcTemplate
import javax.persistence.EntityManager
@DataJpaTest(properties = ["spring.cache.type=none"])
@Import(QueryDslConfig::class)
class ExplorerQueryRepositoryLocalizedNameTest @Autowired constructor(
private val entityManager: EntityManager,
private val jdbcTemplate: JdbcTemplate,
queryFactory: JPAQueryFactory
) {
private val langContext = LangContext().apply { setLang(Lang.KO) }
private val repository = ExplorerQueryRepository(queryFactory, SodaMessageSource(), langContext, "https://cdn.test")
@Test
@DisplayName("KO 채널 검색도 원본 또는 활성 JA 이름을 찾고 self·비활성·USER를 제외한다")
fun shouldSearchOriginalOrActiveJaAliasInKoWithoutBypassingChannelFilters() {
val viewer = saveMember("Comet-viewer")
val original = saveMember("Comet-original")
val aliasOnly = saveMember("another-original")
val inactive = saveMember("Comet-inactive").apply { isActive = false }
val user = saveMember("Comet-user", MemberRole.USER)
listOf(viewer, original, aliasOnly, inactive, user).forEach { saveAlias(it, "Comet alias") }
entityManager.clear()
assertAll(
{
assertEquals(
setOf(original.id, aliasOnly.id),
repository.getSearchChannel("cOmEt", viewer.id!!).map { it.id }.toSet()
)
},
{ assertEquals(2, repository.getSearchChannel("cOmEt", viewer.id!!).size) },
{ assertEquals(listOf(original.id), repository.getSearchChannel("Comet-original", viewer.id!!).map { it.id }) }
)
}
@Test
@DisplayName("채널 검색은 해제한 JA와 EN 이름을 제외하고 동일 행 재활성화 후 별칭을 다시 찾는다")
fun shouldIgnoreSoftClearedAndNonJaChannelAliasesAndSearchReactivatedAlias() {
val viewer = saveMember("channel-viewer", MemberRole.USER)
val creator = saveMember("channel-original")
saveAlias(creator, "ChannelAlias", isActive = false)
saveAlias(creator, "EnglishChannel", lang = "EN")
entityManager.clear()
assertEquals(emptyList<Long>(), repository.getSearchChannel("ChannelAlias", viewer.id!!).map { it.id })
assertEquals(emptyList<Long>(), repository.getSearchChannel("EnglishChannel", viewer.id!!).map { it.id })
assertEquals(listOf(creator.id), repository.getSearchChannel("channel-original", viewer.id!!).map { it.id })
jdbcTemplate.update(
"update creator_localized_name set is_active = true where member_id = ? and lang = 'JA'",
creator.id!!
)
assertEquals(listOf(creator.id), repository.getSearchChannel("ChannelAlias", viewer.id!!).map { it.id })
}
private fun saveMember(nickname: String, role: MemberRole = MemberRole.CREATOR): Member {
return Member(email = "$nickname@test.com", password = "password", nickname = nickname, role = role).also {
entityManager.persist(it)
}
}
private fun saveAlias(member: Member, displayName: String, isActive: Boolean = true, lang: String = "JA") {
entityManager.flush()
jdbcTemplate.update(
"insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
"values (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
member.id!!,
lang,
displayName,
isActive
)
}
}
@@ -11,12 +11,15 @@ import kr.co.vividnext.sodalive.explorer.profile.CreatorDonationRankingService
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.ChannelDonationService
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunityService
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.MemberService
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
@@ -42,7 +45,28 @@ class ExplorerServiceTest {
}
}
private fun createService(queryRepository: ExplorerQueryRepository): ExplorerService {
@Test
@DisplayName("팬톡 수정 응답은 별칭 대신 작성자 원본 이름을 반환하고 deleted_ 접두사만 제거한다")
fun shouldKeepOriginalWriterNameAndRemoveDeletedPrefixWhenModifyingCheers() {
val queryRepository = Mockito.mock(ExplorerQueryRepository::class.java)
val localizedNameService = Mockito.mock(CreatorLocalizedNameService::class.java)
val service = createService(queryRepository, localizedNameService)
val writer = createMember(id = 10L, nickname = "deleted_writer", role = MemberRole.CREATOR)
val cheers = createCheers(writer, createMember(id = 20L, nickname = "creator", role = MemberRole.CREATOR))
Mockito.doReturn(cheers).`when`(queryRepository).getCheers(101L)
Mockito.`when`(localizedNameService.findLocalizedNames(listOf(10L), Lang.JA)).thenReturn(mapOf(10L to "alias"))
val response = service.modifyCheers(PutWriteCheersRequest(cheersId = 101L, content = "modified"), writer)
assertEquals("writer", response.writerNickname)
assertEquals("modified", response.content)
Mockito.verifyNoInteractions(localizedNameService)
}
private fun createService(
queryRepository: ExplorerQueryRepository,
localizedNameService: CreatorLocalizedNameService = Mockito.mock(CreatorLocalizedNameService::class.java)
): ExplorerService {
return ExplorerService(
memberService = Mockito.mock(MemberService::class.java),
memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
@@ -57,7 +81,8 @@ class ExplorerServiceTest {
applicationEventPublisher = Mockito.mock(ApplicationEventPublisher::class.java),
contentTranslationRepository = Mockito.mock(ContentTranslationRepository::class.java),
messageSource = Mockito.mock(SodaMessageSource::class.java),
langContext = Mockito.mock(LangContext::class.java),
langContext = LangContext().apply { setLang(Lang.JA) },
localizedNameService = localizedNameService,
cloudFrontHost = "https://cdn.test"
)
}
@@ -72,9 +72,9 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
// then: 비공개 타인 메시지는 제외되고, createdAt desc/id desc 정렬이 적용돼야 한다.
assertEquals(3, list.size)
assertEquals(secretMine.id, list[0].id)
assertEquals(publicTieSecond.id, list[1].id)
assertEquals(publicTieFirst.id, list[2].id)
assertEquals(secretMine.id, list[0].first.id)
assertEquals(publicTieSecond.id, list[1].first.id)
assertEquals(publicTieFirst.id, list[2].first.id)
assertEquals(3, totalCount)
}
@@ -127,9 +127,9 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
// then: 본인 조회는 비공개 메시지를 포함하고 최신순으로 반환해야 한다.
assertEquals(3, list.size)
assertEquals(recentSecretOther.id, list[0].id)
assertEquals(recentSecretMine.id, list[1].id)
assertEquals(recentPublic.id, list[2].id)
assertEquals(recentSecretOther.id, list[0].first.id)
assertEquals(recentSecretMine.id, list[1].first.id)
assertEquals(recentPublic.id, list[2].first.id)
assertEquals(3, totalCount)
}
@@ -138,7 +138,7 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
fun shouldFetchMemberWithDonationMessages() {
// given: 크리에이터/후원자와 조회 대상 메시지를 준비한다.
val creator = saveMember(nickname = "creator3", role = MemberRole.CREATOR)
val viewer = saveMember(nickname = "viewer3", role = MemberRole.USER)
val viewer = saveMember(nickname = "deleted_viewer3", role = MemberRole.USER)
val message = saveMessage(member = viewer, creator = creator, can = 1, isSecret = false)
val monthStart = LocalDateTime.now().withDayOfMonth(1).toLocalDate().atStartOfDay()
val nextMonthStart = monthStart.plusMonths(1)
@@ -160,8 +160,9 @@ class ChannelDonationMessageRepositoryTest @Autowired constructor(
// then: 조회 결과의 후원자 정보는 세션 없이도 접근 가능해야 한다.
assertEquals(1, list.size)
assertEquals(viewer.id, list[0].member!!.id)
assertEquals("viewer3", list[0].member!!.nickname)
assertEquals(viewer.id, list[0].first.member!!.id)
assertEquals("deleted_viewer3", list[0].first.member!!.nickname)
assertEquals("viewer3", list[0].second)
}
private fun saveMember(nickname: String, role: MemberRole): Member {
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.explorer.profile.channelDonation
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
@@ -97,9 +98,10 @@ class ChannelDonationServiceTest {
Mockito.eq(0L),
Mockito.eq(5L),
anyLocalDateTime(),
anyLocalDateTime()
anyLocalDateTime(),
Mockito.eq(Lang.KO) ?: Lang.KO
)
).thenReturn(listOf(message))
).thenReturn(listOf(message to viewer.nickname))
// when: 채널 후원 목록 조회를 실행한다.
val result = service.getChannelDonationList(
@@ -129,7 +131,8 @@ class ChannelDonationServiceTest {
Mockito.eq(0L),
Mockito.eq(5L),
anyLocalDateTime(),
anyLocalDateTime()
anyLocalDateTime(),
Mockito.eq(Lang.KO) ?: Lang.KO
)
// then: 월 시작/다음 달 시작 범위를 사용해야 한다.
@@ -181,9 +184,10 @@ class ChannelDonationServiceTest {
Mockito.eq(0L),
Mockito.eq(5L),
anyLocalDateTime(),
anyLocalDateTime()
anyLocalDateTime(),
Mockito.eq(Lang.KO) ?: Lang.KO
)
).thenReturn(listOf(message))
).thenReturn(listOf(message to "withdrawn"))
// when: 채널 후원 목록 조회를 실행한다.
val result = service.getChannelDonationList(
@@ -228,9 +232,10 @@ class ChannelDonationServiceTest {
Mockito.eq(0L),
Mockito.eq(5L),
anyLocalDateTime(),
anyLocalDateTime()
anyLocalDateTime(),
Mockito.eq(Lang.KO) ?: Lang.KO
)
).thenReturn(listOf(message))
).thenReturn(listOf(message to viewer.nickname))
// when: 목록 조회를 실행한다.
val result = service.getChannelDonationList(
@@ -275,7 +280,8 @@ class ChannelDonationServiceTest {
Mockito.eq(0L),
Mockito.eq(5L),
anyLocalDateTime(),
anyLocalDateTime()
anyLocalDateTime(),
Mockito.eq(Lang.KO) ?: Lang.KO
)
).thenReturn(emptyList())
@@ -302,7 +308,8 @@ class ChannelDonationServiceTest {
Mockito.eq(0L),
Mockito.eq(5L),
anyLocalDateTime(),
anyLocalDateTime()
anyLocalDateTime(),
Mockito.eq(Lang.KO) ?: Lang.KO
)
// then: 월 시작/다음 달 시작 범위를 사용해야 한다.
@@ -150,6 +150,7 @@ class CreatorCommunityServiceTest {
assertEquals(FcmEventType.INDIVIDUAL, event.type)
assertEquals(PushNotificationCategory.COMMUNITY, event.category)
assertEquals("creator.community.fcm.new_comment", event.messageKey)
assertEquals(commenter.id, event.titleMemberId)
assertEquals(commenter.id, event.senderMemberId)
assertEquals(listOf(creator.id!!), event.recipients)
assertEquals(FcmDeepLinkValue.COMMUNITY, event.deepLinkValue)