chore: archive task files and update RunnerTests
This commit is contained in:
parent
68ccfb13fd
commit
f8f7c37eec
6 changed files with 453 additions and 24 deletions
|
|
@ -41,43 +41,51 @@ task=m-ios-notification-preflight, plan=0, tag=IOS_BRIDGE
|
|||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [IOS_BRIDGE-1] iOS action/event channel registration | [ ] |
|
||||
| [IOS_BRIDGE-2] Minimum iOS method contract and no-op boundaries | [ ] |
|
||||
| [IOS_BRIDGE-3] iOS bridge tests and docs alignment | [ ] |
|
||||
| [IOS_BRIDGE-1] iOS action/event channel registration | [x] |
|
||||
| [IOS_BRIDGE-2] Minimum iOS method contract and no-op boundaries | [x] |
|
||||
| [IOS_BRIDGE-3] iOS bridge tests and docs alignment | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift`에 Dart가 기대하는 action/event channel을 등록하고 기존 `getPlatformVersion` 호환 channel을 유지한다.
|
||||
- [ ] iOS bridge의 최소 method set(`debugSendNativeEvent`, `saveDeviceToken`, `getDeviceToken`, `setAuthToken`, `clearAuthToken`, `setSigningKey`, `getPlatformVersion`)을 구현하고 secret 저장이 필요한 기능은 validation-only no-op 경계로 둔다.
|
||||
- [ ] `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`에 iOS bridge unit tests를 추가하고 `docs/ios-notification-test-guide.md`의 bridge gap 문구를 구현 상태와 남은 범위에 맞게 갱신한다.
|
||||
- [ ] remote Flutter/Xcode 검증과 deterministic structural checks를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
- [x] `packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift`에 Dart가 기대하는 action/event channel을 등록하고 기존 `getPlatformVersion` 호환 channel을 유지한다.
|
||||
- [x] iOS bridge의 최소 method set(`debugSendNativeEvent`, `saveDeviceToken`, `getDeviceToken`, `setAuthToken`, `clearAuthToken`, `setSigningKey`, `getPlatformVersion`)을 구현하고 secret 저장이 필요한 기능은 validation-only no-op 경계로 둔다.
|
||||
- [x] `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`에 iOS bridge unit tests를 추가하고 `docs/ios-notification-test-guide.md`의 bridge gap 문구를 구현 상태와 남은 범위에 맞게 갱신한다.
|
||||
- [x] remote Flutter/Xcode 검증과 deterministic structural checks를 실행한다.
|
||||
- [x] 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`로 아카이브한다.
|
||||
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_0.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리 `agent-task/m-ios-notification-preflight/`를 `agent-task/archive/YYYY/MM/m-ios-notification-preflight/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-ios-notification-preflight/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
- [x] 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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
코드 구현은 계획대로 진행했다. 검증 명령은 remote 환경 제약 때문에 다음과 같이 대체했고, 의미(동일 대상/동일 검증)는 유지했다.
|
||||
|
||||
1. **Remote 작업 디렉터리 및 PATH 대체.** 계획의 최종 검증은 `ssh toki@toki-labs.com 'cd ~/works/nexo/... && flutter ...'`를 고정했으나, 비로그인 ssh 셸 PATH에 `flutter`/`pod`이 없어 `command not found: flutter`로 차단됐다(아래 검증 결과 첫 시도 출력). `agent-test/local/rules.md`와 `packages/messaging_flutter/docs/android-test-environment.md` 절차에 따라 미커밋 작업 트리를 `tar | ssh ... tar -xzf -`로 `$HOME/tmp/nexo-codex-test`에 동기화한 뒤, 각 명령 앞에 `export PATH="$HOME/SDK/flutter/bin:$HOME/Library/Android/sdk/...:/usr/local/bin:$PATH"`를 붙여 실행했다. 검증 대상 코드/명령 자체는 동일하다.
|
||||
2. **Sync 후 머신별 파일 교정.** sync는 머신별 gitignore 파일을 덮으므로 원격 `apps/flutter-test/android/local.properties`를 원격 SDK 경로(`sdk.dir=$HOME/Library/Android/sdk`, `flutter.sdk=$HOME/SDK/flutter`)로 다시 기록했다. 레포 추적 파일은 변경하지 않았다.
|
||||
3. **iOS pod install 선행.** 계획의 `xcodebuild test` 전에 원격에 CocoaPods가 PATH에 없었다(`/usr/local/bin/pod`). `export PATH="/usr/local/bin:..."` 후 `pod install`을 1회 실행해 Flutter/nexo_messaging pod을 통합했다(Firebase는 Flutter SPM 통합). 이는 계획이 전제한 빌드 준비 단계이며 검증 의미를 바꾸지 않는다.
|
||||
4. **xcodebuild destination 대체(`name` → `id`).** 계획 명령은 `-destination "platform=iOS Simulator,name=iPhone 15"`이나, 이 호스트에는 동일 이름 `iPhone 15` 시뮬레이터가 OS 17.2/18.0/18.3.1 3개라 name 기반 해석이 `Unable to find a device matching the provided destination specifier`로 실패했다(검증 결과에 실제 출력 기록). 임의의 다른 기종으로 바꾸지 않고 동일 기종 iPhone 15(OS 18.3.1)의 device id `DD93CB8A-4E3A-45B8-8994-DCE6EFE43CFC`를 지정해 모호성만 제거했다. 결과는 `** TEST SUCCEEDED **`, RunnerTests 10개 전부 통과.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
- **Android reference와의 계약 일치.** iOS `handle` switch는 Android `NexoMessagingPlugin.kt`의 method 이름, 인자 키(`token`, `serverUrl`, `signingKey`), 에러 코드(`INVALID_ARG`)를 그대로 따라 Dart 호출부(`nexo_messaging_plugin.dart`)가 플랫폼별 분기 없이 동작하도록 했다.
|
||||
- **No-op 경계로 secret 미저장.** `setAuthToken`/`clearAuthToken`/`setSigningKey`는 인자 validation만 하고 success(`nil`)를 반환하는 validation-only no-op으로 뒀다. APNs/Firebase 실연동과 secret 저장은 후속 `iOS 알림 테스트` 범위이므로, 이 plan에서는 auth token/signing key를 iOS에 영구 저장하지 않는다. `saveDeviceToken`/`getDeviceToken`만 formatted token string을 `UserDefaults`에 저장/반환한다.
|
||||
- **FlutterStreamHandler로 event channel 구현.** `debugSendNativeEvent`가 `onListen`에서 받은 `eventSink`로 payload를 전달하도록 해, Dart `EventChannel(notifications)` listener가 debug/local routing smoke에서 native 이벤트를 받을 수 있게 했다. legacy `nexo_messaging` MethodChannel은 기존 `getPlatformVersion` 테스트/호환을 위해 유지했다.
|
||||
- **XCTest 결정성/무자격 유지.** RunnerTests는 `UserDefaults` 키를 `tearDown`에서 정리하고 credential 없이 `handle`을 직접 호출해, APNs/FCM 실자산 없이도 통과하도록 했다. iOS XCTest는 Firebase plist 없이도 RunnerTests 번들에서 통과함을 확인했다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
|
|
@ -113,40 +121,91 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
|
|||
### IOS_BRIDGE-1 중간 검증
|
||||
```sh
|
||||
$ rg --sort path -n "com\\.tokilabs\\.nexo\\.messaging/(notifications|notification_actions)|nexo_messaging" packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart
|
||||
(output)
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:5: static let legacy = "nexo_messaging"
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:6: static let events = "com.tokilabs.nexo.messaging/notifications"
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:7: static let actions = "com.tokilabs.nexo.messaging/notification_actions"
|
||||
packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart:11: 'com.tokilabs.nexo.messaging/notifications';
|
||||
packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart:13: 'com.tokilabs.nexo.messaging/notification_actions';
|
||||
```
|
||||
기대 결과대로 Swift와 Dart가 동일한 event/action channel name을 포함하고, Swift는 legacy `nexo_messaging`도 유지한다.
|
||||
|
||||
### IOS_BRIDGE-2 중간 검증
|
||||
```sh
|
||||
$ git diff --check -- packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift
|
||||
(output)
|
||||
(출력 없음 — whitespace error 없음, exit 0)
|
||||
```
|
||||
|
||||
### IOS_BRIDGE-3 중간 검증
|
||||
```sh
|
||||
$ git diff --check -- apps/flutter-test/ios/RunnerTests/RunnerTests.swift docs/ios-notification-test-guide.md
|
||||
(output)
|
||||
(출력 없음 — whitespace error 없음, exit 0)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```sh
|
||||
$ git diff --check -- packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift apps/flutter-test/ios/RunnerTests/RunnerTests.swift docs/ios-notification-test-guide.md
|
||||
(output)
|
||||
(출력 없음 — whitespace error 없음, exit 0)
|
||||
```
|
||||
|
||||
고정 계약 명령(아래)은 비로그인 ssh 셸 PATH에 flutter가 없어 차단됐다. `계획 대비 변경 사항` 1~4 참고.
|
||||
```sh
|
||||
$ ssh toki@toki-labs.com 'cd ~/works/nexo/packages/messaging_flutter && flutter pub get && flutter test && flutter analyze --no-fatal-infos'
|
||||
(output)
|
||||
zsh:1: command not found: flutter
|
||||
EXIT=127
|
||||
```
|
||||
|
||||
대체 실행: 미커밋 트리를 `$HOME/tmp/nexo-codex-test`로 sync 후 PATH 지정.
|
||||
```sh
|
||||
$ ssh toki@toki-labs.com 'export PATH="$HOME/SDK/flutter/bin:$HOME/Library/Android/sdk/platform-tools:$PATH"; cd "$HOME/tmp/nexo-codex-test/packages/messaging_flutter" && flutter pub get && flutter test && flutter analyze --no-fatal-infos'
|
||||
...
|
||||
00:00 +13: Channel constants notification and action channel names are preserved
|
||||
00:00 +14: All tests passed!
|
||||
Analyzing messaging_flutter...
|
||||
No issues found! (ran in 1.2s)
|
||||
EXIT=0
|
||||
```
|
||||
|
||||
```sh
|
||||
$ ssh toki@toki-labs.com 'cd ~/works/nexo/apps/flutter-test && flutter pub get && flutter test && flutter test integration_test'
|
||||
(output)
|
||||
$ ssh toki@toki-labs.com 'export PATH="$HOME/SDK/flutter/bin:$HOME/Library/Android/sdk/platform-tools:$PATH"; cd "$HOME/tmp/nexo-codex-test/apps/flutter-test" && flutter pub get && flutter test && flutter test integration_test -d emulator-5554'
|
||||
# flutter test (widget):
|
||||
00:00 +4: updates device token from plugin callback
|
||||
00:00 +5: All tests passed!
|
||||
# flutter test integration_test (Android emulator-5554, Pixel_6_Pro_API_34):
|
||||
✓ Built build/app/outputs/flutter-apk/app-debug.apk
|
||||
00:00 +0: singleton instance is accessible
|
||||
00:00 +1: native debug event reaches harness routing UI (channel)
|
||||
00:00 +2: native debug event reaches harness routing UI (thread/CRT)
|
||||
00:01 +3: native debug event reaches harness routing UI (boolean CRT)
|
||||
00:01 +4: All tests passed!
|
||||
EXIT=0
|
||||
```
|
||||
주: 계획의 `flutter test integration_test`는 device 미지정이라 headless 환경에서 device 선택이 필요해 `-d emulator-5554`를 명시했다(동일 integration suite).
|
||||
|
||||
```sh
|
||||
$ ssh toki@toki-labs.com 'command -v xcodebuild && cd ~/works/nexo/apps/flutter-test/ios && xcodebuild test -workspace Runner.xcworkspace -scheme Runner -destination "platform=iOS Simulator,name=iPhone 15"'
|
||||
(output)
|
||||
# xcodebuild은 존재(/usr/bin/xcodebuild, Xcode 26.0.1)하나, 동일 이름 iPhone 15 시뮬레이터가 OS 17.2/18.0/18.3.1 3개라 name 기반 destination이 해석되지 않음:
|
||||
xcodebuild: error: Unable to find a device matching the provided destination specifier:
|
||||
The requested device could not be found because no available devices matched the request.
|
||||
```
|
||||
|
||||
대체 실행: pod install 선행 + 동일 기종 iPhone 15(OS 18.3.1) device id 지정(`계획 대비 변경 사항` 4).
|
||||
```sh
|
||||
$ ssh toki@toki-labs.com 'export PATH="/usr/local/bin:$HOME/SDK/flutter/bin:$PATH"; cd "$HOME/tmp/nexo-codex-test/apps/flutter-test/ios" && pod install && xcodebuild test -workspace Runner.xcworkspace -scheme Runner -destination "platform=iOS Simulator,id=DD93CB8A-4E3A-45B8-8994-DCE6EFE43CFC"'
|
||||
Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed.
|
||||
...
|
||||
Test suite 'RunnerTests' started on 'Clone 1 of iPhone 15 - Runner (47733)'
|
||||
Test case 'RunnerTests.testClearAuthTokenMissingArgReturnsInvalidArg()' passed (0.004 seconds)
|
||||
Test case 'RunnerTests.testClearAuthTokenSuccessNoOp()' passed (0.002 seconds)
|
||||
Test case 'RunnerTests.testDebugSendNativeEventForwardsPayloadToEventSink()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testDebugSendNativeEventWithNullPayloadReturnsInvalidArg()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testGetPlatformVersion()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testSaveAndGetDeviceToken()' passed (0.002 seconds)
|
||||
Test case 'RunnerTests.testSaveDeviceTokenWithNullTokenReturnsInvalidArg()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testSetAuthTokenMissingArgReturnsInvalidArg()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testSetAuthTokenSuccessNoOp()' passed (0.002 seconds)
|
||||
Test case 'RunnerTests.testSetSigningKeySuccessNoOp()' passed (0.001 seconds)
|
||||
** TEST SUCCEEDED **
|
||||
EXIT=0
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -169,3 +228,19 @@ Sections and their ownership:
|
|||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: FAIL
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Fail
|
||||
- test coverage: Fail
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- plan deviation: Fail
|
||||
- verification trust: Pass
|
||||
- 발견된 문제:
|
||||
- Required: `apps/flutter-test/ios/RunnerTests/RunnerTests.swift:141` has only the success boundary for `setSigningKey`, but the plan's `IOS_BRIDGE-3` required invalid-argument coverage for required args and the implementation has an `INVALID_ARG` branch for `setSigningKey` at `packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:65`. Add a deterministic XCTest such as `testSetSigningKeyMissingArgReturnsInvalidArg()` that calls `setSigningKey` with a missing `signingKey` or `serverUrl`, asserts `FlutterError.code == "INVALID_ARG"`, and rerun the iOS XCTest command.
|
||||
- 다음 단계:
|
||||
- WARN/FAIL 후속: active plan/review를 archive한 뒤 누락된 `setSigningKey` invalid-argument XCTest와 검증 증거만 다루는 후속 `PLAN-cloud-G07.md` / `CODE_REVIEW-cloud-G07.md`를 작성한다.
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<!-- task=m-ios-notification-preflight plan=1 tag=REVIEW_IOS_BRIDGE -->
|
||||
|
||||
# Code Review Reference - REVIEW_IOS_BRIDGE
|
||||
|
||||
> **[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, user-owned 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`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, task archive move) 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-06-01
|
||||
task=m-ios-notification-preflight, plan=1, tag=REVIEW_IOS_BRIDGE
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/ios-notification-preflight.md`
|
||||
- Task ids:
|
||||
- `bridge-scope`: Dart가 기대하는 EventChannel/MethodChannel 이름, iOS 최소 구현 method, no-op/fallback 경계를 정리한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_1.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_1.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-ios-notification-preflight/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_IOS_BRIDGE-1] setSigningKey invalid-argument XCTest | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`에 `setSigningKey` missing argument가 `FlutterError.code == "INVALID_ARG"`를 반환하는 XCTest를 추가한다.
|
||||
- [x] deterministic structural checks와 iOS XCTest를 실행하고 실제 stdout/stderr를 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_1.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_1.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 active task 디렉터리 `agent-task/m-ios-notification-preflight/`를 `agent-task/archive/YYYY/MM/m-ios-notification-preflight/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] 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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
코드 변경은 계획대로 `setSigningKey` missing-argument XCTest 1개만 추가했다. 검증 명령 자체는 계획의 고정 계약을 그대로 실행했고(중간 검증 2개는 local, 최종 `xcodebuild`는 계획에 명시된 `id=DD93CB8A-...` 그대로), simulator id도 여전히 유효해 destination 대체는 없었다. remote runner 사용에 필요한 준비만 추가했고 검증 의미는 바꾸지 않았다.
|
||||
|
||||
- 현재 worktree(미커밋 변경 포함)를 `tar | ssh ... tar -xzf -`로 `$HOME/tmp/nexo-codex-test`에 재동기화했다.
|
||||
- 비로그인 ssh 셸 PATH에 flutter/pod이 없어 `export PATH="/usr/local/bin:$HOME/SDK/flutter/bin:$PATH"`를 붙이고, `xcodebuild` 전에 `flutter pub get` → `flutter build ios --simulator --debug --config-only` → `pod install`로 빌드 입력을 준비했다(직전 plan과 동일 절차, `agent-test/local/rules.md` 및 `packages/messaging_flutter/docs/android-test-environment.md` 근거). 레포 추적 파일은 변경하지 않았다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- **누락 branch만 고정, 범위 비확대.** 계획 권고대로 missing `signingKey` variant(인자에 `serverUrl`만 전달) 하나만 추가해 `NexoMessagingPlugin.swift`의 `setSigningKey` invalid-argument branch를 deterministic하게 고정했다. source behavior, Dart API, docs는 건드리지 않았다.
|
||||
- **기존 테스트 패턴 일치.** 새 `testSetSigningKeyMissingArgReturnsInvalidArg`는 기존 `testSetAuthTokenMissingArgReturnsInvalidArg`/`testClearAuthTokenMissingArgReturnsInvalidArg`와 동일한 네이밍·expectation·assertion(`(result as? FlutterError)?.code == "INVALID_ARG"`) 패턴을 따르고, `testSetSigningKeySuccessNoOp` 바로 뒤에 배치했다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `RunnerTests.swift`에 `setSigningKey` missing argument XCTest가 추가됐는지 확인한다.
|
||||
- 새 테스트가 `FlutterError.code == "INVALID_ARG"`를 직접 검증하는지 확인한다.
|
||||
- 검증 결과가 실제 stdout/stderr이며 Xcode remote runner 사용 시 sync/destination 변경 근거가 기록됐는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
|
||||
|
||||
### REVIEW_IOS_BRIDGE-1 중간 검증
|
||||
```sh
|
||||
$ git diff --check -- apps/flutter-test/ios/RunnerTests/RunnerTests.swift
|
||||
(출력 없음 — whitespace error 없음, exit 0)
|
||||
```
|
||||
|
||||
```sh
|
||||
$ rg --sort path -n "testSetSigningKey.*InvalidArg|setSigningKey|serverUrl or signingKey is null|INVALID_ARG" apps/flutter-test/ios/RunnerTests/RunnerTests.swift packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:69: XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:104: XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:135: XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:144: let call = FlutterMethodCall(methodName: "setSigningKey", arguments: [
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:157: func testSetSigningKeyMissingArgReturnsInvalidArg() {
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:160: let call = FlutterMethodCall(methodName: "setSigningKey", arguments: [
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:166: XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
apps/flutter-test/ios/RunnerTests/RunnerTests.swift:194: XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:44: result(FlutterError(code: "INVALID_ARG", message: "event payload is null", details: nil))
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:51: result(FlutterError(code: "INVALID_ARG", message: "token is null", details: nil))
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:60: result(FlutterError(code: "INVALID_ARG", message: "serverUrl or token is null", details: nil))
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:64: case "setSigningKey":
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:66: result(FlutterError(code: "INVALID_ARG", message: "serverUrl or signingKey is null", details: nil))
|
||||
packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:72: result(FlutterError(code: "INVALID_ARG", message: "serverUrl is null", details: nil))
|
||||
```
|
||||
기대 결과대로 whitespace error가 없고, `RunnerTests.swift:157`에 `testSetSigningKeyMissingArgReturnsInvalidArg`가 보인다. plugin의 `setSigningKey` invalid branch는 `NexoMessagingPlugin.swift:64-66`.
|
||||
|
||||
### 최종 검증
|
||||
```sh
|
||||
$ git diff --check -- apps/flutter-test/ios/RunnerTests/RunnerTests.swift
|
||||
(출력 없음 — whitespace error 없음, exit 0)
|
||||
```
|
||||
|
||||
remote runner 준비(worktree sync + PATH + pub get/config/pod install)는 `계획 대비 변경 사항` 참고. pod install 직전 simctl 확인에서 동일 기종 iPhone 15(OS 18.3.1) id `DD93CB8A-...`가 여전히 유효해 계획 명령을 그대로 실행했다.
|
||||
```sh
|
||||
$ ssh toki@toki-labs.com 'command -v xcodebuild && cd "$HOME/tmp/nexo-codex-test/apps/flutter-test/ios" && xcodebuild test -workspace Runner.xcworkspace -scheme Runner -destination "platform=iOS Simulator,id=DD93CB8A-4E3A-45B8-8994-DCE6EFE43CFC"'
|
||||
/usr/bin/xcodebuild
|
||||
...
|
||||
Test suite 'RunnerTests' started on 'Clone 1 of iPhone 15 - Runner (53725)'
|
||||
Test case 'RunnerTests.testClearAuthTokenMissingArgReturnsInvalidArg()' passed (0.003 seconds)
|
||||
Test case 'RunnerTests.testClearAuthTokenSuccessNoOp()' passed (0.002 seconds)
|
||||
Test case 'RunnerTests.testDebugSendNativeEventForwardsPayloadToEventSink()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testDebugSendNativeEventWithNullPayloadReturnsInvalidArg()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testGetPlatformVersion()' passed (0.002 seconds)
|
||||
Test case 'RunnerTests.testSaveAndGetDeviceToken()' passed (0.003 seconds)
|
||||
Test case 'RunnerTests.testSaveDeviceTokenWithNullTokenReturnsInvalidArg()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testSetAuthTokenMissingArgReturnsInvalidArg()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testSetAuthTokenSuccessNoOp()' passed (0.001 seconds)
|
||||
Test case 'RunnerTests.testSetSigningKeyMissingArgReturnsInvalidArg()' passed (0.002 seconds)
|
||||
Test case 'RunnerTests.testSetSigningKeySuccessNoOp()' passed (0.001 seconds)
|
||||
** TEST SUCCEEDED **
|
||||
EXIT=0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[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 |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 |
|
||||
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트 | placeholder 텍스트를 실제 내용으로 교체 |
|
||||
| 사용자 리뷰 요청 | 구현 에이전트 | `상태: 없음` 유지 또는 사용자-only blocker evidence 기록 |
|
||||
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
|
||||
| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 |
|
||||
| 코드리뷰 결과 | 리뷰 에이전트 | 스텁에 포함하지 않음 |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Pass
|
||||
- test coverage: Pass
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- plan deviation: Pass
|
||||
- verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계:
|
||||
- PASS: active plan/review를 archive하고 `complete.log` 작성 후 task directory를 `agent-task/archive/2026/06/m-ios-notification-preflight/`로 이동한다.
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# Complete - m-ios-notification-preflight
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-01
|
||||
|
||||
## 요약
|
||||
|
||||
iOS notification preflight의 bridge-scope 작업을 2회 리뷰 루프로 완료했다. 최종 판정은 PASS다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | `setSigningKey` invalid-argument XCTest 누락으로 후속 테스트 보강 필요 |
|
||||
| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | PASS | 누락된 `setSigningKey` missing-argument XCTest 추가 및 Xcode 검증 통과 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- iOS plugin에 Dart가 기대하는 notification EventChannel과 action MethodChannel을 등록하고 legacy `nexo_messaging` channel을 유지했다.
|
||||
- iOS bridge minimum method set을 구현하고 auth/signing secret 저장은 validation-only no-op 경계로 고정했다.
|
||||
- `RunnerTests.swift`에 iOS bridge method/stream handler tests와 `setSigningKey` invalid-argument regression test를 추가했다.
|
||||
- `docs/ios-notification-test-guide.md`를 minimum bridge 구현 상태와 남은 APNs/FCM 범위에 맞게 갱신했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `git diff --check -- apps/flutter-test/ios/RunnerTests/RunnerTests.swift` - PASS; whitespace error 없음.
|
||||
- `rg --sort path -n "testSetSigningKey.*InvalidArg|setSigningKey|serverUrl or signingKey is null|INVALID_ARG" apps/flutter-test/ios/RunnerTests/RunnerTests.swift packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift` - PASS; `testSetSigningKeyMissingArgReturnsInvalidArg`와 iOS invalid branch 확인.
|
||||
- `cd packages/messaging_flutter && flutter test` - PASS; 14 tests passed.
|
||||
- `cd packages/messaging_flutter && flutter analyze --no-fatal-infos` - PASS; No issues found.
|
||||
- `cd apps/flutter-test && flutter test` - PASS; 5 tests passed.
|
||||
- `ssh toki@toki-labs.com 'command -v xcodebuild && cd "$HOME/tmp/nexo-codex-test/apps/flutter-test/ios" && xcodebuild test -workspace Runner.xcworkspace -scheme Runner -destination "platform=iOS Simulator,id=DD93CB8A-4E3A-45B8-8994-DCE6EFE43CFC"'` - PASS; `** TEST SUCCEEDED **`, RunnerTests 11개 통과.
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/ios-notification-preflight.md`
|
||||
- Completed task ids:
|
||||
- `bridge-scope`: PASS; evidence=`plan_cloud_G07_0.log`, `code_review_cloud_G07_0.log`, `plan_cloud_G07_1.log`, `code_review_cloud_G07_1.log`; verification=`cd packages/messaging_flutter && flutter test`, `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`, `cd apps/flutter-test && flutter test`, remote `xcodebuild test`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<!-- task=m-ios-notification-preflight plan=1 tag=REVIEW_IOS_BRIDGE -->
|
||||
|
||||
# Plan - REVIEW_IOS_BRIDGE
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 plan은 직전 코드리뷰 `code_review_cloud_G07_0.log`의 Required 이슈만 처리한다. 구현의 마지막 단계는 반드시 active `CODE_REVIEW-cloud-G07.md`에서 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. 검증 명령을 실행하고 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 둔 채 리뷰 준비를 보고한다.
|
||||
|
||||
구현 중 사용자 결정, 사용자 소유 외부 환경/secret/service 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션을 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 채운다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 만들지 않는다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/ios-notification-preflight.md`
|
||||
- Task ids:
|
||||
- `bridge-scope`: Dart가 기대하는 EventChannel/MethodChannel 이름, iOS 최소 구현 method, no-op/fallback 경계를 정리한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 배경
|
||||
|
||||
직전 구현은 iOS bridge method contract와 channel registration을 추가했고 Flutter/Dart 검증도 통과했다. 하지만 `IOS_BRIDGE-3`의 "required argument invalid cases" 테스트 범위에서 `setSigningKey`의 invalid-argument 분기가 빠졌다. 실제 구현에는 `serverUrl` 또는 `signingKey`가 없을 때 `INVALID_ARG`를 반환하는 branch가 있으므로, 이 branch를 deterministic XCTest로 고정한다.
|
||||
|
||||
## 범위 결정 근거
|
||||
|
||||
- 포함: `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`에 `setSigningKey` missing argument XCTest 추가, 관련 structural/Xcode 검증 증거 기록.
|
||||
- 제외: `packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift`의 behavior 변경, Dart API 변경, docs 변경, APNs/FCM 실기기 smoke, ACK/dismiss/inline reply 구현.
|
||||
- route: `cloud-G07`. iOS XCTest/Xcode 검증과 remote runner 증거가 필요하고, 직전 리뷰가 test coverage Required 이슈였으므로 같은 lane/grade를 유지한다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`에 `setSigningKey` missing argument가 `FlutterError.code == "INVALID_ARG"`를 반환하는 XCTest를 추가한다.
|
||||
- [ ] deterministic structural checks와 iOS XCTest를 실행하고 실제 stdout/stderr를 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [REVIEW_IOS_BRIDGE-1] setSigningKey invalid-argument XCTest
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/flutter-test/ios/RunnerTests/RunnerTests.swift:141`에는 `setSigningKey` success no-op 테스트만 있다. 반면 `packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift:65`는 `serverUrl` 또는 `signingKey`가 없으면 `INVALID_ARG`를 반환한다. 직전 plan의 `IOS_BRIDGE-3`은 required argument invalid cases 검증을 요구했으므로 이 branch가 테스트되지 않은 상태다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`RunnerTests`에 focused XCTest를 추가한다. 예시는 다음과 같다.
|
||||
|
||||
```swift
|
||||
func testSetSigningKeyMissingArgReturnsInvalidArg() {
|
||||
let plugin = NexoMessagingPlugin()
|
||||
|
||||
let call = FlutterMethodCall(methodName: "setSigningKey", arguments: [
|
||||
"serverUrl": "https://smoke.example",
|
||||
])
|
||||
|
||||
let resultExpectation = expectation(description: "result block must be called.")
|
||||
plugin.handle(call) { result in
|
||||
XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
resultExpectation.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
```
|
||||
|
||||
기존 테스트 네이밍과 배치 패턴을 따른다. 필요하면 missing `serverUrl` variant 대신 missing `signingKey` variant 하나만 추가해도 된다. 둘 다 추가해도 되지만 이 follow-up의 목적은 누락 branch를 고정하는 것이므로 불필요한 범위 확대는 피한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`: `testSetSigningKeyMissingArgReturnsInvalidArg` 또는 동등한 이름의 XCTest를 추가한다.
|
||||
- [ ] `apps/flutter-test/ios/RunnerTests/RunnerTests.swift`: assertion은 `FlutterError.code == "INVALID_ARG"`를 직접 검증한다.
|
||||
|
||||
#### 테스트 결정
|
||||
|
||||
- iOS native branch 테스트이므로 Xcode XCTest가 필수다.
|
||||
- local checkout에는 `xcodebuild`가 없을 수 있다. 그 경우 `command -v xcodebuild`의 실제 결과를 기록하고, 기존 remote runner 방식으로 worktree를 sync한 뒤 Xcode XCTest를 실행한다.
|
||||
- Flutter package/app tests는 이번 후속 범위에서 source behavior를 바꾸지 않으므로 필수는 아니지만, 실행했다면 stdout/stderr를 review stub에 기록한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```sh
|
||||
git diff --check -- apps/flutter-test/ios/RunnerTests/RunnerTests.swift
|
||||
```
|
||||
|
||||
```sh
|
||||
rg --sort path -n "testSetSigningKey.*InvalidArg|setSigningKey|serverUrl or signingKey is null|INVALID_ARG" apps/flutter-test/ios/RunnerTests/RunnerTests.swift packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift
|
||||
```
|
||||
|
||||
기대 결과: whitespace error가 없고, `RunnerTests.swift`에 `setSigningKey` invalid-argument XCTest가 보인다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```sh
|
||||
git diff --check -- apps/flutter-test/ios/RunnerTests/RunnerTests.swift
|
||||
```
|
||||
|
||||
```sh
|
||||
ssh toki@toki-labs.com 'command -v xcodebuild && cd "$HOME/tmp/nexo-codex-test/apps/flutter-test/ios" && xcodebuild test -workspace Runner.xcworkspace -scheme Runner -destination "platform=iOS Simulator,id=DD93CB8A-4E3A-45B8-8994-DCE6EFE43CFC"'
|
||||
```
|
||||
|
||||
기대 결과: 모든 명령 exit code `0`. remote runner를 사용할 때는 먼저 현재 worktree가 `$HOME/tmp/nexo-codex-test`에 반영됐는지 확인한다. simulator id가 더 이상 유효하지 않으면 `xcrun simctl list devices available` 등으로 동일 기종 iPhone 15의 실제 id를 찾고, 변경 이유와 실제 출력을 review stub의 `계획 대비 변경 사항` 및 `검증 결과`에 기록한다.
|
||||
|
|
@ -154,6 +154,21 @@ class RunnerTests: XCTestCase {
|
|||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testSetSigningKeyMissingArgReturnsInvalidArg() {
|
||||
let plugin = NexoMessagingPlugin()
|
||||
|
||||
let call = FlutterMethodCall(methodName: "setSigningKey", arguments: [
|
||||
"serverUrl": "https://smoke.example",
|
||||
])
|
||||
|
||||
let resultExpectation = expectation(description: "result block must be called.")
|
||||
plugin.handle(call) { result in
|
||||
XCTAssertEqual((result as? FlutterError)?.code, "INVALID_ARG")
|
||||
resultExpectation.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testClearAuthTokenSuccessNoOp() {
|
||||
let plugin = NexoMessagingPlugin()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue