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:
toki 2026-05-28 21:06:04 +09:00
parent 32104c1120
commit 7064a09a2c
3 changed files with 234 additions and 365 deletions

View file

@ -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 |

View file

@ -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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -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)
}
}