# /dev-run-test — Chạy Dev Self-Check Tests & Report kết quả

> **Scope — dev self-check (smoke), không phải bộ test chính thức.** Chạy các test do
> `/dev-gen-test` sinh ra để dev xác nhận code mình chạy được trước khi review. Đây là một
> self-check của dev, **không** phải lần chạy test authoritative của QC/dev-team (flow riêng).
> Pass/fail được publish lên Living Docs như tín hiệu **dev self-test** — nó cho QC biết
> dev đã chạy check của họ; KHÔNG phải tuyên bố về độ phủ test chính thức.

## Gate
{{include:steps/gate.md}}

*Lưu ý: Với lệnh này, target ở Bước 1 là một UC-ID hoặc tên service. Context loading cung cấp `conventions.test_command` và `tech_stack.module`.*

## Context
{{include:steps/context-loader.md}}

---

## Service Detection

Đọc `active_module` từ context (đã phân giải ở context-loader Bước 1).
Dùng nó để chọn đúng lệnh chạy và bảng phân tích lỗi bên dưới.

---

## Submodule Working Directory

*Bỏ qua section này nếu `service_root` chưa được set (single-service mode).*

Khi chạy ở **umbrella/submodule mode** (`service_root` được phân giải ở context-loader Bước 1.6):

- Mọi lệnh trong section **Run** bên dưới phải thực thi từ trong `{service_root}/`
- `conventions.test_command` được nạp từ `{service_root}/.agent/project-context.yaml` — đã riêng theo service
- Prefix mọi lệnh shell bằng `cd {service_root} &&`:

```bash
cd {service_root}

# Then run any of the commands in the Run section below, e.g.:
{conventions.test_command}
mvn test -Dtest={ClassName}            # java-spring
go test ./... -run Test{FunctionName}  # golang
npx vitest run src/...                 # web-frontend
flutter test test/{domain}/...         # flutter
```

> **Vì sao cd?** Session Claude Code mở ở umbrella root. Mỗi service submodule có build tool, test runner, và cây dependency riêng — test phải chạy từ trong thư mục service.

---

## Run

### Nếu `platform_type = backend`

```bash
# Run all tests for this UC
{conventions.test_command}

# Scoped to specific class (faster feedback)
# java-spring:
mvn test -Dtest={ClassName}
# golang:
go test ./... -run Test{FunctionName}
# dotnet:
dotnet test --filter "FullyQualifiedName~{ClassName}"
# php-laravel:
php artisan test --filter {ClassName}
# context-engineering (pytest):
pytest tests/{domain}/{test_file}.py -v
pytest tests/{domain}/{test_file}.py::{TestClass}::{test_method} -v
pytest tests/ --cov={source_dir} --cov-report=term-missing
```

### Nếu `platform_type = web-frontend`

```bash
# Run all tests
{conventions.test_command}

# Scoped (Vitest / Jest):
npx vitest run src/features/{domain}/{Component}.test.tsx
npx jest src/features/{domain}/{Component}.test.tsx

# E2E (Playwright):
npx playwright test {UC-ID}
# E2E (Cypress):
npx cypress run --spec "cypress/e2e/{UC-ID}*"
```

### Nếu `platform_type = mobile`

```bash
# Flutter:
flutter test test/{domain}/{UC-ID}_test.dart
flutter test integration_test/{UC-ID}_test.dart   # integration

# React Native:
npx jest {UC-ID}

# iOS (Xcode command line):
xcodebuild test -scheme {Scheme} -destination 'platform=iOS Simulator,name=iPhone 15'

# Android:
./gradlew test                              # unit tests
./gradlew connectedAndroidTest              # instrumented (device/emulator required)
```

> **Lưu ý cho Android instrumented test:** cần một emulator đang chạy hoặc device kết nối trước khi chạy `connectedAndroidTest`. Khởi động qua Android Studio hoặc: `emulator -avd {AVD_NAME} &`

---

## Analyze Failures

### Backend failure patterns

#### java-spring / golang / dotnet / php-laravel

| Error Pattern | Nguyên nhân thường gặp | Suggested Fix |
|---|---|---|
| `NullPointerException` | Thiếu setup mock | Kiểm tra `given(...)`/`coEvery`/`mockk` cho dependency null |
| `Bean not found` | Thiếu khai báo mock | Thêm `@MockBean` / inject mock |
| `Expected 200, got 401` | Thiếu setup auth | Thêm auth token/user vào test context |
| `Expected 200, got 400` | Request body fail validation | Kiểm tra field bắt buộc trong DTO |
| `Expected 200, got 403` | Sai role | Thêm đúng role cho test user |
| `LazyInitializationException` | Lazy collection ngoài transaction | Thêm `@Transactional` hoặc eager fetch |
| `Mapper not found` | Code chưa compile | Chạy build trước khi test |
| `DataIntegrityViolationException` | Trùng key trong DB setup | Dùng `@Transactional` + rollback, hoặc clean DB giữa các test |
| Assertion mismatch | Sai giá trị mock return | Đọc lại setup `given(...).willReturn(...)` |

#### context-engineering (AI/LLM pipelines)

| Error Pattern | Nguyên nhân thường gặp | Suggested Fix |
|---|---|---|
| `AssertionError` trên output mock LLM | Giá trị mock return không khớp schema | Kiểm tra lại setup `mock_llm.return_value` / `mock_llm.complete.return_value` |
| `ValidationError` trên response | Cấu trúc output LLM không khớp schema kỳ vọng | Siết schema check hoặc thêm retry logic trong test |
| `ConnectionError` / `APIError` | LLM API thật bị gọi trong test | Đảm bảo mock `patch('...')` được áp dụng — không bao giờ gọi LLM thật trong unit test |
| `TimeoutError` | Test gọi LLM endpoint live | Thêm mock; kiểm tra test fixture |
| Kết quả flaky / non-deterministic | Response LLM thật dùng trong assertion | Thay bằng giá trị mock return tất định |

