<!-- task=m-agent-readable-repository-refactor/11_client_tests plan=0 tag=TEST -->

# Flutter client 테스트 panel/integration 분해

## 이 파일을 읽는 구현 에이전트에게

> 구현 후 계획의 검증 명령을 그대로 실행하고 CODE_REVIEW의 구현 에이전트 소유 섹션에 실제 메모와 stdout/stderr를 채운 뒤 active 파일을 유지한 채 리뷰 준비 완료를 보고한다. 종결 판정, log rename, complete.log와 archive 이동은 code-review 전용이다. 선택된 Milestone의 미해결 구현 잠금 결정이 실제 구현을 차단할 때만 review stub의 사용자 리뷰 요청에 연결 근거를 기록하고 중단한다. 직접 사용자 질문, chat 선택지, request_user_input, USER_REVIEW.md/complete.log 작성은 금지한다. 환경·secret·service, 일반 범위 조정, 후속으로 보완 가능한 증거 공백은 사용자 리뷰 사유가 아니다.

## 배경

apps/client/test/widget_test.dart는 1,596줄에 wire/status fake와 app shell, Edge/Node/runtime/provider/notification 시나리오를 함께 둔다. 공통 harness를 작은 support로 옮기고 panel/integration별 test 파일로 나눈다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 Milestone의 구현 잠금 결정만 active review stub의 사용자 리뷰 요청 섹션에 agent-ops/skills/common/_templates/implementation-user-review-request-section.md 형식으로 기록한다. 구현 중 직접 사용자 prompt는 금지하며, 타당성 검증과 USER_REVIEW.md 작성은 code-review가 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)
- Task ids:
  - `client-tests`: Flutter widget 테스트를 app shell과 panel/integration 시나리오별 파일로 분해
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md
- agent-test/local/rules.md
- agent-test/local/profiles/client-smoke.md
- apps/client/pubspec.yaml
- apps/client/test/widget_test.dart

### SDD 기준

선택 Milestone은 SDD: 불필요다. 외부 API/proto/config schema와 runtime 책임 경계를 바꾸지 않는 동작 보존형 리팩터링이라는 기록 사유를 적용한다.

### 테스트 환경 규칙

test_env=local. local rules와 client-smoke profile을 읽었다. /sdk/flutter/bin/flutter를 확인했고 현재 checkout에서 make client-test가 38 tests PASS했다. mobile/UI 검증이 2분 무진행이면 blind retry를 중단하고 focused flutter test 출력과 capture 불가 사유를 기록한다.

### 테스트 커버리지 공백

동작 변경은 없다. widget_test.dart의 실제 19개 testWidgets와 전체 client 38 tests를 그대로 보존한다.

### 심볼 참조

운영 심볼의 rename/remove는 없다. 기존 테스트 이름과 package 경계를 유지한다.

### 분할 판단

Flutter runner와 UI hang risk가 Go 작업과 달라 독립 11_client_tests로 격리한다. 이 plan이 client-tests를 직접 check-on-pass 한다. 최종 readability plan은 이 complete.log를 기다린다.

### 범위 결정 근거

widget_test.dart와 test-only support/new scenario files만 수정한다. lib UI/DTO, generated Dart, golden/screenshot 추가는 제외한다.

### 빌드 등급

local-G07: 1,596줄과 큰 fake를 6개 시나리오로 나누며 async pump/Completer 의미를 보존해야 한다.

## 구현 체크리스트

- [ ] [TEST-1] widget_test.dart를 app shell, panel, provider DTO, notification 시나리오로 옮기고 원본 testWidgets 19개, 전체 client testWidgets 24개와 make client-test 38 PASS를 보존한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [TEST-1] client widget harness와 시나리오 파일 분리

- 문제: apps/client/test/widget_test.dart:14의 FakeWebSocket, 65의 status repository, 422 이후 20개 widget 시나리오가 한 파일에 결합되어 있다.
- 해결 방법:

