feat(fcm): 수신 언어별 크리에이터 이름을 알림에 반영한다
This commit is contained in:
@@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.i18n.Lang
|
|||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.scheduling.annotation.Async
|
import org.springframework.scheduling.annotation.Async
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.transaction.annotation.Propagation
|
import org.springframework.transaction.annotation.Propagation
|
||||||
@@ -56,7 +57,9 @@ class FcmEvent(
|
|||||||
val commentParentId: Long? = null,
|
val commentParentId: Long? = null,
|
||||||
val myMemberId: Long? = null,
|
val myMemberId: Long? = null,
|
||||||
val isAvailableJoinCreator: Boolean? = null,
|
val isAvailableJoinCreator: Boolean? = null,
|
||||||
val genderRestriction: GenderRestriction? = null
|
val genderRestriction: GenderRestriction? = null,
|
||||||
|
val titleMemberId: Long? = null,
|
||||||
|
val nameArgumentMemberIds: Map<Int, Long> = emptyMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -65,7 +68,8 @@ class FcmSendListener(
|
|||||||
private val memberRepository: MemberRepository,
|
private val memberRepository: MemberRepository,
|
||||||
private val contentCommentRepository: AudioContentCommentRepository,
|
private val contentCommentRepository: AudioContentCommentRepository,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val pushNotificationService: PushNotificationService
|
private val pushNotificationService: PushNotificationService,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService
|
||||||
) {
|
) {
|
||||||
@Async
|
@Async
|
||||||
@TransactionalEventListener
|
@TransactionalEventListener
|
||||||
@@ -169,18 +173,26 @@ class FcmSendListener(
|
|||||||
creatorId: Long? = null,
|
creatorId: Long? = null,
|
||||||
auditionId: Long? = null
|
auditionId: Long? = null
|
||||||
) {
|
) {
|
||||||
val tokensByLang = pushTokens.groupBy { it.languageCode }
|
val tokensByLang = pushTokens.groupBy { Lang.fromAcceptLanguage(it.languageCode) }
|
||||||
|
val nameMemberIds = listOfNotNull(fcmEvent.senderMemberId, fcmEvent.titleMemberId)
|
||||||
|
.plus(fcmEvent.nameArgumentMemberIds.values)
|
||||||
|
.distinct()
|
||||||
|
|
||||||
for ((langCode, tokens) in tokensByLang) {
|
for ((lang, tokens) in tokensByLang) {
|
||||||
val lang = Lang.fromAcceptLanguage(langCode)
|
val names = localizedNameService.findLocalizedNames(nameMemberIds, lang)
|
||||||
val title = translate(fcmEvent.titleKey, fcmEvent.title, lang, fcmEvent.args)
|
val args = fcmEvent.args.mapIndexed { index, argument ->
|
||||||
val message = translate(fcmEvent.messageKey, fcmEvent.message, lang, fcmEvent.args)
|
fcmEvent.nameArgumentMemberIds[index]?.let { names[it] } ?: argument
|
||||||
|
}
|
||||||
|
val defaultTitle = fcmEvent.titleMemberId?.let { names[it] } ?: fcmEvent.title
|
||||||
|
val title = translate(fcmEvent.titleKey, defaultTitle, lang, args)
|
||||||
|
val message = translate(fcmEvent.messageKey, fcmEvent.message, lang, args)
|
||||||
|
|
||||||
pushNotificationService.saveNotification(
|
pushNotificationService.saveNotification(
|
||||||
fcmEvent = fcmEvent,
|
fcmEvent = fcmEvent,
|
||||||
languageCode = lang.code,
|
languageCode = lang.code,
|
||||||
translatedMessage = message,
|
translatedMessage = message,
|
||||||
recipientPushTokens = tokens
|
recipientPushTokens = tokens,
|
||||||
|
localizedSenderNickname = fcmEvent.senderMemberId?.let { names[it] }
|
||||||
)
|
)
|
||||||
|
|
||||||
val tokensByOS = tokens.groupBy { it.deviceType }
|
val tokensByOS = tokens.groupBy { it.deviceType }
|
||||||
|
|||||||
+3
-2
@@ -36,7 +36,8 @@ class PushNotificationService(
|
|||||||
fcmEvent: FcmEvent,
|
fcmEvent: FcmEvent,
|
||||||
languageCode: String,
|
languageCode: String,
|
||||||
translatedMessage: String,
|
translatedMessage: String,
|
||||||
recipientPushTokens: List<PushTokenInfo>
|
recipientPushTokens: List<PushTokenInfo>,
|
||||||
|
localizedSenderNickname: String?
|
||||||
) {
|
) {
|
||||||
if (recipientPushTokens.isEmpty()) return
|
if (recipientPushTokens.isEmpty()) return
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ class PushNotificationService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val notification = PushNotificationList(
|
val notification = PushNotificationList(
|
||||||
senderNicknameSnapshot = senderSnapshot.nickname,
|
senderNicknameSnapshot = localizedSenderNickname ?: senderSnapshot.nickname,
|
||||||
senderProfileImageSnapshot = senderSnapshot.profileImage,
|
senderProfileImageSnapshot = senderSnapshot.profileImage,
|
||||||
message = translatedMessage,
|
message = translatedMessage,
|
||||||
category = category,
|
category = category,
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
package kr.co.vividnext.sodalive.fcm
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.comment.AudioContentComment
|
||||||
|
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentService
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
||||||
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationList
|
||||||
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationListRepository
|
||||||
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationRecipientChunk
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservation
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.room.LiveRoomService
|
||||||
|
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import kr.co.vividnext.sodalive.v2.gift.application.GiftPushService
|
||||||
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.service.UserCreatorChatService
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.EnumSource
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import org.springframework.test.context.event.ApplicationEvents
|
||||||
|
import org.springframework.test.context.event.RecordApplicationEvents
|
||||||
|
import org.springframework.test.util.AopTestUtils
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
properties = [
|
||||||
|
"spring.cache.type=none",
|
||||||
|
"jwt.secret=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZg=="
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||||
|
@RecordApplicationEvents
|
||||||
|
@Transactional
|
||||||
|
class FcmSendListenerLocalizedNameTest @Autowired constructor(
|
||||||
|
private val listener: FcmSendListener,
|
||||||
|
private val chatService: UserCreatorChatService,
|
||||||
|
private val liveRoomService: LiveRoomService,
|
||||||
|
private val commentService: AudioContentCommentService,
|
||||||
|
private val giftPushService: GiftPushService,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val pushTokenRepository: PushTokenRepository,
|
||||||
|
private val notificationRepository: PushNotificationListRepository,
|
||||||
|
private val roomRepository: LiveRoomRepository,
|
||||||
|
private val reservationRepository: LiveReservationRepository,
|
||||||
|
private val jdbcTemplate: JdbcTemplate,
|
||||||
|
private val langContext: LangContext,
|
||||||
|
private val entityManager: EntityManager
|
||||||
|
) {
|
||||||
|
@Autowired
|
||||||
|
private lateinit var applicationEvents: ApplicationEvents
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var fcmService: FcmService
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private lateinit var s3Uploader: S3Uploader
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpRequest() {
|
||||||
|
RequestContextHolder.setRequestAttributes(ServletRequestAttributes(MockHttpServletRequest()))
|
||||||
|
langContext.setLang(Lang.EN)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun resetRequest() {
|
||||||
|
RequestContextHolder.resetRequestAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("실제 DM 이벤트의 이름 인자와 255자 발신자 스냅샷을 토큰 언어별로 독립 렌더링한다")
|
||||||
|
fun shouldRenderDmNameArgumentAndSenderSnapshotForEachTokenLanguage() {
|
||||||
|
val creator = savedMember("dm-creator", MemberRole.CREATOR)
|
||||||
|
val recipient = savedMember("dm-recipient")
|
||||||
|
val japaneseName = "名".repeat(255)
|
||||||
|
saveAlias(creator, japaneseName)
|
||||||
|
saveTokens(recipient)
|
||||||
|
val roomId = chatService.createOrGetRoom(creator, recipient.id!!).roomId
|
||||||
|
|
||||||
|
chatService.sendTextMessageByWebSocket(creator.id!!, roomId, "dm-creator 본문은 그대로", Lang.EN)
|
||||||
|
val event = publishedEvent()
|
||||||
|
AopTestUtils.getUltimateTargetObject<FcmSendListener>(listener).send(event)
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val pushes = outgoingPushes()
|
||||||
|
val snapshots = notificationRepository.findAll().associateBy { it.languageCode }
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals(2, pushes.size) },
|
||||||
|
{ assertEquals("DM", pushes.getValue("token-ja").first) },
|
||||||
|
{ assertEquals("${japaneseName}からDMが届きました。", pushes.getValue("token-ja").second) },
|
||||||
|
{ assertEquals("DM", pushes.getValue("token-ko").first) },
|
||||||
|
{ assertEquals("dm-creator님으로 부터 DM이 도착했습니다.", pushes.getValue("token-ko").second) },
|
||||||
|
{ assertEquals(japaneseName, snapshots.getValue("ja").senderNicknameSnapshot) },
|
||||||
|
{ assertEquals("dm-creator", snapshots.getValue("ko").senderNicknameSnapshot) },
|
||||||
|
{ assertEquals(pushes.getValue("token-ja").second, snapshots.getValue("ja").message) },
|
||||||
|
{ assertEquals(pushes.getValue("token-ko").second, snapshots.getValue("ko").message) },
|
||||||
|
{ assertEquals(listOf("dm-creator"), event.args) },
|
||||||
|
{ assertEquals("dm-creator", memberRepository.findById(creator.id!!).orElseThrow().nickname) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("실제 라이브 취소 이벤트의 제목만 현지화하고 같은 문자열인 방 제목 인자는 보존한다")
|
||||||
|
fun shouldLocalizeLiveSenderTitleWithoutReplacingRoomTitleArgument() {
|
||||||
|
val creator = savedMember("creator.*$", MemberRole.CREATOR)
|
||||||
|
val recipient = savedMember("live-recipient")
|
||||||
|
saveAlias(creator, "日本の配信者")
|
||||||
|
saveTokens(recipient)
|
||||||
|
val room = roomRepository.saveAndFlush(
|
||||||
|
LiveRoom(
|
||||||
|
title = "creator.*$",
|
||||||
|
notice = "notice",
|
||||||
|
beginDateTime = LocalDateTime.now().plusDays(1),
|
||||||
|
numberOfPeople = 10,
|
||||||
|
isAdult = false
|
||||||
|
).apply { member = creator }
|
||||||
|
)
|
||||||
|
reservationRepository.saveAndFlush(
|
||||||
|
LiveReservation().apply {
|
||||||
|
member = recipient
|
||||||
|
this.room = room
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
liveRoomService.cancelLive(CancelLiveRequest(room.id!!, "취소 사유"), creator)
|
||||||
|
val event = publishedEvent()
|
||||||
|
AopTestUtils.getUltimateTargetObject<FcmSendListener>(listener).send(event)
|
||||||
|
|
||||||
|
val pushes = outgoingPushes()
|
||||||
|
val snapshots = notificationRepository.findAll().associateBy { it.languageCode }
|
||||||
|
assertAll(
|
||||||
|
{ assertEquals("日本の配信者", pushes.getValue("token-ja").first) },
|
||||||
|
{ assertEquals("ライブ取消: creator.*$", pushes.getValue("token-ja").second) },
|
||||||
|
{ assertEquals("creator.*$", pushes.getValue("token-ko").first) },
|
||||||
|
{ assertEquals("라이브 취소 : creator.*$", pushes.getValue("token-ko").second) },
|
||||||
|
{ assertEquals("日本の配信者", snapshots.getValue("ja").senderNicknameSnapshot) },
|
||||||
|
{ assertEquals("creator.*$", snapshots.getValue("ko").senderNicknameSnapshot) },
|
||||||
|
{ assertEquals("creator.*$", event.title) },
|
||||||
|
{ assertEquals(listOf("creator.*$"), event.args) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("발신자 ID가 없는 과거 알림은 현재 이름 변경 뒤 신규 알림을 발송해도 이름과 본문이 불변이다")
|
||||||
|
fun shouldPreserveHistoricalNotificationWithoutSenderId() {
|
||||||
|
val creator = savedMember("historical-creator", MemberRole.CREATOR)
|
||||||
|
val recipient = savedMember("historical-recipient")
|
||||||
|
saveTokens(recipient)
|
||||||
|
val historical = notificationRepository.saveAndFlush(
|
||||||
|
PushNotificationList(
|
||||||
|
senderNicknameSnapshot = "過去の名前",
|
||||||
|
message = "過去の配信通知",
|
||||||
|
category = PushNotificationCategory.LIVE,
|
||||||
|
languageCode = "ja"
|
||||||
|
).apply { addRecipientChunk(PushNotificationRecipientChunk(listOf(recipient.id!!))) }
|
||||||
|
)
|
||||||
|
saveAlias(creator, "現在の名前")
|
||||||
|
creator.nickname = "changed-original"
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
|
||||||
|
val roomId = chatService.createOrGetRoom(creator, recipient.id!!).roomId
|
||||||
|
chatService.sendTextMessageByWebSocket(creator.id!!, roomId, "새 알림 본문", Lang.EN)
|
||||||
|
AopTestUtils.getUltimateTargetObject<FcmSendListener>(listener).send(publishedEvent())
|
||||||
|
entityManager.flush()
|
||||||
|
entityManager.clear()
|
||||||
|
val stored = notificationRepository.findById(historical.id!!).orElseThrow()
|
||||||
|
|
||||||
|
assertEquals(3L, notificationRepository.count())
|
||||||
|
assertEquals("過去の名前", stored.senderNicknameSnapshot)
|
||||||
|
assertEquals("過去の配信通知", stored.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(value = MemberRole::class, names = ["CREATOR", "USER"])
|
||||||
|
@DisplayName("실제 오디오 답글은 부모 이름 제목과 행위자 snapshot을 분리하고 콘텐츠 제목 인자를 보존한다")
|
||||||
|
fun shouldKeepReplyTitleOwnerSeparateFromActualSender(senderRole: MemberRole) {
|
||||||
|
val parentAuthor = savedMember("parent-author", MemberRole.CREATOR)
|
||||||
|
val sender = savedMember("reply-sender", senderRole)
|
||||||
|
saveAlias(parentAuthor, "親コメントの作者")
|
||||||
|
saveAlias(sender, "返信した作者")
|
||||||
|
saveTokens(parentAuthor)
|
||||||
|
val theme = AudioContentTheme("theme", "theme.png")
|
||||||
|
entityManager.persist(theme)
|
||||||
|
val content = AudioContent(title = "parent-author", detail = "detail", languageCode = "en").apply {
|
||||||
|
member = parentAuthor
|
||||||
|
this.theme = theme
|
||||||
|
}
|
||||||
|
entityManager.persist(content)
|
||||||
|
val parent = AudioContentComment("parent", "en").apply {
|
||||||
|
member = parentAuthor
|
||||||
|
audioContent = content
|
||||||
|
}
|
||||||
|
entityManager.persist(parent)
|
||||||
|
entityManager.flush()
|
||||||
|
|
||||||
|
commentService.registerComment(sender, "reply", content.id!!, parent.id, languageCode = "en")
|
||||||
|
val event = publishedEvent()
|
||||||
|
AopTestUtils.getUltimateTargetObject<FcmSendListener>(listener).send(event)
|
||||||
|
val pushes = outgoingPushes()
|
||||||
|
val snapshots = notificationRepository.findAll().associateBy { it.languageCode }
|
||||||
|
|
||||||
|
assertEquals(parentAuthor.id, event.titleMemberId)
|
||||||
|
assertEquals(sender.id, event.senderMemberId)
|
||||||
|
assertEquals("親コメントの作者", pushes.getValue("token-ja").first)
|
||||||
|
assertEquals("コメントに返信しました: parent-author", pushes.getValue("token-ja").second)
|
||||||
|
assertEquals("parent-author", pushes.getValue("token-ko").first)
|
||||||
|
assertEquals("reply-sender", snapshots.getValue("ko").senderNicknameSnapshot)
|
||||||
|
assertEquals(
|
||||||
|
if (senderRole == MemberRole.CREATOR) "返信した作者" else "reply-sender",
|
||||||
|
snapshots.getValue("ja").senderNicknameSnapshot
|
||||||
|
)
|
||||||
|
assertEquals(listOf("parent-author"), event.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("실제 기프트 알림의 자유 제목과 본문은 수신자 별칭이나 토큰 언어로 이름 치환하지 않는다")
|
||||||
|
fun shouldPreserveGiftTextAndDefaultSenderSnapshotWithoutNameMetadata() {
|
||||||
|
val recipient = savedMember("운송장 번호를 입력해 주세요", MemberRole.CREATOR)
|
||||||
|
saveAlias(recipient, "名前ではない通知")
|
||||||
|
saveTokens(recipient)
|
||||||
|
|
||||||
|
giftPushService.sendTrackingDeadlineReminder("gift-001", recipient.id!!)
|
||||||
|
val event = publishedEvent()
|
||||||
|
AopTestUtils.getUltimateTargetObject<FcmSendListener>(listener).send(event)
|
||||||
|
val pushes = outgoingPushes()
|
||||||
|
|
||||||
|
assertEquals(null, event.senderMemberId)
|
||||||
|
assertEquals(null, event.titleMemberId)
|
||||||
|
assertEquals(emptyMap<Int, Long>(), event.nameArgumentMemberIds)
|
||||||
|
assertEquals("운송장 번호를 입력해 주세요", pushes.getValue("token-ja").first)
|
||||||
|
assertEquals(pushes.getValue("token-ko"), pushes.getValue("token-ja"))
|
||||||
|
notificationRepository.findAll().forEach { assertEquals(event.title, it.senderNicknameSnapshot) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publishedEvent(): FcmEvent = applicationEvents.stream(FcmEvent::class.java).toList().single()
|
||||||
|
|
||||||
|
private fun savedMember(nickname: String, role: MemberRole = MemberRole.USER): Member = memberRepository.saveAndFlush(
|
||||||
|
Member(email = "$nickname@localized-fcm.test", password = "pw", nickname = nickname, role = role)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun saveAlias(member: Member, displayName: String) {
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at) " +
|
||||||
|
"VALUES (?, 'JA', ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
|
||||||
|
member.id!!,
|
||||||
|
displayName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveTokens(member: Member) {
|
||||||
|
pushTokenRepository.saveAndFlush(PushToken("token-ja", "aos", "ja-JP").apply { this.member = member })
|
||||||
|
pushTokenRepository.saveAndFlush(PushToken("token-ko", "ios", "ko-KR").apply { this.member = member })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun outgoingPushes(): Map<String, Pair<String, String>> = Mockito.mockingDetails(fcmService).invocations
|
||||||
|
.filter { it.method.name == "send" }
|
||||||
|
.associate { invocation ->
|
||||||
|
val tokens = invocation.getArgument<List<String>>(0)
|
||||||
|
tokens.single() to Pair(invocation.getArgument<String>(1), invocation.getArgument<String>(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-5
@@ -71,7 +71,8 @@ class PushNotificationServiceTest {
|
|||||||
fcmEvent = event,
|
fcmEvent = event,
|
||||||
languageCode = "ko",
|
languageCode = "ko",
|
||||||
translatedMessage = "테스트 메시지",
|
translatedMessage = "테스트 메시지",
|
||||||
recipientPushTokens = pushTokens
|
recipientPushTokens = pushTokens,
|
||||||
|
localizedSenderNickname = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// then: 수신자 없음 규칙에 따라 저장이 발생하지 않아야 한다.
|
// then: 수신자 없음 규칙에 따라 저장이 발생하지 않아야 한다.
|
||||||
@@ -95,7 +96,8 @@ class PushNotificationServiceTest {
|
|||||||
fcmEvent = event,
|
fcmEvent = event,
|
||||||
languageCode = "ko",
|
languageCode = "ko",
|
||||||
translatedMessage = "시스템 알림",
|
translatedMessage = "시스템 알림",
|
||||||
recipientPushTokens = pushTokens
|
recipientPushTokens = pushTokens,
|
||||||
|
localizedSenderNickname = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// then: SYSTEM 카테고리 보정 결과에 따라 저장이 발생하지 않아야 한다.
|
// then: SYSTEM 카테고리 보정 결과에 따라 저장이 발생하지 않아야 한다.
|
||||||
@@ -124,7 +126,8 @@ class PushNotificationServiceTest {
|
|||||||
fcmEvent = event,
|
fcmEvent = event,
|
||||||
languageCode = "ko",
|
languageCode = "ko",
|
||||||
translatedMessage = "라이브가 시작되었습니다.",
|
translatedMessage = "라이브가 시작되었습니다.",
|
||||||
recipientPushTokens = pushTokens
|
recipientPushTokens = pushTokens,
|
||||||
|
localizedSenderNickname = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// then: 보정된 LIVE 카테고리로 저장되어야 한다.
|
// then: 보정된 LIVE 카테고리로 저장되어야 한다.
|
||||||
@@ -163,7 +166,8 @@ class PushNotificationServiceTest {
|
|||||||
fcmEvent = event,
|
fcmEvent = event,
|
||||||
languageCode = "ko",
|
languageCode = "ko",
|
||||||
translatedMessage = "라이브가 시작되었습니다.",
|
translatedMessage = "라이브가 시작되었습니다.",
|
||||||
recipientPushTokens = pushTokens
|
recipientPushTokens = pushTokens,
|
||||||
|
localizedSenderNickname = null
|
||||||
)
|
)
|
||||||
|
|
||||||
// then: 발송자 스냅샷/딥링크/카테고리/언어와 수신자 청크가 정확히 저장되어야 한다.
|
// then: 발송자 스냅샷/딥링크/카테고리/언어와 수신자 청크가 정확히 저장되어야 한다.
|
||||||
@@ -206,7 +210,8 @@ class PushNotificationServiceTest {
|
|||||||
fcmEvent = event,
|
fcmEvent = event,
|
||||||
languageCode = "ko",
|
languageCode = "ko",
|
||||||
translatedMessage = "새 댓글이 등록되었습니다.",
|
translatedMessage = "새 댓글이 등록되었습니다.",
|
||||||
recipientPushTokens = pushTokens
|
recipientPushTokens = pushTokens,
|
||||||
|
localizedSenderNickname = null
|
||||||
)
|
)
|
||||||
|
|
||||||
val captor = ArgumentCaptor.forClass(PushNotificationList::class.java)
|
val captor = ArgumentCaptor.forClass(PushNotificationList::class.java)
|
||||||
|
|||||||
Reference in New Issue
Block a user