refactor: NexoMessagingPluginTest unit tests - debug APIs, intentPayload
- Add comprehensive unit tests for debugSendNativeEvent, saveDeviceToken, setAuthToken, setSigningKey, clearAuthToken methods - Add unit tests for intentPayload helper method with mocked Intent/Bundle - Add test for unknown method returning notImplemented - Replace placeholder comment with actual test implementations - Use Mockito to mock android.jar stubs (Intent, Bundle) for gradle unit tests
This commit is contained in:
parent
32104c1120
commit
7064a09a2c
3 changed files with 234 additions and 365 deletions
|
|
@ -1,148 +0,0 @@
|
|||
<!-- task=m-client-validation/03+01,02_android_native_validation plan=0 tag=ANDROID -->
|
||||
|
||||
# Code Review Reference - ANDROID
|
||||
|
||||
> **[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-28
|
||||
task=m-client-validation/03+01,02_android_native_validation, plan=0, tag=ANDROID
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_0.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-client-validation/03+01,02_android_native_validation/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. PASS이고 task group이 `m-client-validation`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [ANDROID-1] Native unit test 계약 보강 | [ ] |
|
||||
| [ANDROID-2] Local Android preflight 기록 | [ ] |
|
||||
| [ANDROID-3] Remote/device verification handoff | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [ANDROID-1] `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`를 plugin method-call/intent payload 계약 중심으로 보강한다.
|
||||
- [ ] [ANDROID-2] local Android preflight를 실행하고 `./gradlew testDebugUnitTest`의 실제 통과 또는 SDK blocker 출력을 기록한다.
|
||||
- [ ] [ANDROID-3] remote Android guide를 사용 가능한 경우 integration/native checks를 실행하고, 불가능하면 review stub의 사용자 리뷰 요청에 필요한 외부 준비 사항을 정확히 남긴다.
|
||||
- [ ] `flutter doctor -v`와 `command -v adb`/`command -v emulator`/`command -v sdkmanager` 결과를 기록한다.
|
||||
- [ ] `./gradlew testDebugUnitTest`를 `apps/flutter-test/android`에서 실행하고 실제 stdout/stderr를 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_0.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리 `agent-task/m-client-validation/03+01,02_android_native_validation/`를 `agent-task/archive/YYYY/MM/m-client-validation/03+01,02_android_native_validation/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-client-validation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-client-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- predecessor `01_docs_boundary`와 `02+01_flutter_contract_tests`가 complete인지 확인한다.
|
||||
- native tests가 template `getPlatformVersion` 수준을 넘어 plugin contract를 검증하는지 확인한다.
|
||||
- Android SDK/device blocker를 PASS처럼 처리하지 않았는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### ANDROID-1 중간 검증
|
||||
|
||||
```sh
|
||||
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
### ANDROID-2 중간 검증
|
||||
|
||||
```sh
|
||||
$ command -v adb
|
||||
$ command -v emulator
|
||||
$ command -v sdkmanager
|
||||
$ flutter doctor -v
|
||||
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
### ANDROID-3 중간 검증
|
||||
|
||||
```sh
|
||||
$ cd apps/flutter-test && flutter test integration_test -d emulator-5554
|
||||
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```sh
|
||||
$ command -v adb
|
||||
$ command -v emulator
|
||||
$ command -v sdkmanager
|
||||
$ flutter doctor -v
|
||||
$ cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
$ cd apps/flutter-test && flutter test integration_test -d emulator-5554
|
||||
$ git diff --check -- packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt packages/messaging_flutter/android/build.gradle.kts packages/messaging_flutter/docs/android-test-environment.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[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.
|
||||
|
||||
Sections and their ownership:
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| 구현 항목별 완료 여부 | Fixed at stub creation | Implementing agent checks `[ ]` -> `[x]` only |
|
||||
| 구현 체크리스트 | Fixed at stub creation from plan | Implementing agent checks `[ ]` -> `[x]` only |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 | Fixed headings and commands | Implementing agent fills command output only |
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
<!-- task=m-client-validation/03+01,02_android_native_validation plan=0 tag=ANDROID -->
|
||||
|
||||
# Plan - ANDROID
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 명령 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 스킬 전용이다. 사용자만 결정할 수 있는 사항, 외부 환경 준비, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 사항과 근거를 채우고 멈춘다.
|
||||
|
||||
## 배경
|
||||
|
||||
Milestone은 Android native unit test와 remote Android verification path를 기준선으로 요구한다. 현재 local 환경은 Android SDK가 없어 `./gradlew testDebugUnitTest`가 `SDK location not found`로 실패하고, integration test도 Android device/emulator가 없어 실패한다. 이 subtask는 native test 내용을 템플릿성 확인에서 실제 plugin contract로 키우고, local/remote verification을 정직하게 기록한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 blocker는 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 섹션 형식은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`와 같으며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `apps/flutter-test/android/local.properties`
|
||||
- `apps/flutter-test/android/settings.gradle.kts`
|
||||
- `apps/flutter-test/android/build.gradle.kts`
|
||||
- `apps/flutter-test/android/app/build.gradle.kts`
|
||||
- `apps/flutter-test/android/app/src/main/AndroidManifest.xml`
|
||||
- `packages/messaging_flutter/android/build.gradle.kts`
|
||||
- `packages/messaging_flutter/android/src/main/AndroidManifest.xml`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/PushNotificationEvents.kt`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/LaunchIntentHelper.kt`
|
||||
- `packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoFirebaseMessagingService.kt`
|
||||
- `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`
|
||||
- `packages/messaging_flutter/docs/android-test-environment.md`
|
||||
- `apps/flutter-test/README.md`
|
||||
- `packages/messaging_flutter/README.md`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `NexoMessagingPlugin.onMethodCall`: current native test only checks `getPlatformVersion`; invalid argument paths and debug event method are not covered.
|
||||
- `intentPayload`: no native unit test currently verifies extras -> opened/userInteraction payload mapping.
|
||||
- Local Android verification: blocked because local `apps/flutter-test/android/local.properties` has `flutter.sdk=/sdk/flutter` but no `sdk.dir`, and `ANDROID_HOME` is unavailable.
|
||||
- Remote Android verification: documented but not executed in this local session.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- 변경/삭제할 심볼 없음.
|
||||
- Relevant references checked with `rg --sort path -n "debugSendNativeEvent|intentPayload|testDebugUnitTest|ANDROID_HOME|sdk.dir" apps/flutter-test packages/messaging_flutter`.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split decision policy를 plan 파일 선택 전에 평가했다.
|
||||
- shared task group: `agent-task/m-client-validation`.
|
||||
- predecessor dependencies from directory name: `01_docs_boundary` and `02+01_flutter_contract_tests`.
|
||||
- this subtask: `03+01,02_android_native_validation`, waits for both because it relies on finalized doc language and Flutter contract tests.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- No remote credentials or host-specific tokens are added to tracked docs.
|
||||
- No Android SDK is installed into the repository.
|
||||
- No public package rename, manifest identifier rename, or generated Flutter platform file regeneration is allowed.
|
||||
- FCM real delivery remains manual smoke; this subtask may document/run it only when external infrastructure is already prepared.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- build=`cloud-G07`, review=`cloud-G07`. Success depends on terminal/Gradle workflow, environment diagnosis, Android SDK/device availability, and possibly remote execution.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
- Wait for `agent-task/m-client-validation/01_docs_boundary/complete.log`.
|
||||
- Wait for `agent-task/m-client-validation/02+01_flutter_contract_tests/complete.log`.
|
||||
- Do not add dependencies beyond the `03+01,02_...` directory name.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [ANDROID-1] `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`를 plugin method-call/intent payload 계약 중심으로 보강한다.
|
||||
- [ ] [ANDROID-2] local Android preflight를 실행하고 `./gradlew testDebugUnitTest`의 실제 통과 또는 SDK blocker 출력을 기록한다.
|
||||
- [ ] [ANDROID-3] remote Android guide를 사용 가능한 경우 integration/native checks를 실행하고, 불가능하면 review stub의 사용자 리뷰 요청에 필요한 외부 준비 사항을 정확히 남긴다.
|
||||
- [ ] `flutter doctor -v`와 `command -v adb`/`command -v emulator`/`command -v sdkmanager` 결과를 기록한다.
|
||||
- [ ] `./gradlew testDebugUnitTest`를 `apps/flutter-test/android`에서 실행하고 실제 stdout/stderr를 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [ANDROID-1] Native unit test 계약 보강
|
||||
|
||||
#### 문제
|
||||
|
||||
[packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt](/config/workspace/nexo/packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt:8)는 template comment와 `getPlatformVersion` 단일 테스트만 갖고 있다. [packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt](/config/workspace/nexo/packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt:139)의 `debugSendNativeEvent` invalid argument path와 [packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt](/config/workspace/nexo/packages/messaging_flutter/android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPlugin.kt:122)의 `intentPayload` mapping은 native test로 고정되어 있지 않다.
|
||||
|
||||
Before:
|
||||
|
||||
```kotlin
|
||||
16 internal class NexoMessagingPluginTest {
|
||||
17 @Test
|
||||
18 fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
기존 template comment를 제거하고, 최소한 다음 native tests를 추가한다.
|
||||
|
||||
- `onMethodCall_debugSendNativeEvent_withInvalidPayload_returnsInvalidArg`
|
||||
- `onMethodCall_saveDeviceToken_withoutToken_returnsInvalidArg`
|
||||
- 가능하면 Android SDK/Robolectric 없이 가능한 범위에서 `intentPayload` mapping test. 필요하면 implementation blocker로 남기고 문서화한다.
|
||||
|
||||
새 dependency는 추가하지 않는 것을 기본으로 한다. Robolectric이 꼭 필요하면 `packages/messaging_flutter/android/build.gradle.kts`의 `testImplementation` 추가 여부와 이유를 review stub에 기록한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt`
|
||||
- [ ] 필요 시 `packages/messaging_flutter/android/build.gradle.kts`
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
Native unit test를 직접 추가한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```sh
|
||||
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
### [ANDROID-2] Local Android preflight 기록
|
||||
|
||||
#### 문제
|
||||
|
||||
현재 local `./gradlew testDebugUnitTest`는 Android SDK path가 없어 실패했다. [apps/flutter-test/android/local.properties](/config/workspace/nexo/apps/flutter-test/android/local.properties:1)는 `flutter.sdk`만 갖고 있고 `sdk.dir`이 없다.
|
||||
|
||||
Observed:
|
||||
|
||||
```text
|
||||
SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable or by setting the sdk.dir path in your project's local properties file
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
구현자는 local 환경을 repo에 설치하지 않는다. `command -v adb`, `command -v emulator`, `command -v sdkmanager`, `flutter doctor -v`, `./gradlew testDebugUnitTest`를 실행하고 실제 출력을 review stub에 기록한다. `ANDROID_HOME`이 준비되어 있으면 Gradle test를 통과시킨다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] 소스 파일 변경 없음. 검증 결과 기록만 수행한다.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
테스트 작성 없음. 환경 preflight다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```sh
|
||||
command -v adb
|
||||
command -v emulator
|
||||
command -v sdkmanager
|
||||
flutter doctor -v
|
||||
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
### [ANDROID-3] Remote/device verification handoff
|
||||
|
||||
#### 문제
|
||||
|
||||
[packages/messaging_flutter/docs/android-test-environment.md](/config/workspace/nexo/packages/messaging_flutter/docs/android-test-environment.md:73)는 remote check path를 제공하지만 실제 실행은 외부 SSH/device 조건이 필요하다.
|
||||
|
||||
Before:
|
||||
|
||||
```markdown
|
||||
73 ## Run The Checks
|
||||
77 cd "$HOME/tmp/nexo-codex-test/packages/messaging_flutter"
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
remote host 접근이 가능하면 guide의 sync/emulator/check commands를 그대로 실행한다. 접근이 불가능하면 `사용자 리뷰 요청`에 필요한 준비 사항을 남긴다: Android SDK 또는 remote SSH availability, emulator/device, Firebase config 여부.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] 필요 시 `packages/messaging_flutter/docs/android-test-environment.md`의 command drift만 수정한다.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
새 테스트 없음. 외부 환경 검증이다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```sh
|
||||
cd apps/flutter-test && flutter test integration_test -d emulator-5554
|
||||
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
| --- | --- |
|
||||
| `packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt` | ANDROID-1 |
|
||||
| `packages/messaging_flutter/android/build.gradle.kts` | ANDROID-1, only if a test dependency is justified |
|
||||
| `packages/messaging_flutter/docs/android-test-environment.md` | ANDROID-3, only if command drift is found |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```sh
|
||||
command -v adb
|
||||
command -v emulator
|
||||
command -v sdkmanager
|
||||
flutter doctor -v
|
||||
cd apps/flutter-test/android && ./gradlew testDebugUnitTest
|
||||
cd apps/flutter-test && flutter test integration_test -d emulator-5554
|
||||
git diff --check -- packages/messaging_flutter/android/src/test/kotlin/com/tokilabs/nexo/messaging/NexoMessagingPluginTest.kt packages/messaging_flutter/android/build.gradle.kts packages/messaging_flutter/docs/android-test-environment.md
|
||||
```
|
||||
|
||||
Local Android SDK/device 부재는 PASS가 아니라 environment blocker다. blocker가 남으면 `사용자 리뷰 요청`을 채우고 code-review가 stop 여부를 판단하게 한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
package com.tokilabs.nexo.messaging
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import org.mockito.Mockito
|
||||
import kotlin.test.Test
|
||||
|
||||
/*
|
||||
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
|
||||
*
|
||||
* Once you have built the workspace client app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `apps/flutter-test/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
*/
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class NexoMessagingPluginTest {
|
||||
@Test
|
||||
|
|
@ -24,4 +22,232 @@ internal class NexoMessagingPluginTest {
|
|||
|
||||
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
|
||||
}
|
||||
|
||||
// ---- debugSendNativeEvent ----
|
||||
|
||||
@Test
|
||||
fun onMethodCall_debugSendNativeEvent_withNullPayload_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("debugSendNativeEvent", null)
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("event payload is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMethodCall_debugSendNativeEvent_withNonMapPayload_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
// Pass a string instead of a Map – cast to Map<*, *> will yield null
|
||||
val call = MethodCall("debugSendNativeEvent", "not a map")
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("event payload is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
// ---- saveDeviceToken ----
|
||||
|
||||
@Test
|
||||
fun onMethodCall_saveDeviceToken_withoutToken_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("saveDeviceToken", mapOf("other" to "value"))
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("token is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMethodCall_saveDeviceToken_withNullArguments_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("saveDeviceToken", null)
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("token is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
// ---- setAuthToken ----
|
||||
|
||||
@Test
|
||||
fun onMethodCall_setAuthToken_withoutServerUrl_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("setAuthToken", mapOf("token" to "abc"))
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("serverUrl or token is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMethodCall_setAuthToken_withoutToken_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("setAuthToken", mapOf("serverUrl" to "https://example.com"))
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("serverUrl or token is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
// ---- setSigningKey ----
|
||||
|
||||
@Test
|
||||
fun onMethodCall_setSigningKey_withoutServerUrl_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("setSigningKey", mapOf("signingKey" to "key123"))
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("serverUrl or signingKey is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMethodCall_setSigningKey_withoutSigningKey_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("setSigningKey", mapOf("serverUrl" to "https://example.com"))
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("serverUrl or signingKey is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
// ---- clearAuthToken ----
|
||||
|
||||
@Test
|
||||
fun onMethodCall_clearAuthToken_withoutServerUrl_returnsInvalidArg() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("clearAuthToken", null)
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).error(
|
||||
Mockito.eq("INVALID_ARG"),
|
||||
Mockito.eq("serverUrl is null"),
|
||||
Mockito.isNull()
|
||||
)
|
||||
}
|
||||
|
||||
// ---- unknown method ----
|
||||
|
||||
@Test
|
||||
fun onMethodCall_unknownMethod_returnsNotImplemented() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
|
||||
val call = MethodCall("nonExistentMethod", null)
|
||||
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
|
||||
plugin.onMethodCall(call, mockResult)
|
||||
|
||||
Mockito.verify(mockResult).notImplemented()
|
||||
}
|
||||
|
||||
// ---- intentPayload ----
|
||||
//
|
||||
// Gradle unit tests run against the android.jar stub, whose Intent/Bundle
|
||||
// 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)
|
||||
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) }
|
||||
Mockito.`when`(intent.extras).thenReturn(bundle)
|
||||
return intent
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withNullIntent_returnsNull() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
assertNull(plugin.intentPayload(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withNullExtras_returnsNull() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val intent = Mockito.mock(Intent::class.java)
|
||||
Mockito.`when`(intent.extras).thenReturn(null)
|
||||
assertNull(plugin.intentPayload(intent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withEmptyExtras_returnsNull() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val intent = mockIntent(emptyMap())
|
||||
assertNull(plugin.intentPayload(intent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withExtras_returnsDataWithTypeAndUserInteraction() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val intent = mockIntent(mapOf("channel_id" to "ch-123", "post_id" to "post-456"))
|
||||
val result = plugin.intentPayload(intent)
|
||||
assertNotNull(result)
|
||||
assertEquals("ch-123", result["channel_id"])
|
||||
assertEquals("post-456", result["post_id"])
|
||||
// default type when not specified in extras
|
||||
assertEquals("opened", result["type"])
|
||||
assertEquals(true, result["userInteraction"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_withExplicitType_preservesType() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val intent = mockIntent(mapOf("type" to "custom_event", "data" to "value"))
|
||||
val result = plugin.intentPayload(intent)
|
||||
assertNotNull(result)
|
||||
assertEquals("custom_event", result["type"])
|
||||
assertEquals(true, result["userInteraction"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intentPayload_alwaysSetsUserInteractionTrue() {
|
||||
val plugin = NexoMessagingPlugin()
|
||||
val intent = mockIntent(mapOf("key" to "val"))
|
||||
val result = plugin.intentPayload(intent)
|
||||
assertNotNull(result)
|
||||
assertTrue(result["userInteraction"] as Boolean)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue