test #400
15
docs/20260313_푸시시스템카테고리저장정책보완.md
Normal file
15
docs/20260313_푸시시스템카테고리저장정책보완.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
- [x] 리뷰 결과 요약 및 수정 범위 확정
|
||||||
|
- [x] FcmEvent 저장 조건 제거 및 서비스 계층으로 정책 이동
|
||||||
|
- [x] PushNotificationService에서 SYSTEM 저장 제외 보장
|
||||||
|
- [x] category null 회귀 방지 테스트 추가
|
||||||
|
- [x] 검증 실행 (LSP, 테스트, 빌드)
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
|
||||||
|
### 1차 구현
|
||||||
|
- 무엇을: `SYSTEM` 카테고리 저장 제외 정책을 Listener에서 Service로 이동하고, `category = null` 회귀를 막는 테스트를 추가했다.
|
||||||
|
- 왜: 현재 Listener 조건은 `category != null`을 요구해 타입 기반 카테고리 보정(`resolveCategory`)을 우회할 수 있어, 비SYSTEM 이벤트의 저장 누락 위험이 있었다.
|
||||||
|
- 어떻게:
|
||||||
|
- `lsp_diagnostics` 실행: Kotlin LSP 미설정으로 불가(환경상 `.kt` 진단 서버 없음).
|
||||||
|
- `./gradlew test --tests kr.co.vividnext.sodalive.fcm.notification.PushNotificationServiceTest` 실행: 성공.
|
||||||
|
- `./gradlew build` 실행: 성공.
|
||||||
@@ -79,7 +79,7 @@ class ChargeEventService(
|
|||||||
applicationEventPublisher.publishEvent(
|
applicationEventPublisher.publishEvent(
|
||||||
FcmEvent(
|
FcmEvent(
|
||||||
type = FcmEventType.INDIVIDUAL,
|
type = FcmEventType.INDIVIDUAL,
|
||||||
category = PushNotificationCategory.MESSAGE,
|
category = PushNotificationCategory.SYSTEM,
|
||||||
title = chargeEvent.title,
|
title = chargeEvent.title,
|
||||||
messageKey = "can.charge.event.additional_can_paid",
|
messageKey = "can.charge.event.additional_can_paid",
|
||||||
args = listOf(additionalCan),
|
args = listOf(additionalCan),
|
||||||
@@ -103,7 +103,7 @@ class ChargeEventService(
|
|||||||
applicationEventPublisher.publishEvent(
|
applicationEventPublisher.publishEvent(
|
||||||
FcmEvent(
|
FcmEvent(
|
||||||
type = FcmEventType.INDIVIDUAL,
|
type = FcmEventType.INDIVIDUAL,
|
||||||
category = PushNotificationCategory.MESSAGE,
|
category = PushNotificationCategory.SYSTEM,
|
||||||
titleKey = "can.charge.event.first_title",
|
titleKey = "can.charge.event.first_title",
|
||||||
messageKey = "can.charge.event.additional_can_paid",
|
messageKey = "can.charge.event.additional_can_paid",
|
||||||
args = listOf(additionalCan),
|
args = listOf(additionalCan),
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class PushNotificationService(
|
|||||||
if (recipientMemberIds.isEmpty()) return
|
if (recipientMemberIds.isEmpty()) return
|
||||||
|
|
||||||
val category = resolveCategory(fcmEvent) ?: return
|
val category = resolveCategory(fcmEvent) ?: return
|
||||||
|
if (category == PushNotificationCategory.SYSTEM) return
|
||||||
val senderSnapshot = resolveSenderSnapshot(fcmEvent)
|
val senderSnapshot = resolveSenderSnapshot(fcmEvent)
|
||||||
val deepLink = FcmService.buildDeepLink(
|
val deepLink = FcmService.buildDeepLink(
|
||||||
serverEnv = serverEnv,
|
serverEnv = serverEnv,
|
||||||
|
|||||||
@@ -78,6 +78,62 @@ class PushNotificationServiceTest {
|
|||||||
Mockito.verify(pushNotificationListRepository, Mockito.never()).save(Mockito.any(PushNotificationList::class.java))
|
Mockito.verify(pushNotificationListRepository, Mockito.never()).save(Mockito.any(PushNotificationList::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNotSaveWhenResolvedCategoryIsSystem() {
|
||||||
|
// given: 이벤트 category가 null이고 타입 기반 보정 결과가 SYSTEM인 상황을 준비한다.
|
||||||
|
val event = FcmEvent(
|
||||||
|
type = FcmEventType.INDIVIDUAL,
|
||||||
|
category = null,
|
||||||
|
recipients = listOf(1L)
|
||||||
|
)
|
||||||
|
val pushTokens = listOf(PushTokenInfo(token = "token-1", deviceType = "aos", languageCode = "ko"))
|
||||||
|
|
||||||
|
Mockito.`when`(pushTokenRepository.findMemberIdsByTokenIn(listOf("token-1"))).thenReturn(listOf(1L))
|
||||||
|
|
||||||
|
// when: 알림 적재를 실행한다.
|
||||||
|
service.saveNotification(
|
||||||
|
fcmEvent = event,
|
||||||
|
languageCode = "ko",
|
||||||
|
translatedMessage = "시스템 알림",
|
||||||
|
recipientPushTokens = pushTokens
|
||||||
|
)
|
||||||
|
|
||||||
|
// then: SYSTEM 카테고리 보정 결과에 따라 저장이 발생하지 않아야 한다.
|
||||||
|
Mockito.verify(pushNotificationListRepository, Mockito.never()).save(Mockito.any(PushNotificationList::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldSaveWhenCategoryIsNullAndResolvedCategoryIsNonSystem() {
|
||||||
|
// given: 이벤트 category가 null이어도 타입 기반 보정 결과가 LIVE면 저장되어야 한다.
|
||||||
|
val event = FcmEvent(
|
||||||
|
type = FcmEventType.START_LIVE,
|
||||||
|
category = null,
|
||||||
|
roomId = 11L,
|
||||||
|
creatorId = 20L,
|
||||||
|
deepLinkValue = FcmDeepLinkValue.LIVE,
|
||||||
|
deepLinkId = 11L
|
||||||
|
)
|
||||||
|
val pushTokens = listOf(PushTokenInfo(token = "token-a", deviceType = "aos", languageCode = "ko"))
|
||||||
|
|
||||||
|
Mockito.`when`(pushTokenRepository.findMemberIdsByTokenIn(listOf("token-a"))).thenReturn(listOf(10L))
|
||||||
|
Mockito.`when`(pushNotificationListRepository.save(Mockito.any(PushNotificationList::class.java)))
|
||||||
|
.thenAnswer { invocation -> invocation.getArgument(0) }
|
||||||
|
|
||||||
|
// when: 알림 적재를 실행한다.
|
||||||
|
service.saveNotification(
|
||||||
|
fcmEvent = event,
|
||||||
|
languageCode = "ko",
|
||||||
|
translatedMessage = "라이브가 시작되었습니다.",
|
||||||
|
recipientPushTokens = pushTokens
|
||||||
|
)
|
||||||
|
|
||||||
|
// then: 보정된 LIVE 카테고리로 저장되어야 한다.
|
||||||
|
val captor = ArgumentCaptor.forClass(PushNotificationList::class.java)
|
||||||
|
Mockito.verify(pushNotificationListRepository).save(captor.capture())
|
||||||
|
val saved = captor.value
|
||||||
|
assertEquals(PushNotificationCategory.LIVE, saved.category)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldSaveChunkedRecipientsAndSenderSnapshotWhenEventIsValid() {
|
fun shouldSaveChunkedRecipientsAndSenderSnapshotWhenEventIsValid() {
|
||||||
// given: 1001명의 수신자를 가진 유효 이벤트를 준비한다.
|
// given: 1001명의 수신자를 가진 유효 이벤트를 준비한다.
|
||||||
|
|||||||
Reference in New Issue
Block a user