Files
CFDivePlatform/openspec/changes/archive/2026-06-16-feature-test-coverage/design.md
T
a620906209 80f5cf179f
Run Tests / test (pull_request) Failing after 1m53s
chore(openspec): 歸檔 feature-test-coverage、provider-verification-workflow 變更
兩個 change 全任務完成(feature-test-coverage 40/40、provider-verification-workflow 48/48)。

同步規格:
- feature-test-coverage/spec.md 補齊 ## Requirements 區塊標頭(結構修正)
- Admin 教練審核 scenario 改為 approve 端點(toggle-verified 已由 #36 移除)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 19:19:52 +08:00

57 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
現有 187 個 Feature test 分佈在認證、狀態機邊界、聊天授權等安全層,核心業務端點(Provider 課程管理、時段管理、Admin 使用者操作、通知觸發、預約列表)完全未覆蓋。缺口已於 2026-06-11 稽核報告中識別(通知系統「僅 Scheduler 路徑覆蓋」),此 change 補齊這五個區塊。
**現有 Factory 狀況**`database/factories/` 只有 `UserFactory``DivingOffer``CourseSchedule``Booking` 均無 factory——現有測試一律使用 `Model::create([...])` 直接建立,新測試沿用此慣例,不新增 factory(避免引入 app 行為)。
**認證方式**:現有 Feature test 全部使用 `$this->actingAs($user)` + `getJson/postJson` 等 HTTP helpers,不手動建立 token。新測試維持相同模式。
## Goals / Non-Goals
**Goals:**
- 5 個新 Feature test 類別:`ProviderOfferCrudTest``ProviderScheduleCrudTest``AdminUserManagementTest``NotificationTriggerTest``BookingListTest`
- 涵蓋正常流程(happy path)+ 所有權邊界(403+ 輸入驗證(422)
- 通知測試補齊直接事件路徑(建立/確認/拒絕/取消),不只驗證 Scheduler 完課路徑
**Non-Goals:**
- 不修改任何 app 程式碼
- 不補 Unit test(已於上一個 change 完成)
- 不測試 UI / 前端行為
- 不測試通知 Email 實際發送(`notification-email` 規格另有覆蓋)
## Decisions
### D1:延續現有 Feature test 架構,不引入 Service layer mock
現有測試全部使用 `RefreshDatabase` + 真實 DB,未 mock 任何 repository/service。維持一致性優先於執行速度——187 案例目前 42 秒,新增 ~50 案例後預估 55 秒以內,可接受。
**替代方案**mock `Cache::tags()``Notification`。決議:`Notification::fake()` 繼續使用(現有慣例),`Cache::tags()` 讓它真實運行(Redis 在 Docker 中可用)。
### D2:通知測試使用 `Notification::fake()` + `assertSentTo()`
現有 `BookingSchedulerTest` 已驗證 Scheduler 完課觸發通知。`NotificationTriggerTest` 補齊 controller 直接觸發的路徑(`store``confirm``reject``member cancel``provider cancel`),同樣 fake 後斷言 `Notification::assertSentTo()`,不依賴 queue worker。
### D3:預約列表測試不涵蓋分頁(暫緩 O3.2)
`MemberBookingController::index()` 目前為 `->get()` 全量回傳(O3.2 未完成),測試僅斷言回傳正確的資料集合(`data` 陣列包含/不包含正確的 booking)。分頁斷言待 O3.2 實作後加入。
### D4:所有權邊界是必測案例,非選測
Provider CRUD 的「他人不可操作」403 路徑在現有 `BookingLifecycleTest` 已有先例,這裡對 Offer 和 Schedule 同樣必須測。Admin 操作已有 `AdminEndpointAuthTest` 覆蓋角色層的 401/403`AdminUserManagementTest` 聚焦資料正確性與操作副作用(is_active 切換、is_verified 切換 + 快取清除)。
## Risks / Trade-offs
- **`Cache::tags()` 與 driver 相容性**`phpunit.xml` 設定 `CACHE_STORE=array`。Laravel 11 的 `array` store 繼承 `TaggableStore`,因此 `Cache::tags(['diving_offers'])->flush()` 在測試環境可正常執行(`DivingOfferVisibilityTest::test_toggle_verified_takes_effect_immediately_despite_cache` 已驗證)。真正的風險是:若未來 CI 或本機改用 `file``database` 等不支援 tags 的 driver,所有呼叫 `Cache::tags()` 的 controller 測試都會拋出 `BadMethodCallException`。→ 緩解:在 tasks 加上驗收項目,確認 `phpunit.xml``CACHE_STORE``array``redis`,並加入 README 說明。
- **Admin toggle-verified 的快取副作用驗證方式**:`DivingOfferVisibilityTest` 已有端對端驗證(toggle → 公開列表立即消失)。`AdminUserManagementTest` 不重複這個斷言,只驗證 `is_verified` 欄位變更與 HTTP 200 即可,避免測試重複。
- **Notification::fake() 無法驗證 mail 實際送出**:這是刻意的邊界——只驗證通知物件被派發至正確收件者,不驗證 email 模板內容(屬於 `notification-email` 規格的範疇)。
## 各測試類別案例清單
| 測試類別 | 案例 | 涵蓋邊界 |
|----------|------|---------|
| `ProviderOfferCrudTest` | store 成功、缺必填 422、update 成功、update 他人 403、destroy 成功、destroy 他人 403、未認證 401 | happy path + ownership + auth |
| `ProviderScheduleCrudTest` | store 成功、store 他人課程 403、過去日期 422、update max_participants 成功、update 低於 current 422、update 他人 403、destroy 同時取消 bookings | happy path + 容量不變式 + ownership |
| `AdminUserManagementTest` | 列出 members 只含 member role、列出 providers 只含 provider role、toggle-active 改變 is_active、toggle-verified 改變 is_verified、非 admin 403 | 資料正確性 + 角色隔離 |
| `NotificationTriggerTest` | BookingCreated→provider、Confirmed→member、Rejected→member、CancelledByMember→provider、CancelledByProvider→member | 5 條直接觸發路徑,收件者各不同 |
| `BookingListTest` | `GET /api/member/bookings`MemberBookingController::index)、`GET /api/provider/bookings`ProviderBookingController::index)、`GET /api/admin/bookings`AdminBookingController::index)——各建兩方資料斷言隔離、admin 全看、未認證 401 | 路由已確認存在;角色資料隔離邊界 |