Phase 99: Verification
빌드 검증
nx build dha-sleep-api
빌드가 성공해야 구현 완료로 간주한다.
기능 테스트
테스트 1: SCHEDULED 보고
방법: conversation.push_planned webhook을 트리거하여 push 생성
기대 로그:
[PushDeliveryReporterService] Push delivery reported { pushId: '...', status: 'SCHEDULED' }
기대 HTTP 호출:
POST {agentzApiUrl}/push-delivery/report
Body: { pushId, status: 'SCHEDULED', pushType, sessionId, userId, scheduledAt }
테스트 2: COMPLETED 보고
방법: Push 예약 시간이 도래하여 Bull 작업이 실행되고 발송 성공
기대 로그:
[PushDeliveryReporterService] Push delivery reported { pushId: '...', status: 'COMPLETED' }
기대 HTTP 호출:
POST {agentzApiUrl}/push-delivery/report
Body: { pushId, status: 'COMPLETED', pushType, sessionId, userId, channel: 'fcm'|'telegram'|'kakaotalk', scheduledAt }
테스트 3: CANCELLED 보고
사전 조건: cancelIfUserInitiatesFirst=true인 push가 스케줄되고, 사용자가 30분 이내에 메시지 전송
기대 로그:
[PushDeliveryReporterService] Push delivery reported { pushId: '...', status: 'CANCELLED' }
테스트 4: FAILED 보고
사전 조건: FCM token 없음 + Telegram/KakaoTalk 미연결 → 모든 채널 실패 (첫 번째 시도에서 즉시 실패)
참고: 기존 코드에서 첫 실패 시 DB status→FAILED 후 throw하므로, Bull retry attempt 2에서는 "already processed" 로 스킵된다. 따라서 FAILED 보고는 첫 번째 실패 시점에서 즉시 발생한다.
기대 로그:
[PushDeliveryReporterService] Push delivery reported { pushId: '...', status: 'FAILED' }
기대 HTTP 호출:
POST {agentzApiUrl}/push-delivery/report
Body: { pushId, status: 'FAILED', error: '...', channel: 'fcm', pushType, sessionId, userId, scheduledAt }
테스트 5: Best-effort (Platform API 다운)
방법: agentz-studio API 중단 상태에서 push 발송
기대 동작:
- Push 정상 발송됨 (COMPLETED)
- Reporter 로그:
Push delivery report error (non-fatal) - Push 처리 로직에 영향 없음
테스트 6: 비활성화 상태
방법: AGENTZ_ENABLED=false 환경변수로 서비스 시작
기대 동작:
- Push 정상 발송됨
- Reporter 로그:
Push delivery reporting disabled or not configured - Platform API 호출 없음
사용자 시나리오
Happy Path: Push 정상 발송
1. Platform webhook: conversation.push_planned (type: EVENING_CHECKIN, scheduledAt: 21:00)
2. PushSchedulerService: ScheduledPush 생성
3. Reporter: POST /push-delivery/report (SCHEDULED)
4. [21:00] Bull job 실행
5. NotificationRouter: Telegram 성공
6. Reporter: POST /push-delivery/report (COMPLETED, channel: telegram)
Edge Case: User Activity 취소
1. Platform webhook: conversation.push_planned (cancelIfUserInitiatesFirst: true)
2. Reporter: POST /push-delivery/report (SCHEDULED)
3. [20:45] 사용자가 메시지 전송
4. [21:00] Bull job 실행 → isUserRecentlyActive=true
5. ScheduledPush status → CANCELLED
6. Reporter: POST /push-delivery/report (CANCELLED)
Error Path: 모든 채널 실패
1. Platform webhook: conversation.push_planned
2. Reporter: POST /push-delivery/report (SCHEDULED)
3. [21:00] Bull job 실행 (attempt 1)
4. status → TRIGGERED
5. NotificationRouter: Telegram 실패 → KakaoTalk 실패 → FCM 실패
6. status → FAILED + Reporter: POST /push-delivery/report (FAILED, channel, error)
7. throw → Bull retry 트리거
8. [attempt 2] push 조회 → status=FAILED → "already processed" → return (성공)
→ Bull은 job 성공으로 간주, retry 종료
참고: 기존 코드의 retry 동작 한계. 첫 실패에서 DB status→FAILED 후 retry에서는 status 체크에 걸려 스킵된다. 이는 기존 코드의 문제이며 이 플랜의 범위 밖.
Error Path: Reporter 실패 (non-fatal)
1. [21:00] Bull job 실행 → push 발송 성공 (COMPLETED)
2. Reporter: POST /push-delivery/report → timeout (5s)
3. Log: "Push delivery report error (non-fatal)"
4. Push 처리 결과: 정상 완료 (보고 실패가 push에 영향 없음)
5. Platform: 이 push는 보고 누락 → Sweep에서 OVERDUE로 감지됨
체크리스트
-
PushDeliveryReporterService생성 -
PushJobProcessor에 COMPLETED 보고 hook 추가 -
PushJobProcessor에 CANCELLED 보고 hook 추가 -
PushJobProcessor에 FAILED 보고 hook 추가 (handleFailedJob) -
PushSchedulerService에 SCHEDULED 보고 hook 추가 -
PushModule에 서비스 등록 - 빌드 성공 확인
- Best-effort 패턴 확인 (보고 실패 시 push 정상 동작)
변경 파일 총 목록
| # | 파일 | 유형 |
|---|---|---|
| 1 | apps/dha-sleep-api/src/app/push/push-delivery-reporter.service.ts | NEW |
| 2 | apps/dha-sleep-api/src/app/push/push-job.processor.ts | EDIT |
| 3 | apps/dha-sleep-api/src/app/push/push-scheduler.service.ts | EDIT |
| 4 | apps/dha-sleep-api/src/app/push/push.module.ts | EDIT |
| 총계 | 1 NEW + 3 EDIT |
Platform 연동 확인 (agentz-studio Plan 120 기준)
| Platform 기대 | Client 제공 | 매칭 |
|---|---|---|
pushId (externalPushId) | ScheduledPush.id (UUID) | ✅ |
status (PushDeliveryStatus enum) | SCHEDULED/COMPLETED/FAILED/CANCELLED | ✅ |
timestamp (ISO8601) | new Date().toISOString() | ✅ |
pushType (string) | ScheduledPush.type | ✅ |
sessionId (for tracking correlation) | ScheduledPush.createdFromSessionId | ✅ |
userId (for tracking correlation) | ScheduledPush.userId | ✅ |
channel (delivery channel) | routeResult.channel | ✅ |
error (failure reason) | error.message | ✅ |
retryCount | job.attemptsMade | ✅ |
scheduledAt (original schedule) | ScheduledPush.scheduledAt | ✅ |
Natural key correlation: Platform sweep은
sessionId + userId + pushType로 correlation한다. Client가 보고하는 이 3개 필드는 모두 webhook의data.sessionId,data.userId,data.pushPlan.type에서 유래하므로 정확히 매칭된다.