update: client-validation milestone, FCM ack smoke task, flutter-test fixes, and skill updates
This commit is contained in:
parent
eb04e93d45
commit
77d615c6b7
11 changed files with 438 additions and 112 deletions
110
HANDOFF.md
Normal file
110
HANDOFF.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Handoff: client-validation / FCM ACK Smoke
|
||||
|
||||
Date: 2026-05-29
|
||||
|
||||
## Active Route
|
||||
|
||||
- Milestone: `agent-roadmap/phase/product-foundation/milestones/client-validation.md`
|
||||
- Task: `agent-task/m-client-validation/04+03_fcm_ack_smoke/`
|
||||
- Plan: `agent-task/m-client-validation/04+03_fcm_ack_smoke/PLAN-cloud-G07.md`
|
||||
- Review stub: `agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md`
|
||||
- State: incomplete, blocked on valid signed push payload or scope decision.
|
||||
|
||||
Do not record raw FCM tokens, auth tokens, signing keys, private endpoint values,
|
||||
screenshots with secrets, or unredacted logs in tracked files.
|
||||
|
||||
## Completed In This Loop
|
||||
|
||||
- `agent-ops/skills/common/plan/SKILL.md` and
|
||||
`agent-ops/skills/common/code-review/SKILL.md` now resolve split predecessor
|
||||
completion from both active task paths and `agent-task/archive/**`.
|
||||
- Archived predecessor is valid:
|
||||
`agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`.
|
||||
- `apps/flutter-test` now initializes Firebase and `NexoMessagingPlugin` at app
|
||||
startup, after callbacks are registered.
|
||||
- Widget/integration tests can construct `MyApp(initializePlugin: false)` so
|
||||
deterministic tests are not tied to Firebase/FCM runtime setup.
|
||||
- `apps/flutter-test` has direct `firebase_core` dependency.
|
||||
- Local `cd apps/flutter-test && flutter analyze` passed.
|
||||
- Local `cd apps/flutter-test && flutter test` passed.
|
||||
- Remote `cd apps/flutter-test && flutter pub get && flutter analyze && flutter test`
|
||||
passed.
|
||||
- Remote app run with
|
||||
`ORG_GRADLE_PROJECT_nexoSmokeApplicationId=com.tokilabs.mattermost` initialized
|
||||
Firebase/plugin and saved an FCM token.
|
||||
- Remote push-proxy direct sends returned `{"status":"OK"}`.
|
||||
- Device logcat confirmed FCM receipt and ACK request:
|
||||
- `NexoFCM : >>> onMessageReceived ENTER`
|
||||
- `NexoFCM : onMessageReceived type=message ... ackId=...`
|
||||
- `ReceiptDelivery: Send ACK=...`
|
||||
- `NomadNetwork: POST https://mattermost.toki-labs.com/api/v4/notifications/ack`
|
||||
- Placeholder and unsigned payloads were rejected with
|
||||
`Notification skipped: signature verification failed`, which is expected.
|
||||
|
||||
## Current Blocker
|
||||
|
||||
Notification display and downstream behavior are still blocked because the smoke
|
||||
does not yet have a valid server-generated signed push payload.
|
||||
|
||||
Confirmed PASS:
|
||||
|
||||
- Archived preflight dependency.
|
||||
- Remote readiness.
|
||||
- Firebase/plugin initialization.
|
||||
- Device token save, without recording raw token.
|
||||
- FCM receipt.
|
||||
- ACK POST request evidence.
|
||||
- Invalid/unsigned signature rejection.
|
||||
|
||||
Still BLOCKED:
|
||||
|
||||
- Valid signature pass.
|
||||
- System notification display.
|
||||
- Notification tap/opened event.
|
||||
- Channel/thread routing.
|
||||
- Inline reply.
|
||||
- Dismiss action.
|
||||
|
||||
Remote integration note: `flutter test integration_test -d emulator-5554` built
|
||||
and installed the app, then produced no runner output for more than 40 seconds.
|
||||
The local ssh wrapper was killed. Treat this as inconclusive, not PASS.
|
||||
|
||||
## Continue From Here
|
||||
|
||||
1. Keep active files in place. Do not write `complete.log` or move the task to
|
||||
archive until code-review flow decides it.
|
||||
2. Decide whether to provide a valid signed push trigger or split the remaining
|
||||
notification UI checks into a follow-up task.
|
||||
3. If continuing the smoke, use the remote environment from
|
||||
`agent-ops/rules/private/testing-env.md`.
|
||||
4. Continue with device `emulator-5554` and:
|
||||
`ORG_GRADLE_PROJECT_nexoSmokeApplicationId=com.tokilabs.mattermost`.
|
||||
5. Do not print the raw token. Keep it only in the remote shell env used for the
|
||||
push request.
|
||||
6. Re-run push with a valid signature, then collect redacted evidence for
|
||||
display/opened/routing/reply/dismiss.
|
||||
7. Update `CODE_REVIEW-cloud-G07.md` first. User-review or final archive
|
||||
decisions remain code-review responsibilities.
|
||||
|
||||
Useful remote evidence paths already referenced in the review stub:
|
||||
|
||||
- `/tmp/nexo-flutter-test-run-after-init.log`
|
||||
- `/tmp/nexo-token-logcat-after-init.txt`
|
||||
- `/tmp/nexo-smoke-screen-after-init.png`
|
||||
- `/tmp/nexo-send-response.json`
|
||||
- `/tmp/nexo-after-send-logcat.txt`
|
||||
- `/tmp/nexo-send-response-nosig.json`
|
||||
- `/tmp/nexo-after-send-logcat-nosig.txt`
|
||||
|
||||
## Source Files Touched
|
||||
|
||||
- `agent-ops/skills/common/plan/SKILL.md`
|
||||
- `agent-ops/skills/common/code-review/SKILL.md`
|
||||
- `agent-roadmap/phase/product-foundation/milestones/client-validation.md`
|
||||
- `agent-task/m-client-validation/04+03_fcm_ack_smoke/PLAN-cloud-G07.md`
|
||||
- `agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md`
|
||||
- `apps/flutter-test/lib/main.dart`
|
||||
- `apps/flutter-test/pubspec.yaml`
|
||||
- `apps/flutter-test/pubspec.lock`
|
||||
- `apps/flutter-test/test/widget_test.dart`
|
||||
- `apps/flutter-test/integration_test/plugin_integration_test.dart`
|
||||
|
|
@ -35,6 +35,7 @@ Implementation may stop early by filling the active review stub's `사용자 리
|
|||
- If `review-number == 1`, the verdict is `FAIL`, and the blocking issue is a test environment problem rather than a repo-fixable implementation issue, stop the automatic loop and write `USER_REVIEW.md`.
|
||||
- If the active review file has a filled `사용자 리뷰 요청` with `상태` other than `없음`, and the blocker is a user-only decision, external environment prerequisite, or scope conflict that the repository cannot safely resolve by code changes alone, stop the automatic loop and write `USER_REVIEW.md`.
|
||||
- Treat a first-review failure as a test environment problem only with evidence: missing SDK/tool/runtime, unavailable external service, absent secret/credential, unsupported OS/device/interactive environment, or another prerequisite outside the repository. Record the exact command and stdout/stderr. Before claiming a tool is missing, require `command -v <tool>` or the project-equivalent check.
|
||||
- If the blocker claims a split predecessor `complete.log` is missing, check both `agent-task/{task_group}/{predecessor_subtask}/complete.log` and `agent-task/archive/*/*/{task_group}/{predecessor_subtask}/complete.log` before triggering `USER_REVIEW.md`. If an archived predecessor `complete.log` exists, do not use the active-only miss as a user-review reason; write a normal WARN/FAIL follow-up plan that cites the archived path unless independent user-only or external blockers remain.
|
||||
- Treat an implementation-owned `사용자 리뷰 요청` as valid only with specific decision questions, evidence, attempted commands/output or a reason commands could not run, and clear resume conditions. Vague uncertainty, repo-owned setup bugs, and ordinary incomplete implementation become normal WARN/FAIL follow-up plans instead.
|
||||
- Do not classify repo-owned setup bugs as environment problems. If a manifest, bootstrap script, test fixture, or documented project entrypoint should provide the dependency, write a normal follow-up plan instead.
|
||||
- `USER_REVIEW.md` is created from `agent-ops/skills/common/code-review/templates/user-review-template.md`, filled with the archived loop history, current archived plan/review paths, verdict, loop count, blocking evidence, and user decision options.
|
||||
|
|
@ -153,6 +154,7 @@ Before writing the verdict:
|
|||
- Confirm the implementation marked the matching checklist items in the active review file, including the final mandatory `CODE_REVIEW-*-G??.md` completion item.
|
||||
- Treat blank placeholder sections, missing actual implementation notes, missing checklist completion, or missing actual stdout/stderr in the active review file as a completeness or verification-trust failure.
|
||||
- Read the `사용자 리뷰 요청` section. If `상태` is not `없음`, validate the exact decision needed, blocker evidence, command output or not-run reason, and resume condition before deciding whether the user-review gate triggers.
|
||||
- For split dependency blockers, verify predecessor completion against active and archived `complete.log` paths before accepting the blocker as user-review-worthy.
|
||||
- Grep renamed/removed symbols for stale references.
|
||||
- Confirm every required test exists, name matches, and assertions are meaningful.
|
||||
- Cross-check claimed verification output in the active review file against actual code and project commands.
|
||||
|
|
|
|||
|
|
@ -85,6 +85,15 @@ Task directory naming rules:
|
|||
- Example: split independent docs/UI plus an integration under one task group as `01_core`, `02+01_db`, `03+02_api`, `04_docs`, `05_ui`, `06+05_integration`; `01_core`, `04_docs`, and `05_ui` can start together, and `06+05_integration` waits only for `05_ui`.
|
||||
- Preserve task group and subtask directory names verbatim; do not normalize, reinterpret, or choose execution order by agent judgment.
|
||||
|
||||
Split predecessor completion lookup:
|
||||
|
||||
- For a dependent subtask directory `NN+PP[,QQ...]_{subtask_name}`, resolve each predecessor subtask before writing the plan.
|
||||
- Check predecessor completion in this order: `agent-task/{task_group}/{predecessor_subtask}/complete.log`, then `agent-task/archive/*/*/{task_group}/{predecessor_subtask}/complete.log`.
|
||||
- If the predecessor is found only in archive, treat it as complete and cite the actual archived `complete.log` path in `분석 결과` and `의존 관계 및 구현 순서`; do not require the predecessor to remain active.
|
||||
- If multiple archived exact-path candidates exist, choose the newest `YYYY/MM` path and record all candidates in `분석 결과`.
|
||||
- If no active or archived `complete.log` exists, treat the predecessor as incomplete and plan the predecessor first or ask for a routing decision. Do not invent marker files for downstream work.
|
||||
- The downstream implementation must verify the resolved predecessor path from the plan, not a hard-coded active-only path.
|
||||
|
||||
Routing rules:
|
||||
|
||||
- Build defaults to `local` when the plan is explicit, tests are runnable, and failure is review-detectable.
|
||||
|
|
@ -170,6 +179,7 @@ Complete all items below before creating any files. Work through them in order;
|
|||
- [ ] **Pre-check compile issues** — identify missing interface implementations, type mismatches, and broken imports.
|
||||
- [ ] **Verify verification commands** — confirm that the final verification commands actually run in this repository layout.
|
||||
- [ ] **Stabilize fragile verification** — for search or generated-output checks, choose deterministic commands up front, such as `rg --sort path`, and decide whether cached test output is acceptable or `-count=1` is required.
|
||||
- [ ] **Resolve split predecessors** — for every `NN+PP[,QQ...]_...` dependency, search active and archived `complete.log` paths using the split predecessor completion lookup rule and record the resolved path or missing state.
|
||||
|
||||
## Step 3 - Determine GXX Grade
|
||||
|
||||
|
|
@ -235,7 +245,7 @@ Each plan item must include:
|
|||
|
||||
Include `의존 관계 및 구현 순서` only when order matters.
|
||||
|
||||
For split multi-plan work, the `{subtask_dir}` directory name is the runtime source of truth. If a plan has a `NN+PP[,QQ...]_...` subtask directory name, `의존 관계 및 구현 순서` must echo the decoded predecessor subtask directories under the same task group that must produce `complete.log` before implementation starts, and it must not add dependencies that are absent from the directory name.
|
||||
For split multi-plan work, the `{subtask_dir}` directory name is the runtime source of truth. If a plan has a `NN+PP[,QQ...]_...` subtask directory name, `의존 관계 및 구현 순서` must echo the decoded predecessor subtask directories under the same task group and the resolved active or archived `complete.log` path for each predecessor. It must not add dependencies that are absent from the directory name.
|
||||
|
||||
Quality rules:
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@
|
|||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- 결정 필요: 없음
|
||||
- 상태: 잠금
|
||||
- 결정 필요:
|
||||
- valid signed payload를 제공해 `04+03_fcm_ack_smoke`를 끝까지 진행할지, 이번 loop는 FCM receipt + ACK까지로 축소하고 notification UI 계열은 후속으로 분리할지 결정한다.
|
||||
|
||||
## 범위
|
||||
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
|
||||
자동화하기 어려운 FCM과 서버 ACK 검증을 수동 체크로 분리한다.
|
||||
|
||||
- [ ] [fcm-manual] 실제 Firebase FCM delivery와 서버 ACK 검증 절차를 문서화한다.
|
||||
- [ ] [fcm-manual] 실제 Firebase FCM delivery와 서버 ACK 검증 절차를 문서화한다. 진행: `04+03_fcm_ack_smoke`에서 remote FCM receipt와 ACK POST evidence까지 확인했다. 남은 검증: valid signed payload, notification display, opened-routing, inline reply, dismiss.
|
||||
- [ ] [remote-guide] 로컬 Android SDK가 없을 때 원격 테스트 환경 문서가 client README와 연결되도록 유지한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
|
@ -69,8 +70,8 @@
|
|||
|
||||
- 관련 경로: `apps/flutter-test/`, `packages/messaging_flutter/`, `packages/messaging_flutter/docs/android-test-environment.md`
|
||||
- 표준선(선택): client는 실제 소비 앱 모양의 검증 호스트이며 별도 `/example`이나 `/sandbox`를 두지 않는다. Flutter SDK는 nexo-owned embedded SDK로 유지한다
|
||||
- 진행 메모: `thin-client`는 `apps/flutter-test/README.md`와 `apps/flutter-test/test/widget_test.dart` 기준을 맞췄다. `sdk-boundary`는 `packages/messaging_flutter/README.md`의 SDK ownership/test strategy 기준을 보강했다.
|
||||
- 활성 계획: `agent-task/m-client-validation/01_integration_path/`, `agent-task/m-client-validation/02_native_test/`
|
||||
- 진행 메모: `thin-client`는 `apps/flutter-test/README.md`와 `apps/flutter-test/test/widget_test.dart` 기준을 맞췄다. `sdk-boundary`는 `packages/messaging_flutter/README.md`의 SDK ownership/test strategy 기준을 보강했다. `04+03_fcm_ack_smoke`는 archived `03_smoke_preflight` 완료를 승계해 `apps/flutter-test`의 Firebase/plugin 초기화 누락을 보정했고, local/remote `flutter analyze`와 `flutter test` PASS, remote FCM token 저장, push-proxy send OK, device FCM receipt와 ACK POST까지 확인했다. valid signed payload가 없어 notification display/opened-routing/inline reply/dismiss evidence는 아직 BLOCKED다.
|
||||
- 활성 계획: `agent-task/m-client-validation/01_integration_path/`, `agent-task/m-client-validation/02_native_test/`, `agent-task/m-client-validation/04+03_fcm_ack_smoke/`
|
||||
- 선행 작업: client migration과 1차 테스트
|
||||
- 후속 작업: 메시징 계약 표준화, 알림 파이프라인 고도화
|
||||
- 확인 필요: 없음
|
||||
- 후속 작업: valid server-generated signed push trigger 확보 후 notification display, opened-routing, inline reply, dismiss evidence 수집. 이후 메시징 계약 표준화, 알림 파이프라인 고도화
|
||||
- 확인 필요: valid signed payload를 제공해 현재 smoke를 끝까지 진행할지, 이번 loop는 FCM receipt + ACK까지로 축소하고 notification UI 계열은 후속으로 분리할지 결정 필요
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- task=m-client-validation/04+03_fcm_ack_smoke plan=0 tag=SMOKE_RUN -->
|
||||
<!-- task=m-client-validation/04+03_fcm_ack_smoke plan=1 tag=REVIEW_SMOKE_RUN -->
|
||||
|
||||
# Code Review Reference - 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.
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
## 개요
|
||||
|
||||
date=2026-05-29
|
||||
task=m-client-validation/04+03_fcm_ack_smoke, plan=0, tag=SMOKE_RUN
|
||||
task=m-client-validation/04+03_fcm_ack_smoke, plan=1, tag=REVIEW_SMOKE_RUN
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ task=m-client-validation/04+03_fcm_ack_smoke, plan=0, tag=SMOKE_RUN
|
|||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
|
||||
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` 위치에서 체크한 뒤 보고한다.
|
||||
|
|
@ -34,16 +34,18 @@ task=m-client-validation/04+03_fcm_ack_smoke, plan=0, tag=SMOKE_RUN
|
|||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [SMOKE_RUN-1] Android test host 실행 | [ ] |
|
||||
| [SMOKE_RUN-2] FCM delivery와 ACK evidence 수집 | [ ] |
|
||||
| [SMOKE_RUN-3] Manual Smoke Checklist evidence 정리 | [ ] |
|
||||
| [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 정리 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [SMOKE_RUN-1] `03_smoke_preflight` 완료를 확인하고 Android target에서 `apps/flutter-test`를 실행한다.
|
||||
- [ ] [SMOKE_RUN-2] 실제 server push trigger를 실행하고 FCM receipt, signature handling, notification display, ACK response evidence를 수집한다.
|
||||
- [ ] [SMOKE_RUN-3] opened-routing, inline reply, dismiss, device-token prefix evidence를 redact해서 review stub에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
- [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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
|
|
@ -64,29 +66,56 @@ task=m-client-validation/04+03_fcm_ack_smoke, plan=0, tag=SMOKE_RUN
|
|||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
- 사용자 추가 지시("너가 일단 진행해봐")에 따라 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를 수집한다.
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `03_smoke_preflight/complete.log` 존재 확인이 실제로 수행되었는지 확인한다.
|
||||
- trigger/logcat/server evidence가 실제 출력 또는 파일 경로로 기록되었는지 확인한다.
|
||||
- secret/token/signing key/server endpoint 원문이 review stub에 남지 않았는지 확인한다.
|
||||
- manual checklist 항목이 PASS/BLOCKED/FAIL로 과장 없이 분류되었는지 확인한다.
|
||||
- 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 중 재검토 가능한 형태인지 본다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
|
|
@ -98,35 +127,110 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
|
|||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### SMOKE_RUN-1 중간 검증
|
||||
```
|
||||
$ test -f agent-task/m-client-validation/03_smoke_preflight/complete.log
|
||||
$ test -n "${NEXO_ANDROID_DEVICE_ID:-}"
|
||||
$ cd apps/flutter-test && flutter pub get
|
||||
$ adb -s "$NEXO_ANDROID_DEVICE_ID" logcat -c
|
||||
$ cd apps/flutter-test && flutter run -d "$NEXO_ANDROID_DEVICE_ID"
|
||||
(output)
|
||||
### REVIEW_SMOKE_RUN-1 중간 검증
|
||||
```
|
||||
$ test -f agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log
|
||||
exit=0
|
||||
|
||||
### SMOKE_RUN-2 중간 검증
|
||||
```
|
||||
$ test -n "${NEXO_SMOKE_TRIGGER_CMD:-}"
|
||||
$ bash -lc "$NEXO_SMOKE_TRIGGER_CMD" 2>&1 | tee /tmp/nexo-smoke-trigger.log
|
||||
$ adb -s "$NEXO_ANDROID_DEVICE_ID" logcat -d | rg "NexoFirebaseMessagingService|ReceiptDelivery|NotificationReplyBroadcastReceiver|NotificationDismissService|ACK|signature|opened|Navigate" | tee /tmp/nexo-smoke-logcat.txt
|
||||
(output)
|
||||
$ 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 충족.
|
||||
|
||||
### SMOKE_RUN-3 중간 검증
|
||||
### REVIEW_SMOKE_RUN-2 중간 검증
|
||||
```
|
||||
$ rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
(output)
|
||||
$ 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
|
||||
# 위 표/근거로 매칭됨.
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```
|
||||
$ rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
(output)
|
||||
$ 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 파일 유지.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- task=m-client-validation/04+03_fcm_ack_smoke plan=0 tag=SMOKE_RUN -->
|
||||
<!-- task=m-client-validation/04+03_fcm_ack_smoke plan=1 tag=REVIEW_SMOKE_RUN -->
|
||||
|
||||
# Plan - SMOKE_RUN: Real FCM And ACK Smoke
|
||||
# Plan - REVIEW_SMOKE_RUN: Resume Real FCM And ACK Smoke
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
## 배경
|
||||
|
||||
`manual-smoke` Epic의 실제 FCM delivery와 server ACK 확인은 headless repository test가 아니라 Android/Firebase/server runtime에 의존한다. `03_smoke_preflight`에서 환경 준비가 확인된 뒤에만 앱 실행, push trigger, logcat/server evidence를 수집한다. 결과는 active `CODE_REVIEW-cloud-G07.md`에 기록하고 roadmap은 수정하지 않는다.
|
||||
이전 review stop은 `03_smoke_preflight`를 active 경로에서만 찾아 생긴 false blocker였다. 선행 preflight는 archive에 PASS 완료되어 있고, 원격 Android host와 trigger env도 확인됐다. 이 계획은 archived preflight evidence를 승계해 실제 FCM delivery와 ACK smoke를 다시 진행한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
|
|
@ -18,20 +18,33 @@
|
|||
|
||||
### 읽은 파일
|
||||
|
||||
- `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`
|
||||
- `agent-ops/rules/project/domain/client-app/rules.md`
|
||||
- `agent-ops/rules/project/domain/messaging-flutter/rules.md`
|
||||
- `agent-ops/rules/private/testing-env.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 FCM delivery, notification display, inline reply, dismiss, and server ACK are intentionally outside deterministic Flutter tests. Evidence must come from device/emulator logs, app UI, screenshots/recordings, and server-side ACK/reply logs.
|
||||
- Existing integration tests cover method channel, event channel, and opened-routing via debug injection only. They do not prove Firebase delivery.
|
||||
- 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.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
|
|
@ -39,135 +52,199 @@
|
|||
|
||||
### 분할 판단
|
||||
|
||||
분할 정책을 먼저 평가했다. shared task group은 `m-client-validation`이다.
|
||||
Split decision policy was evaluated. This remains one dependent subtask under shared task group `m-client-validation`.
|
||||
|
||||
- `03_smoke_preflight`: Firebase/Android/server trigger readiness를 확인하는 선행 작업.
|
||||
- `04+03_fcm_ack_smoke`: 이 계획이며 `03_smoke_preflight`의 `complete.log`가 필요하다.
|
||||
- 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 수정은 기본 범위가 아니다. smoke 결과에서 제품 결함이 확인되면 이 plan에서 임의 수정하지 말고 review stub에 evidence를 남긴다.
|
||||
- tracked docs, roadmap, public README에 secret/token/signing key/server endpoint 원문을 쓰지 않는다.
|
||||
- 실제 smoke evidence는 review stub에 redact해서 기록하고, 민감한 원본 로그나 screenshot은 repository 밖 임시 경로에 둔다.
|
||||
- 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`. long-running `flutter run`, adb/logcat, external push trigger, manual evidence 해석이 성공 조건이라 terminal-agent work다.
|
||||
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.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [SMOKE_RUN-1] `03_smoke_preflight` 완료를 확인하고 Android target에서 `apps/flutter-test`를 실행한다.
|
||||
- [ ] [SMOKE_RUN-2] 실제 server push trigger를 실행하고 FCM receipt, signature handling, notification display, ACK response evidence를 수집한다.
|
||||
- [ ] [SMOKE_RUN-3] opened-routing, inline reply, dismiss, device-token prefix evidence를 redact해서 review stub에 기록한다.
|
||||
- [ ] [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` 완료에 의존한다. 시작 전 `agent-task/m-client-validation/03_smoke_preflight/complete.log`가 있어야 한다. 없으면 구현하지 말고 preflight 완료를 요청한다.
|
||||
이 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로 보지 않는다.
|
||||
|
||||
## [SMOKE_RUN-1] Android test host 실행
|
||||
## [REVIEW_SMOKE_RUN-1] Preflight와 remote readiness 승계
|
||||
|
||||
### 문제
|
||||
|
||||
`packages/messaging_flutter/README.md:251-288`의 manual smoke checklist는 실제 Android host app이 떠 있고 Firebase config가 준비된 상태를 전제로 한다. 앱이 실행되지 않으면 FCM receipt와 opened-routing evidence를 수집할 수 없다.
|
||||
`user_review_0.log`는 `03_smoke_preflight/complete.log` active 경로 부재를 blocker로 기록했다. 실제 완료 산출물은 `agent-task/archive/2026/05/m-client-validation/03_smoke_preflight/complete.log`에 있다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
preflight 완료를 확인하고, `NEXO_ANDROID_DEVICE_ID`로 지정된 device/emulator에서 `apps/flutter-test`를 실행한다. long-running command 출력은 review stub에 요약하지 말고 실제 출력 또는 저장 파일 경로를 기록한다.
|
||||
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/m-client-validation/03_smoke_preflight/complete.log` 존재 확인.
|
||||
- [ ] `apps/flutter-test` dependency 준비.
|
||||
- [ ] 지정 Android target에서 앱 실행.
|
||||
- [ ] `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 없이 기록.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
실제 device smoke라 테스트 파일은 추가하지 않는다.
|
||||
테스트 파일 추가 없음. 이 항목은 remote smoke readiness 검증이다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
test -f agent-task/m-client-validation/03_smoke_preflight/complete.log
|
||||
test -n "${NEXO_ANDROID_DEVICE_ID:-}"
|
||||
cd apps/flutter-test && flutter pub get
|
||||
adb -s "$NEXO_ANDROID_DEVICE_ID" logcat -c
|
||||
cd apps/flutter-test && flutter run -d "$NEXO_ANDROID_DEVICE_ID"
|
||||
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}"
|
||||
'"'"'
|
||||
```
|
||||
|
||||
기대 결과: app이 지정 device/emulator에서 실행되고 logcat buffer가 clear된 상태로 smoke를 시작할 수 있다.
|
||||
기대 결과: 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.
|
||||
|
||||
## [SMOKE_RUN-2] FCM delivery와 ACK evidence 수집
|
||||
## [REVIEW_SMOKE_RUN-2] Android app 실행과 FCM token evidence
|
||||
|
||||
### 문제
|
||||
|
||||
repository test injection path는 true Firebase delivery와 server ACK를 증명하지 못한다. 실제 server push trigger와 device logcat, server-side ACK/reply log를 함께 확인해야 한다.
|
||||
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`.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
private 환경에서 제공된 `NEXO_SMOKE_TRIGGER_CMD`를 실행해 실제 push를 보낸다. command 자체나 출력에 secret이 포함되면 review stub에는 redacted 형태만 기록한다. device logcat에서는 plugin/service 관련 line만 추출한다.
|
||||
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`로 사용한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `NEXO_SMOKE_TRIGGER_CMD` 실행 결과 저장.
|
||||
- [ ] `NexoFirebaseMessagingService`, signature handling, ACK 관련 logcat evidence 저장.
|
||||
- [ ] server-side ACK/reply status evidence를 redacted 형태로 기록.
|
||||
- [ ] remote app dependency 준비.
|
||||
- [ ] logcat buffer clear.
|
||||
- [ ] app 실행 output 저장.
|
||||
- [ ] `android_rn-v2:` token prefix evidence 기록.
|
||||
- [ ] raw token은 review stub에 출력하지 않고 redacted 처리.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
외부 smoke evidence 수집 작업이라 테스트 파일은 추가하지 않는다.
|
||||
테스트 파일 추가 없음. 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
|
||||
adb -s "$NEXO_ANDROID_DEVICE_ID" logcat -d | rg "NexoFirebaseMessagingService|ReceiptDelivery|NotificationReplyBroadcastReceiver|NotificationDismissService|ACK|signature|opened|Navigate" | tee /tmp/nexo-smoke-logcat.txt
|
||||
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 command가 성공하고 logcat extract에 receipt/signature/ACK 관련 evidence가 포함된다.
|
||||
기대 결과: 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로 기록한다.
|
||||
|
||||
## [SMOKE_RUN-3] Manual Smoke Checklist evidence 정리
|
||||
## [REVIEW_SMOKE_RUN-4] Manual smoke checklist evidence 정리
|
||||
|
||||
### 문제
|
||||
|
||||
`packages/messaging_flutter/README.md:275-288`의 checklist는 FCM, notification, ACK, opened-routing, inline reply, dismiss, token prefix를 모두 확인해야 한다. 일부만 확인하고 PASS로 처리하면 manual-smoke Epic이 과하게 완료된 것으로 보일 수 있다.
|
||||
`packages/messaging_flutter/README.md`의 manual smoke checklist는 FCM, notification display, ACK, opened-routing, inline reply, dismiss, token prefix를 모두 분류해야 한다. 일부만 확인하고 PASS로 처리하면 manual-smoke Epic이 과하게 완료된다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
각 checklist 항목을 review stub의 `주요 설계 결정` 또는 `검증 결과`에 PASS/BLOCKED/FAIL로 기록한다. secret이 포함된 원본 파일은 repository 밖 임시 경로만 남기고 tracked file에는 redact된 설명만 남긴다.
|
||||
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로 분류하고 사유 기록.
|
||||
- [ ] 미확인 항목은 PASS/BLOCKED/FAIL로 분류하고 사유 기록.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
manual evidence 정리 작업이라 테스트 파일은 추가하지 않는다.
|
||||
테스트 파일 추가 없음. Manual evidence 정리 작업이다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token" agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md
|
||||
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에 checklist evidence가 항목별로 기록된다.
|
||||
기대 결과: review stub에 manual smoke checklist evidence가 항목별로 기록된다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
| --- | --- |
|
||||
| `agent-task/m-client-validation/04+03_fcm_ack_smoke/CODE_REVIEW-cloud-G07.md` | SMOKE_RUN-1, SMOKE_RUN-2, SMOKE_RUN-3 |
|
||||
| `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
|
||||
rg --sort path -n "FCM payload wakes|ACK request|onNotificationOpened|onNavigateToChannel|onNavigateToThread|Inline reply|Dismiss action|Device token" 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
|
||||
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
|
||||
```
|
||||
|
||||
기대 결과: 실제 smoke checklist 항목의 PASS/BLOCKED/FAIL evidence가 review stub에 있다.
|
||||
기대 결과: archived preflight dependency가 확인되고, review stub에 actual smoke checklist 항목의 PASS/BLOCKED/FAIL evidence가 있다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ void main() {
|
|||
testWidgets('native debug event reaches harness routing UI (channel)', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const MyApp(initializePlugin: false));
|
||||
|
||||
NexoMessagingPlugin.instance.listenNativeChannelForTesting();
|
||||
await NexoMessagingPlugin.instance.debugSendNativeEventForTesting(const {
|
||||
|
|
@ -54,7 +54,7 @@ void main() {
|
|||
testWidgets('native debug event reaches harness routing UI (thread/CRT)', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const MyApp(initializePlugin: false));
|
||||
|
||||
NexoMessagingPlugin.instance.listenNativeChannelForTesting();
|
||||
await NexoMessagingPlugin.instance.debugSendNativeEventForTesting(const {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nexo_messaging/nexo_messaging.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp();
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
const MyApp({super.key, this.initializePlugin = true});
|
||||
|
||||
final bool initializePlugin;
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
|
|
@ -46,6 +51,18 @@ class _MyAppState extends State<MyApp> {
|
|||
if (!mounted) return;
|
||||
setState(() => _lastOpened = _formatOpened(event));
|
||||
});
|
||||
if (widget.initializePlugin) {
|
||||
unawaited(_initializePlugin(plugin));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initializePlugin(NexoMessagingPlugin plugin) async {
|
||||
try {
|
||||
await plugin.initialize();
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint('[NexoClient] Failed to initialize messaging plugin: $error');
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ packages:
|
|||
source: hosted
|
||||
version: "7.0.1"
|
||||
firebase_core:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ dependencies:
|
|||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
firebase_core: ^3.12.1
|
||||
nexo_messaging:
|
||||
path: ../../packages/messaging_flutter
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:nexo_client/main.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets('renders initial harness state', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const MyApp(initializePlugin: false));
|
||||
|
||||
expect(find.text('Device token: pending'), findsOneWidget);
|
||||
expect(find.text('Last notification: none'), findsOneWidget);
|
||||
|
|
@ -30,7 +30,7 @@ void main() {
|
|||
testWidgets('updates opened and navigation state from plugin events (channel)', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const MyApp(initializePlugin: false));
|
||||
|
||||
NexoMessagingPlugin.instance.handleNativeEvent(const {
|
||||
'type': PushNotificationType.opened,
|
||||
|
|
@ -53,7 +53,9 @@ void main() {
|
|||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.text('Last navigation: channel:https://nexo.example.com/channel-123'),
|
||||
find.text(
|
||||
'Last navigation: channel:https://nexo.example.com/channel-123',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
|
|
@ -63,7 +65,7 @@ void main() {
|
|||
testWidgets(
|
||||
'updates opened and navigation state from plugin events (thread/CRT)',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const MyApp(initializePlugin: false));
|
||||
|
||||
NexoMessagingPlugin.instance.handleNativeEvent(const {
|
||||
'type': PushNotificationType.opened,
|
||||
|
|
@ -87,7 +89,9 @@ void main() {
|
|||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.text('Last navigation: thread:https://nexo.example.com/thread-456'),
|
||||
find.text(
|
||||
'Last navigation: thread:https://nexo.example.com/thread-456',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
|
|
@ -98,7 +102,7 @@ void main() {
|
|||
testWidgets('updates device token from plugin callback', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(const MyApp(initializePlugin: false));
|
||||
|
||||
NexoMessagingPlugin.instance.onDeviceTokenReady?.call(
|
||||
'test-device-token-123',
|
||||
|
|
|
|||
Loading…
Reference in a new issue