~~~text
Before
widget_test.dart:14  class FakeWebSocket implements WebSocket
widget_test.dart:65  class FakeControlPlaneStatusRepository implements ControlPlaneStatusRepository
widget_test.dart:422 testWidgets('Client App basic rendering...')

After
test/support/client_test_harness.dart
test/app_shell_test.dart
test/edge_nodes_panels_test.dart
test/runtime_panel_test.dart
test/provider_status_test.dart
test/notification_integration_test.dart
widget_test.dart removed
~~~

- 수정 파일 및 체크리스트:
  - [ ] apps/client/test/widget_test.dart: 모든 scenario를 이동한 뒤 삭제한다.
  - [ ] apps/client/test/support/client_test_harness.dart: fake wire/status repository와 pump helper를 최소 공개 범위로 둔다.
  - [ ] apps/client/test/app_shell_test.dart: handshake/error/agent shell/mobile layout을 둔다.
  - [ ] apps/client/test/edge_nodes_panels_test.dart: Edge/Node/log/refresh를 둔다.
  - [ ] apps/client/test/runtime_panel_test.dart: domain agent/command/gating을 둔다.
  - [ ] apps/client/test/provider_status_test.dart: provider rendering/DTO parsing을 둔다.
  - [ ] apps/client/test/notification_integration_test.dart: Nexo stream/SnackBar를 둔다.
- 테스트 작성: 추가 테스트는 작성하지 않는다. 원본 19개 testWidgets의 이름, pump 순서, assertion과 전체 38 tests를 보존한다.
- 중간 검증:

~~~bash
dart format apps/client/test
test ! -e apps/client/test/widget_test.dart
[ "$(git show HEAD:apps/client/test/widget_test.dart | rg -n 'testWidgets\(' | wc -l)" -eq 19 ]
[ "$(rg --sort path -n 'testWidgets\(' apps/client/test/app_shell_test.dart apps/client/test/edge_nodes_panels_test.dart apps/client/test/runtime_panel_test.dart apps/client/test/provider_status_test.dart apps/client/test/notification_integration_test.dart | wc -l)" -eq 19 ]
[ "$(rg --sort path -n 'testWidgets\(' apps/client/test | wc -l)" -eq 24 ]
~~~

기대 결과: Dart format 성공, 원본 삭제, 분해 대상 testWidgets 19개와 전체 client testWidgets 24개.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| apps/client/test/widget_test.dart | TEST-1 |
| apps/client/test/support/client_test_harness.dart | TEST-1 |
| apps/client/test/app_shell_test.dart | TEST-1 |
| apps/client/test/edge_nodes_panels_test.dart | TEST-1 |
| apps/client/test/runtime_panel_test.dart | TEST-1 |
| apps/client/test/provider_status_test.dart | TEST-1 |
| apps/client/test/notification_integration_test.dart | TEST-1 |

## 최종 검증

~~~bash
make client-test
[ "$(git show HEAD:apps/client/test/widget_test.dart | rg -n 'testWidgets\(' | wc -l)" -eq 19 ]
[ "$(rg --sort path -n 'testWidgets\(' apps/client/test/app_shell_test.dart apps/client/test/edge_nodes_panels_test.dart apps/client/test/runtime_panel_test.dart apps/client/test/provider_status_test.dart apps/client/test/notification_integration_test.dart | wc -l)" -eq 19 ]
[ "$(rg --sort path -n 'testWidgets\(' apps/client/test | wc -l)" -eq 24 ]
python3 -c "from pathlib import Path; fs=sorted(Path('apps/client/test').rglob('*.dart')); bad=[(sum(1 for _ in f.open()),str(f)) for f in fs if sum(1 for _ in f.open())>1000]; print(bad); raise SystemExit(bool(bad))"
~~~

기대 결과: Flutter 전체 38 tests PASS, 분해 대상 testWidgets 19개와 전체 client testWidgets 24개, 1,000줄 초과 test 없음.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