### Web frontend failure patterns

| Error Pattern | Nguyên nhân thường gặp | Suggested Fix |
|---|---|---|
| `Unable to find role "..."` | Element chưa render | Bọc trong `await waitFor(() => ...)` |
| `TestingLibraryElementError: Found multiple elements` | Selector quá rộng | Dùng `getByRole(..., { name: '...' })` để thu hẹp |
| `Network request not intercepted` | Thiếu MSW handler / `cy.intercept` | Thêm handler cho endpoint |
| `act(...)` warning | State update sau khi test kết thúc | Await async event / `await userEvent.click(...)` |
| `Cannot read properties of undefined` | Component render trước khi data load | Thêm loading state hoặc mock data đã resolve |
| Playwright timeout | Page chưa navigate / element ẩn | Kiểm tra route, thêm `waitForSelector` |
| `expect(page.locator(...)).toBeVisible` fail | Sai selector | Dùng Playwright Inspector để tìm đúng locator |

### Mobile failure patterns

#### Flutter
| Error Pattern | Nguyên nhân thường gặp | Suggested Fix |
|---|---|---|
| `pumpAndSettle timed out` | Async operation chưa hoàn thành | Dùng `pump(Duration(...))` cho delay cụ thể |
| `No widget found` | Widget chưa render / sai finder | Kiểm tra `find.byType`, `find.text`, `find.byKey` |
| `setState called after dispose` | Widget bị dispose trước khi async xong | Cancel async trong `dispose()` |
| BLoC state mismatch | Sai event emit | Verify `mockBloc` nhận đúng event |

#### React Native
| Error Pattern | Nguyên nhân thường gặp | Suggested Fix |
|---|---|---|
| `Unable to find element` | Thiếu `testID` hoặc sai query | Thêm `accessibilityLabel` hoặc `testID` vào component |
| `act(...)` warning | Async state update | Bọc trong `act(async () => { ... })` |
| Thiếu navigation mock | `useNavigation` chưa mock | Thêm jest mock cho `@react-navigation/native` |

#### iOS / Android
| Error Pattern | Nguyên nhân thường gặp | Suggested Fix |
|---|---|---|
| `XCTAssertEqual failed` | Sai giá trị kỳ vọng | Kiểm tra output ViewModel cho mock đã cho |
| `Compose node not found` | Sai `contentDescription` / `testTag` | Thêm `Modifier.testTag(...)` vào composable |
| `Hilt injection failed` | Thiếu test module | Thêm `@UninstallModules` + `@BindValue` trong test class |
| Emulator not available | `connectedAndroidTest` không có device | Khởi động emulator trước, chờ nó boot |

---

## Write Trace State

Sau khi chạy, lưu kết quả vào **TSV authoritative** trong service để chúng tới được
report Living Docs ở spec module (qua `/sync` + `/validate-traces`). Các file test
ở lại trong service — chỉ *status* của lần chạy được report.

Cập nhật **sổ của platform đang test** `{paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv` (`{platform}` = platform của code/`.feature` đang test — `system` cho backend, `web`/`app` cho FE/App; nếu `domain`/`prd_slug` không phân giải được từ spec target, định vị TSV bằng cách glob `{paths.trace_dir}/**/{UC-ID}-{platform}.tsv` — nó được tạo trước đó bởi `/generate-bdd`) — cho mỗi scenario row (khớp `sc_id` qua tag
`@trace.verifies={UC-ID}-SC{N}` của test). *(Umbrella + `spec_source`: `trace_dir` là `{spec_source}/.trace` — test chạy từ `service_root` nhưng update `dev_selftest` ghi vào **spec repo**; commit/push spec submodule cho nó.)*

| Cột | Giá trị |
|--------|-------|
| `dev_selftest` | `pass` nếu mọi test của SC này pass · `fail` nếu có cái fail · `not_run` nếu test của nó bị skip/vắng |
| `dev_selftest_at` | hôm nay `YYYY-MM-DD` |

Giữ nguyên mọi cột khác — đặc biệt **không bao giờ** đụng `qc_status`/`qc_run_at`
(kết quả QC automation chính thức, do `/qc-run-test` sở hữu). `dev_selftest` (dev smoke)
và `qc_status` (QC chính thức) là hai tín hiệu riêng. `dev_selftest`/`dev_selftest_at` cũng
trực giao với `status` (OK/GAP/DRIFT/UNTRACKED): `status` theo dõi *coverage*, `dev_selftest`
theo dõi *kết quả chạy* gần nhất của dev.

## Refresh Panel Mirror
{{include:steps/trace-mirror.md}}

## Output

{{include:steps/report-footer.md}}

```
/dev-run-test Report — {UC-ID} ({active_module})
✅ Passed: {N} | ❌ Failed: {M} | ⏭️ Skipped: {K} | Duration: {X}s

## Failed Tests
| Test | Error | Root Cause |
|------|-------|------------|

## Recommendations
{fix cụ thể cho từng failure}

Trace: {paths.trace_dir}/{domain}/{prd-slug}/{UC-ID}-{platform}.tsv updated (dev_selftest, dev_selftest_at)

Next:
  Mọi test pass → /review-code {UC-ID}
  Test fail     → /fix-bug {TICKET_ID} (bug thật) hoặc fix test (sai expectation)

📊 Living Docs: chạy /validate-traces (hoặc /sync) để push trace này lên dashboard spec-module.
```
