update: delete fcm_ack_smoke plans, update flutter-test and messaging_flutter android plugin
This commit is contained in:
parent
77d615c6b7
commit
13360fd826
5 changed files with 87 additions and 495 deletions
|
|
@ -1,239 +0,0 @@
|
|||
<!-- task=m-client-validation/04+03_fcm_ack_smoke plan=1 tag=REVIEW_SMOKE_RUN -->
|
||||
|
||||
# Code Review Reference - REVIEW_SMOKE_RUN
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a user-only decision, external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-29
|
||||
task=m-client-validation/04+03_fcm_ack_smoke, plan=1, tag=REVIEW_SMOKE_RUN
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-client-validation/04+03_fcm_ack_smoke/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
|
||||
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_SMOKE_RUN-1] Preflight와 remote readiness 승계 | [x] |
|
||||
| [REVIEW_SMOKE_RUN-2] Android app 실행과 FCM token evidence | [x] |
|
||||
| [REVIEW_SMOKE_RUN-3] Real push와 ACK evidence 수집 | [ ] |
|
||||
| [REVIEW_SMOKE_RUN-4] Manual smoke checklist evidence 정리 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [REVIEW_SMOKE_RUN-1] Archived preflight evidence와 원격 Android/trigger readiness를 확인하고 smoke 실행 변수를 정한다.
|
||||
- [x] [REVIEW_SMOKE_RUN-2] 원격 Android target에서 `apps/flutter-test`를 실행하고 FCM device token evidence를 수집한다.
|
||||
- [ ] [REVIEW_SMOKE_RUN-3] 실제 push trigger 또는 push-proxy `/api/v1/send_push`를 실행하고 FCM receipt, notification display, ACK/log evidence를 수집한다.
|
||||
- [ ] [REVIEW_SMOKE_RUN-4] opened-routing, inline reply, dismiss, device-token prefix evidence를 redact해서 review stub에 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
|
||||
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- 사용자 추가 지시("너가 일단 진행해봐")에 따라 plan의 기본 범위를 확장해 repo-owned test host 초기화 누락을 직접 수정했다.
|
||||
- `apps/flutter-test/lib/main.dart`에 `WidgetsFlutterBinding.ensureInitialized()`, `Firebase.initializeApp()`, `NexoMessagingPlugin.instance.initialize()` 호출을 추가했다. `onDeviceTokenReady` callback이 등록된 뒤 plugin 초기화가 실행되도록 `MyApp` initState에서 비동기로 호출했다.
|
||||
- Widget/integration tests가 Firebase/FCM 환경에 묶이지 않도록 `MyApp(initializePlugin: false)` 옵션을 추가하고 기존 tests는 이 옵션을 사용하게 했다.
|
||||
- `firebase_core`를 `apps/flutter-test/pubspec.yaml`의 direct dependency로 추가했다. `firebase_core` import가 app source에 직접 생겼기 때문이다.
|
||||
- 원격 smoke는 계속 `ORG_GRADLE_PROJECT_nexoSmokeApplicationId=com.tokilabs.mattermost` override로 진행했다. 기본 `com.tokilabs.nexo_client`는 원격 `google-services.json`에 client가 없어 `:app:processDebugGoogleServices`에서 실패한다.
|
||||
- REVIEW_SMOKE_RUN-3은 부분 진행됐다. raw token 확보 후 push-proxy `/api/v1/send_push`는 `{"status":"OK"}`를 반환했고 device logcat에서 `NexoFCM` receipt와 `ReceiptDelivery` ACK POST를 확인했다. 그러나 placeholder/unsigned signature는 `Notification skipped: signature verification failed`로 막혀 notification display, opened-routing, inline reply, dismiss evidence는 아직 없다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- 원격 readiness(REVIEW_SMOKE_RUN-1)는 archived preflight 승계 전제하에 통과: 원격 flutter/adb 존재, `emulator-5554` 연결, `NEXO_SMOKE_TRIGGER_CMD` non-empty, `push_proxy_url=http://localhost:18066`.
|
||||
- 앱 초기화 수정 후 remote Android에서 `FirebaseInitProvider initialization successful`, `Permission: AuthorizationStatus.authorized`, `DatabaseHelper: Device token saved`, `[PushNotification] Device token saved`를 확인했다. Raw token 값은 출력하지 않고 remote `/tmp/nexo_global.db`에서 env로만 추출했다.
|
||||
- token prefix는 `android_rn-v2:`가 맞다(`packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart:14,211` → `'$_kDeviceTokenPrefix-v2:$token'`, prefix `android_rn`). Device token DB row length는 156이고 raw token은 review stub에 남기지 않았다.
|
||||
- Actual push evidence는 두 단계로 나뉜다. FCM receipt/ACK path는 PASS: push-proxy response `{"status":"OK"}`, logcat `NexoFCM : >>> onMessageReceived ENTER`, `ReceiptDelivery: Send ACK=...`, `NomadNetwork: POST .../api/v4/notifications/ack`. Notification display/routing path는 BLOCKED: valid signed payload가 없어 `Notification skipped: signature verification failed`.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._
|
||||
|
||||
- 상태: 차단(BLOCKED)
|
||||
- 사유 유형: 외부 signed push payload 또는 signing material 부재
|
||||
- 진행 경과: repo-owned test host 초기화 누락은 수정했고, raw FCM token 확보와 실제 FCM receipt/ACK evidence까지 수집했다. 남은 차단은 valid signature 단계다.
|
||||
- 결정 필요:
|
||||
1. 실제 server-generated valid signature가 포함된 push trigger를 제공할지.
|
||||
2. 또는 smoke 범위를 이번 loop에서는 FCM receipt + ACK까지로 축소하고 notification display/opened/reply/dismiss/signature 항목을 후속으로 분리할지.
|
||||
- 차단 근거:
|
||||
- REVIEW_SMOKE_RUN-3: placeholder signature push와 unsigned push 모두 FCM receipt + ACK POST까지 도달했지만 `Notification skipped: signature verification failed`로 notification display 전에 중단됐다.
|
||||
- 원격 app shared preferences에 `signing_key_https://mattermost.toki-labs.com`가 존재한다. 따라서 bogus/non-server signature는 정상적으로 reject된다.
|
||||
- `NEXO_SMOKE_TRIGGER_CMD` helper는 현재 valid signed payload를 만들어 보내지 않는다.
|
||||
- 실행한 검증/명령(실제 출력):
|
||||
- Local: `flutter analyze` PASS, `flutter test` PASS.
|
||||
- Remote: `flutter pub get && flutter analyze && flutter test` PASS.
|
||||
- Remote integration: `flutter test integration_test -d emulator-5554` built and installed the app, then produced no runner output for over 40s; the local ssh wrapper was killed. Treat as inconclusive, not PASS.
|
||||
- Remote run after init fix: `FirebaseInitProvider initialization successful`, `[PushNotification] Permission: AuthorizationStatus.authorized`, `DatabaseHelper: Device token saved`, `[PushNotification] Device token saved`.
|
||||
- Remote push with placeholder signature: push-proxy response `{"status":"OK"}`; logcat `NexoFCM : >>> onMessageReceived ENTER`, `ReceiptDelivery: Send ACK=nexo-smoke-1780028173`, `NomadNetwork: POST https://mattermost.toki-labs.com/api/v4/notifications/ack`, then `Notification skipped: signature verification failed`.
|
||||
- Remote push without signature: push-proxy response `{"status":"OK"}`; logcat `NexoFCM : >>> onMessageReceived ENTER`, `ReceiptDelivery: Send ACK=nexo-smoke-nosig-1780028205`, then `Notification skipped: signature verification failed`.
|
||||
- Archived preflight `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`: local `test -f` PASS.
|
||||
- 미완: valid signature PASS, invalid/unsigned drop, notification display, opened-routing, inline reply, dismiss.
|
||||
- 환경 정리: override 빌드는 emulator-5554의 기존 `com.tokilabs.mattermost`(Detox 자산)를 교체했다(사용자 승인). Detox 재사용이 필요하면 Mattermost 앱 재설치가 필요할 수 있음을 명시한다.
|
||||
- 재개 조건:
|
||||
1. Valid server-generated signature가 포함된 push trigger를 준비한다.
|
||||
2. 같은 remote app/token 상태에서 trigger를 재실행한다.
|
||||
3. Notification display 이후 tap/opened, inline reply, dismiss evidence를 수집한다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- Preflight dependency가 active-only path가 아니라 archived `complete.log`로 확인되었는지 본다.
|
||||
- `smoke_trigger_ready=1` readiness output을 actual FCM send evidence로 오인하지 않았는지 본다.
|
||||
- Raw token, auth token, signing key, private endpoint values가 tracked file에 남지 않았는지 본다.
|
||||
- Manual checklist 항목이 PASS/BLOCKED/FAIL로 과장 없이 분류되었는지 본다.
|
||||
- Real push evidence와 ACK evidence가 stdout/stderr, logcat, server/push-proxy log, screenshot/recording path 중 재검토 가능한 형태인지 본다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### REVIEW_SMOKE_RUN-1 중간 검증
|
||||
```
|
||||
$ test -f agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log
|
||||
exit=0
|
||||
|
||||
$ ssh toki@toki-labs.com 'zsh -lc '...'
|
||||
set -e
|
||||
command -v flutter -> /Users/toki/SDK/flutter/bin/flutter
|
||||
command -v adb -> /Users/toki/Library/Android/sdk/platform-tools/adb
|
||||
test -n "${NEXO_SMOKE_TRIGGER_CMD:-}" -> (set -e 통과 = non-empty)
|
||||
adb devices:
|
||||
List of devices attached
|
||||
emulator-5554 device
|
||||
push_proxy_url=http://localhost:18066
|
||||
remote_exit=0
|
||||
```
|
||||
PASS: 원격 readiness 충족.
|
||||
|
||||
### REVIEW_SMOKE_RUN-2 중간 검증
|
||||
```
|
||||
$ flutter analyze
|
||||
Analyzing flutter-test...
|
||||
No issues found! (ran in 4.2s)
|
||||
|
||||
$ flutter test
|
||||
00:00 +4: All tests passed!
|
||||
|
||||
$ ssh toki@toki-labs.com 'zsh -lc "cd ~/works/nexo/apps/flutter-test && flutter pub get && flutter analyze && flutter test"'
|
||||
Analyzing flutter-test...
|
||||
No issues found! (ran in 5.5s)
|
||||
00:00 +4: All tests passed!
|
||||
|
||||
$ ssh toki@toki-labs.com 'zsh -lc "cd ~/works/nexo/apps/flutter-test && flutter test integration_test -d emulator-5554"'
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
Installing build/app/outputs/flutter-apk/app-debug.apk...
|
||||
# 이후 테스트 runner 출력 없이 40초 이상 대기. local ssh wrapper kill, exit=255. Inconclusive.
|
||||
|
||||
$ ssh toki@toki-labs.com 'zsh -lc "... flutter run -d emulator-5554 --no-resident ..."'
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
Installing build/app/outputs/flutter-apk/app-debug.apk...
|
||||
FirebaseInitProvider: FirebaseApp initialization successful
|
||||
[PushNotification] Permission: AuthorizationStatus.authorized
|
||||
DatabaseHelper: Device token saved
|
||||
[PushNotification] Device token saved
|
||||
```
|
||||
PASS: app initialization fix works locally and on remote. Remote app stores an FCM token. Raw token was not printed; screenshot path `/tmp/nexo-smoke-screen-after-init.png`, run log path `/tmp/nexo-flutter-test-run-after-init.log`, token log path `/tmp/nexo-token-logcat-after-init.txt`.
|
||||
|
||||
### REVIEW_SMOKE_RUN-3 중간 검증
|
||||
```
|
||||
$ ssh toki@toki-labs.com 'zsh -lc "... curl -fsS -X POST http://localhost:18066/api/v1/send_push ..."'
|
||||
{"status":"OK"}
|
||||
|
||||
# placeholder signature push, ack_id=nexo-smoke-1780028173
|
||||
NexoFCM : >>> onMessageReceived ENTER, dataSize=20
|
||||
NexoFCM : onMessageReceived type=message channelId=nexo-smoke-channel ackId=nexo-smoke-1780028173 serverUrl=https://mattermost.toki-labs.com
|
||||
ReceiptDelivery: Send ACK=nexo-smoke-1780028173 TYPE=message to URL=https://mattermost.toki-labs.com ID-LOADED=false
|
||||
NomadNetwork: POST https://mattermost.toki-labs.com/api/v4/notifications/ack
|
||||
NexoFCM : Notification skipped: signature verification failed
|
||||
|
||||
$ ssh toki@toki-labs.com 'zsh -lc "... curl -fsS -X POST http://localhost:18066/api/v1/send_push ..."'
|
||||
{"status":"OK"}
|
||||
|
||||
# unsigned push, ack_id=nexo-smoke-nosig-1780028205
|
||||
NexoFCM : >>> onMessageReceived ENTER, dataSize=20
|
||||
NexoFCM : onMessageReceived type=message channelId=nexo-smoke-channel ackId=nexo-smoke-nosig-1780028205 serverUrl=https://mattermost.toki-labs.com
|
||||
ReceiptDelivery: Send ACK=nexo-smoke-nosig-1780028205 TYPE=message to URL=https://mattermost.toki-labs.com ID-LOADED=false
|
||||
NomadNetwork: POST https://mattermost.toki-labs.com/api/v4/notifications/ack
|
||||
NexoFCM : Notification skipped: signature verification failed
|
||||
```
|
||||
PARTIAL: FCM receipt and ACK request evidence collected. Notification display and downstream opened/reply/dismiss evidence remain BLOCKED by signature verification. Remote evidence paths: `/tmp/nexo-send-response.json`, `/tmp/nexo-after-send-logcat.txt`, `/tmp/nexo-send-response-nosig.json`, `/tmp/nexo-after-send-logcat-nosig.txt`.
|
||||
|
||||
### REVIEW_SMOKE_RUN-4 중간 검증
|
||||
Manual smoke checklist 분류:
|
||||
|
||||
| 항목 | 판정 | 사유 |
|
||||
|------|------|------|
|
||||
| FCM payload wakes / receipt | PASS | `NexoFCM : >>> onMessageReceived ENTER`, `onMessageReceived ... ackId=...` |
|
||||
| valid signature 처리 | BLOCKED | server-generated valid signature가 없음. Placeholder signature rejected. |
|
||||
| invalid/unsigned drop | PASS | placeholder and unsigned attempts both ended at `Notification skipped: signature verification failed`. |
|
||||
| notification display | BLOCKED | signature verification 단계에서 skip되어 system notification evidence 없음. |
|
||||
| ACK request success | PASS | `ReceiptDelivery: Send ACK=...`, `NomadNetwork: POST .../api/v4/notifications/ack`; no failure log observed. |
|
||||
| onNotificationOpened (opened event) | BLOCKED | notification display가 없어 tap/open evidence 수집 불가. |
|
||||
| onNavigateToChannel / onNavigateToThread routing | BLOCKED | notification display/open blocked. |
|
||||
| Inline reply | BLOCKED | notification display blocked. |
|
||||
| Dismiss action | BLOCKED | notification display blocked. |
|
||||
| Device token prefix(`android_rn-v2:`) | PASS | app code formats prefix and remote DB stored token length 156 after stripping prefix for push; raw token not printed. |
|
||||
|
||||
```
|
||||
$ rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token|BLOCKED|FAIL" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
# 위 표/근거로 매칭됨.
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
$ test -f agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log
|
||||
exit=0
|
||||
$ cd apps/flutter-test && flutter analyze
|
||||
No issues found! (ran in 4.2s)
|
||||
$ cd apps/flutter-test && flutter test
|
||||
00:00 +4: All tests passed!
|
||||
$ rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token|BLOCKED|FAIL" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
# REVIEW_SMOKE_RUN-4 표와 검증 결과 전반에서 PASS/BLOCKED evidence가 매칭됨.
|
||||
```
|
||||
종합: REVIEW_SMOKE_RUN-1 PASS, REVIEW_SMOKE_RUN-2 PASS, REVIEW_SMOKE_RUN-3 PARTIAL(FCM receipt + ACK PASS, notification display BLOCKED), REVIEW_SMOKE_RUN-4 PARTIAL. 구현 미완료 상태로 `사용자 리뷰 요청` 작성 후 중단. active 파일 유지.
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
<!-- task=m-client-validation/04+03_fcm_ack_smoke plan=1 tag=REVIEW_SMOKE_RUN -->
|
||||
|
||||
# Plan - REVIEW_SMOKE_RUN: Resume Real FCM And ACK Smoke
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 계획의 구현이 끝나도 작업은 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용으로 채우기 전까지 완료가 아니다. 검증 명령을 실행하고 실제 stdout/stderr를 붙이며, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 사용자만 줄 수 있는 결정, 외부 환경 준비, 또는 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션을 정확한 근거와 재개 조건으로 채우고 멈춘다. `USER_REVIEW.md`, `complete.log`, archive 이동은 code-review 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
이전 review stop은 `03_smoke_preflight`를 active 경로에서만 찾아 생긴 false blocker였다. 선행 preflight는 archive에 PASS 완료되어 있고, 원격 Android host와 trigger env도 확인됐다. 이 계획은 archived preflight evidence를 승계해 실제 FCM delivery와 ACK smoke를 다시 진행한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단 사유가 생기면 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션을 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 채운다. code-review가 그 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/skills/common/plan/SKILL.md`
|
||||
- `agent-ops/skills/common/code-review/SKILL.md`
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/product-foundation/PHASE.md`
|
||||
- `agent-roadmap/phase/product-foundation/milestones/client-validation.md`
|
||||
- `agent-task/m-client-validation/04+03_fcm_ack_smoke/user_review_0.log`
|
||||
- `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`
|
||||
- `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/code_review_cloud_G07_2.log`
|
||||
- `apps/flutter-test/README.md`
|
||||
- `apps/flutter-test/lib/main.dart`
|
||||
- `packages/messaging_flutter/README.md`
|
||||
- `packages/messaging_flutter/docs/android-test-environment.md`
|
||||
- `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoFirebaseMessagingService.kt`
|
||||
- `packages/messaging_flutter/android/src/main/java/com/tokilabs/nexo/messaging/ReceiptDelivery.java`
|
||||
- `packages/messaging_flutter/android/src/main/java/com/tokilabs/nexo/messaging/helpers/CustomPushNotificationHelper.java`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/helpers/DatabaseHelper.kt`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/helpers/Network.kt`
|
||||
- `services/push-proxy/server/push_notification.go`
|
||||
- `services/push-proxy/server/server.go`
|
||||
- `services/push-proxy/server/android_notification_server.go`
|
||||
- `services/push-proxy/swagger/spec.yaml`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- True Firebase delivery, notification display, inline reply, dismiss, and server ACK remain outside deterministic Flutter tests. Evidence must come from remote Android logs, app UI/screenshot or recording, push-proxy/server logs, and redacted command output.
|
||||
- The active task is evidence collection. No new deterministic test file is required unless the smoke reveals a repo-owned setup bug.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- none. public symbol rename/remove 없음.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
Split decision policy was evaluated. This remains one dependent subtask under shared task group `m-client-validation`.
|
||||
|
||||
- Current task: `04+03_fcm_ack_smoke`.
|
||||
- Decoded predecessor: `03_smoke_preflight`.
|
||||
- Resolved predecessor evidence: `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`.
|
||||
- Previous false blocker: `agent-task/m-client-validation/03_smoke_preflight/complete.log` is absent because PASS moved the task to archive.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- Source code changes are not the default scope. If actual smoke proves a repo-owned setup bug, record exact evidence in the review stub instead of silently expanding scope.
|
||||
- Do not write secret, token, signing key, auth token, raw Firebase credential, or private endpoint values into tracked files.
|
||||
- Remote raw logs, screenshots, recordings, and unredacted trigger output must stay outside the repository; record only redacted excerpts or file paths.
|
||||
- `NEXO_SMOKE_TRIGGER_CMD` currently points to a readiness helper that prints `smoke_trigger_ready=1`; that output alone is not FCM send evidence.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. This depends on remote shell, `flutter run`, adb/logcat, external FCM/push-proxy behavior, stdout/stderr interpretation, and manual evidence.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [REVIEW_SMOKE_RUN-1] Archived preflight evidence와 원격 Android/trigger readiness를 확인하고 smoke 실행 변수를 정한다.
|
||||
- [ ] [REVIEW_SMOKE_RUN-2] 원격 Android target에서 `apps/flutter-test`를 실행하고 FCM device token evidence를 수집한다.
|
||||
- [ ] [REVIEW_SMOKE_RUN-3] 실제 push trigger 또는 push-proxy `/api/v1/send_push`를 실행하고 FCM receipt, notification display, ACK/log evidence를 수집한다.
|
||||
- [ ] [REVIEW_SMOKE_RUN-4] opened-routing, inline reply, dismiss, device-token prefix evidence를 redact해서 review stub에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
이 task directory 이름 `04+03_fcm_ack_smoke`는 같은 task group의 `03_smoke_preflight`에 의존한다. 선행 완료 경로는 active가 아니라 archive의 `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`다. 구현 시작 전 이 경로를 확인하고, active-only 경로 부재를 blocker로 보지 않는다.
|
||||
|
||||
## [REVIEW_SMOKE_RUN-1] Preflight와 remote readiness 승계
|
||||
|
||||
### 문제
|
||||
|
||||
`user_review_0.log`는 `03_smoke_preflight/complete.log` active 경로 부재를 blocker로 기록했다. 실제 완료 산출물은 `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`에 있다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
Archived preflight를 선행 완료로 인정하고 원격 host에서 Android target과 trigger env를 다시 확인한다. `NEXO_ANDROID_DEVICE_ID`가 비어 있으면 connected device `emulator-5554`를 사용한다. `NEXO_PUSH_PROXY_URL`은 remote private rule 기준 기본값 `http://localhost:18066`을 사용하되, 이미 원격 env에 값이 있으면 그 값을 우선한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log` 존재 확인.
|
||||
- [ ] remote `adb`, `flutter`, connected Android device 확인.
|
||||
- [ ] `NEXO_SMOKE_TRIGGER_CMD` 존재 확인.
|
||||
- [ ] smoke runtime 기본값을 secret 없이 기록.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
테스트 파일 추가 없음. 이 항목은 remote smoke readiness 검증이다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
test -f agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log
|
||||
ssh toki@toki-labs.com 'zsh -lc '"'"'
|
||||
set -e
|
||||
command -v flutter
|
||||
command -v adb
|
||||
test -n "${NEXO_SMOKE_TRIGGER_CMD:-}"
|
||||
adb devices | sed -n "1,8p"
|
||||
printf "push_proxy_url=%s\n" "${NEXO_PUSH_PROXY_URL:-http://localhost:18066}"
|
||||
'"'"'
|
||||
```
|
||||
|
||||
기대 결과: archived preflight exists, remote Flutter/adb exist, `NEXO_SMOKE_TRIGGER_CMD` is non-empty, at least one Android device is connected, and the push proxy URL is resolved without printing secrets.
|
||||
|
||||
## [REVIEW_SMOKE_RUN-2] Android app 실행과 FCM token evidence
|
||||
|
||||
### 문제
|
||||
|
||||
FCM send requires a raw Firebase token. The test app displays the plugin-formatted token as `android_rn-v2:<raw-token>` in `apps/flutter-test/lib/main.dart`.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
Remote host에서 `apps/flutter-test`를 지정 Android device로 실행한다. `flutter run`은 long-running command이므로 output을 `/tmp`에 저장하고 review stub에는 redacted path와 relevant lines만 기록한다. Device token은 UI/log에서 `android_rn-v2:` prefix evidence를 남기고, actual push에는 prefix 뒤 raw token을 `NEXO_SMOKE_DEVICE_ID`로 사용한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] remote app dependency 준비.
|
||||
- [ ] logcat buffer clear.
|
||||
- [ ] app 실행 output 저장.
|
||||
- [ ] `android_rn-v2:` token prefix evidence 기록.
|
||||
- [ ] raw token은 review stub에 출력하지 않고 redacted 처리.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
테스트 파일 추가 없음. FCM token acquisition은 remote Android runtime evidence다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
ssh toki@toki-labs.com 'zsh -lc '"'"'
|
||||
set -e
|
||||
export PATH="$HOME/SDK/flutter/bin:$HOME/Library/Android/sdk/emulator:$HOME/Library/Android/sdk/platform-tools:$HOME/Library/Android/sdk/cmdline-tools/latest/bin:$PATH"
|
||||
export ANDROID_HOME="$HOME/Library/Android/sdk"
|
||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
||||
export NEXO_ANDROID_DEVICE_ID="${NEXO_ANDROID_DEVICE_ID:-emulator-5554}"
|
||||
cd "$HOME/works/nexo/apps/flutter-test"
|
||||
flutter pub get
|
||||
adb -s "$NEXO_ANDROID_DEVICE_ID" logcat -c
|
||||
flutter run -d "$NEXO_ANDROID_DEVICE_ID" 2>&1 | tee /tmp/nexo-flutter-test-run.log
|
||||
'"'"'
|
||||
```
|
||||
|
||||
기대 결과: app이 remote Android target에서 실행되고 `Device token: android_rn-v2:<redacted>` evidence를 UI/log/screenshot/recording 중 하나로 수집할 수 있다.
|
||||
|
||||
## [REVIEW_SMOKE_RUN-3] Real push와 ACK evidence 수집
|
||||
|
||||
### 문제
|
||||
|
||||
Preflight helper의 `smoke_trigger_ready=1`은 readiness evidence일 뿐 실제 FCM send가 아니다. 실제 smoke는 FCM receipt와 plugin ACK/server log를 증명해야 한다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
우선 private `NEXO_SMOKE_TRIGGER_CMD`가 실제 send를 수행하는지 확인한다. readiness-only helper라면 `NEXO_PUSH_PROXY_URL`, `NEXO_SMOKE_DEVICE_ID`, generated `NEXO_SMOKE_ACK_ID`를 사용해 remote push-proxy `/api/v1/send_push`로 direct trigger를 실행한다. Raw token, server URL, auth/signing values는 출력하지 않는다. Valid signature가 필요한 단계에서 signing material이 없다는 구체 증거가 나오면 review stub의 `사용자 리뷰 요청`에 필요한 값과 command output을 남긴다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `NEXO_SMOKE_TRIGGER_CMD` output이 actual send인지 readiness-only인지 분류.
|
||||
- [ ] readiness-only이면 push-proxy direct trigger command를 실행.
|
||||
- [ ] `NexoFirebaseMessagingService`, `ReceiptDelivery`, `ACK`, `signature`, `opened`, `Navigate` logcat evidence 저장.
|
||||
- [ ] push-proxy/server ACK or send response evidence를 redacted 형태로 기록.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
테스트 파일 추가 없음. 실제 FCM/server smoke evidence 수집이다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
ssh toki@toki-labs.com 'zsh -lc '"'"'
|
||||
set -e
|
||||
export NEXO_ANDROID_DEVICE_ID="${NEXO_ANDROID_DEVICE_ID:-emulator-5554}"
|
||||
export NEXO_PUSH_PROXY_URL="${NEXO_PUSH_PROXY_URL:-http://localhost:18066}"
|
||||
export NEXO_SMOKE_ACK_ID="${NEXO_SMOKE_ACK_ID:-nexo-smoke-$(date +%s)}"
|
||||
test -n "${NEXO_SMOKE_TRIGGER_CMD:-}"
|
||||
bash -lc "$NEXO_SMOKE_TRIGGER_CMD" 2>&1 | tee /tmp/nexo-smoke-trigger.log
|
||||
if rg -q "smoke_trigger_ready=1" /tmp/nexo-smoke-trigger.log && ! rg -q "status|message_id|Sending android push notification" /tmp/nexo-smoke-trigger.log; then
|
||||
printf "trigger_class=readiness_only\n"
|
||||
else
|
||||
printf "trigger_class=actual_send_or_unknown\n"
|
||||
fi
|
||||
adb -s "$NEXO_ANDROID_DEVICE_ID" logcat -d |
|
||||
rg "NexoFirebaseMessagingService|ReceiptDelivery|NotificationReplyBroadcastReceiver|NotificationDismissService|ACK|signature|opened|Navigate" |
|
||||
tee /tmp/nexo-smoke-logcat.txt
|
||||
'"'"'
|
||||
```
|
||||
|
||||
기대 결과: trigger가 actual send인지 분류되고, actual FCM receipt/ACK/signature/opened 관련 logcat evidence가 `/tmp/nexo-smoke-logcat.txt`에 저장된다. `trigger_class=readiness_only`만 있고 FCM receipt가 없으면 actual send 조건을 review stub에 BLOCKED로 기록한다.
|
||||
|
||||
## [REVIEW_SMOKE_RUN-4] Manual smoke checklist evidence 정리
|
||||
|
||||
### 문제
|
||||
|
||||
`packages/messaging_flutter/README.md`의 manual smoke checklist는 FCM, notification display, ACK, opened-routing, inline reply, dismiss, token prefix를 모두 분류해야 한다. 일부만 확인하고 PASS로 처리하면 manual-smoke Epic이 과하게 완료된다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
Review stub에 각 checklist 항목을 PASS/BLOCKED/FAIL로 기록한다. Secret이 포함된 원본은 repository 밖 `/tmp`나 remote path만 남긴다. 미확인 항목은 PASS가 아니라 BLOCKED/FAIL로 분류하고 다음 재개 조건을 적는다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] FCM receipt, valid signature, invalid/unsigned drop, notification display evidence 기록.
|
||||
- [ ] ACK success, opened event, channel/thread routing evidence 기록.
|
||||
- [ ] inline reply, dismiss, `android_rn-v2:` token prefix evidence 기록.
|
||||
- [ ] 미확인 항목은 PASS/BLOCKED/FAIL로 분류하고 사유 기록.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
테스트 파일 추가 없음. Manual evidence 정리 작업이다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token|BLOCKED|FAIL" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
```
|
||||
|
||||
기대 결과: review stub에 manual smoke checklist evidence가 항목별로 기록된다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
| --- | --- |
|
||||
| `agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md` | REVIEW_SMOKE_RUN-1, REVIEW_SMOKE_RUN-2, REVIEW_SMOKE_RUN-3, REVIEW_SMOKE_RUN-4 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```sh
|
||||
test -f agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log
|
||||
rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token|BLOCKED|FAIL" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
```
|
||||
|
||||
기대 결과: archived preflight dependency가 확인되고, review stub에 actual smoke checklist 항목의 PASS/BLOCKED/FAIL evidence가 있다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -58,6 +58,7 @@ class _MyAppState extends State<MyApp> {
|
|||
|
||||
Future<void> _initializePlugin(NexoMessagingPlugin plugin) async {
|
||||
try {
|
||||
await _configureSmokeAuthToken(plugin);
|
||||
await plugin.initialize();
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint('[NexoClient] Failed to initialize messaging plugin: $error');
|
||||
|
|
@ -65,6 +66,20 @@ class _MyAppState extends State<MyApp> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _configureSmokeAuthToken(NexoMessagingPlugin plugin) async {
|
||||
const serverUrl = String.fromEnvironment('NEXO_SMOKE_SERVER_URL');
|
||||
const authToken = String.fromEnvironment('NEXO_SMOKE_AUTH_TOKEN');
|
||||
const identifier = String.fromEnvironment('NEXO_SMOKE_SERVER_IDENTIFIER');
|
||||
if (serverUrl.isEmpty || authToken.isEmpty) return;
|
||||
|
||||
await plugin.setAuthToken(
|
||||
serverUrl,
|
||||
authToken,
|
||||
identifier: identifier.isEmpty ? null : identifier,
|
||||
);
|
||||
debugPrint('[NexoClient] Smoke auth token configured');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_notificationSubscription?.cancel();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tokilabs.nexo.messaging
|
|||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.tokilabs.nexo.messaging.helpers.CustomPushNotificationHelper
|
||||
import com.tokilabs.nexo.messaging.helpers.DatabaseHelper
|
||||
import com.tokilabs.nexo.messaging.helpers.Network
|
||||
|
|
@ -122,10 +123,7 @@ class NexoMessagingPlugin :
|
|||
internal fun intentPayload(intent: Intent?): Map<String, Any?>? {
|
||||
val extras = intent?.extras ?: return null
|
||||
if (extras.isEmpty) return null
|
||||
val data = mutableMapOf<String, Any?>()
|
||||
for (key in extras.keySet()) {
|
||||
data[key] = extras.get(key)
|
||||
}
|
||||
val data = bundleToCodecMap(extras)
|
||||
if (data.isEmpty()) return null
|
||||
return data + mapOf(
|
||||
"type" to (data["type"] ?: "opened"),
|
||||
|
|
@ -133,6 +131,43 @@ class NexoMessagingPlugin :
|
|||
)
|
||||
}
|
||||
|
||||
private fun bundleToCodecMap(bundle: Bundle): Map<String, Any?> {
|
||||
val data = mutableMapOf<String, Any?>()
|
||||
for (key in bundle.keySet()) {
|
||||
data[key] = codecSafeValue(bundle.get(key))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private fun codecSafeValue(value: Any?): Any? = when (value) {
|
||||
null,
|
||||
is Boolean,
|
||||
is Int,
|
||||
is Long,
|
||||
is Double,
|
||||
is String,
|
||||
is ByteArray,
|
||||
is IntArray,
|
||||
is LongArray,
|
||||
is DoubleArray,
|
||||
-> value
|
||||
is Float -> value.toDouble()
|
||||
is Short -> value.toInt()
|
||||
is Char -> value.toString()
|
||||
is CharSequence -> value.toString()
|
||||
is Bundle -> bundleToCodecMap(value)
|
||||
is BooleanArray -> value.toList()
|
||||
is FloatArray -> value.map { it.toDouble() }
|
||||
is ShortArray -> value.map { it.toInt() }
|
||||
is CharArray -> value.map { it.toString() }
|
||||
is Array<*> -> value.map { codecSafeValue(it) }
|
||||
is Iterable<*> -> value.map { codecSafeValue(it) }
|
||||
is Map<*, *> -> value.entries.associate { (key, entryValue) ->
|
||||
key.toString() to codecSafeValue(entryValue)
|
||||
}
|
||||
else -> value.toString()
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||
val context = applicationContext
|
||||
when (call.method) {
|
||||
|
|
|
|||
|
|
@ -188,12 +188,17 @@ internal class NexoMessagingPluginTest {
|
|||
// methods throw "not mocked" at runtime. We mock them with Mockito (already
|
||||
// a test dependency) instead of pulling in Robolectric.
|
||||
|
||||
private fun mockIntent(extras: Map<String, Any?>): Intent {
|
||||
val intent = Mockito.mock(Intent::class.java)
|
||||
private fun mockBundle(extras: Map<String, Any?>): Bundle {
|
||||
val bundle = Mockito.mock(Bundle::class.java)
|
||||
Mockito.`when`(bundle.isEmpty).thenReturn(extras.isEmpty())
|
||||
Mockito.`when`(bundle.keySet()).thenReturn(extras.keys)
|
||||
extras.forEach { (key, value) -> Mockito.`when`(bundle.get(key)).thenReturn(value) }
|
||||
return bundle
|
||||
}
|
||||
|
||||
private fun mockIntent(extras: Map<String, Any?>): Intent {
|
||||
val intent = Mockito.mock(Intent::class.java)
|
||||
val bundle = mockBundle(extras)
|
||||
Mockito.`when`(intent.extras).thenReturn(bundle)
|
||||
return intent
|
||||
}
|
||||
|
|
@ -242,6 +247,32 @@ internal class NexoMessagingPluginTest {
|
|||
assertEquals(true, result["userInteraction"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withNestedBundle_convertsBundleToMap() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val nestedBundle = mockBundle(mapOf("post_id" to "post-456"))
|
||||
val intent = mockIntent(mapOf("data" to nestedBundle))
|
||||
|
||||
val result = plugin.intentPayload(intent)
|
||||
|
||||
assertNotNull(result)
|
||||
val data = result["data"] as Map<*, *>
|
||||
assertEquals("post-456", data["post_id"])
|
||||
assertEquals("opened", result["type"])
|
||||
assertEquals(true, result["userInteraction"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withUnsupportedExtra_stringifiesValue() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val intent = mockIntent(mapOf("unsupported" to Any()))
|
||||
|
||||
val result = plugin.intentPayload(intent)
|
||||
|
||||
assertNotNull(result)
|
||||
assertTrue(result["unsupported"] is String)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_alwaysSetsUserInteractionTrue() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
|
|
|||
Loading…
Reference in a new issue