Files
CFDivePlatform/openspec/changes/feature-test-coverage/tasks.md
T
a620906209 49a4b89655
Run Tests / test (pull_request) Successful in 1m51s
test(feature): 補齊五個核心業務流程 Feature test——Offer/Schedule CRUD、Admin 管理、通知觸發、預約列表
ProviderOfferCrudTest(7 案例):store/update/destroy 正常流程 + 所有權邊界(403)+ 未認證(401)
ProviderScheduleCrudTest(7 案例):store/update/destroy + 容量不變式(422)+ destroy 同步取消 active bookings
AdminUserManagementTest(5 案例):列表角色過濾 + toggle-active/toggle-verified DB 副作用 + 非 admin 403
NotificationTriggerTest(5 案例):5 條直接觸發路徑,收件者各不同(BookingCreated/CancelledByMember → provider;其餘 → member)
BookingListTest(4 案例):三角色資料隔離邊界 + 未認證 401

全套件 215 passed / 529 assertions,零 regression。
同步新增 openspec/changes/feature-test-coverage/ 與 openspec/specs/feature-test-coverage/spec.md。

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

84 lines
7.9 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.
## 0. 前置確認(實作前一次性)
- [x] 0.1 確認 `phpunit.xml``CACHE_STORE=array`Laravel 11 array store 支援 tags,若非 array/redis 須先修正再實作)
- [x] 0.2 確認現有認證模式:所有新測試使用 `$this->actingAs($user)` + `postJson/putJson`,不手動建立 Sanctum token
- [x] 0.3 確認 factory 狀況:`DivingOffer``CourseSchedule``Booking` 均無 factory,一律使用 `Model::create([...])` 直接建立,不新增 factory
## 1. Provider 課程 CRUD 測試
- [x] 1.1 [整合測試] 建立 `tests/Feature/ProviderOfferCrudTest.php`,加入 private helpers
- `makeProvider(): User`User::create + ProviderProfile::createis_verified=true
- `makeOffer(User $provider): DivingOffer`DivingOffer::create 最小必填欄位)
- [x] 1.2 [整合測試] `test_provider_creates_offer_successfully`POST 201data.provider_id = auth provider idDB 存在
- [x] 1.3 [整合測試] `test_create_offer_missing_required_field_returns_422`:缺 title 送出,回傳 422(代表性一個欄位即可)
- [x] 1.4 [整合測試] `test_provider_updates_own_offer`PUT 200DB 欄位已變更
- [x] 1.5 [整合測試] `test_provider_cannot_update_others_offer`Provider B 更新 Provider A 的課程,403
- [x] 1.6 [整合測試] `test_provider_deletes_own_offer`DELETE 200DB 記錄不存在
- [x] 1.7 [整合測試] `test_provider_cannot_delete_others_offer`403
- [x] 1.8 [整合測試] `test_unauthenticated_request_is_rejected`:無 token POST401
## 2. Provider 時段管理測試
- [x] 2.1 [整合測試] 建立 `tests/Feature/ProviderScheduleCrudTest.php`,加入 private helpers
- `makeProvider(): User`
- `makeOffer(User $provider): DivingOffer`
- `makeSchedule(DivingOffer $offer, int $currentParticipants = 0): CourseSchedule`
- [x] 2.2 [整合測試] `test_provider_creates_schedule_successfully`POST 201status=open
- [x] 2.3 [整合測試] `test_provider_cannot_create_schedule_for_others_offer`403
- [x] 2.4 [整合測試] `test_past_date_returns_422`scheduled_date = yesterday422(代表性驗證案例)
- [x] 2.5 [整合測試] `test_provider_updates_schedule_max_participants`PUT 200DB 更新
- [x] 2.6 [整合測試] `test_max_participants_below_current_returns_422`current_participants=3PUT max_participants=2422
- [x] 2.7 [整合測試] `test_provider_cannot_update_others_schedule`403
- [x] 2.8 [整合測試] `test_delete_schedule_marks_active_bookings_as_provider_cancelled`:建立一筆 pending + 一筆 confirmed bookingDELETE 200;斷言時段 status=`cancelled`(記錄保留);兩筆 booking status=`provider_cancelled`(記錄保留,不刪除);不在 pending/confirmed 的 booking 不受影響
## 3. Admin 使用者管理測試
- [x] 3.1 [整合測試] 建立 `tests/Feature/AdminUserManagementTest.php`,加入 private helpers
- `makeAdmin(): User`role=admin
- `makeMember(): User`role=member + MemberProfile
- `makeProvider(bool $isVerified = false): User`role=provider + ProviderProfile
- [x] 3.2 [整合測試] `test_admin_lists_members_only`:建立 member + provider 各一,GET /api/admin/members 回傳 data 只含 member role
- [x] 3.3 [整合測試] `test_admin_lists_providers_only`GET /api/admin/providers 回傳 data 只含 provider role
- [x] 3.4 [整合測試] `test_admin_toggles_member_active_status`:目標 is_active=true → POST toggle-active → DB is_active=false
- [x] 3.5 [整合測試] `test_admin_toggles_provider_verified_status`:目標 is_verified=false → POST toggle-verified → DB is_verified=true(不重複 DivingOfferVisibilityTest 的快取副作用驗證,只斷言 DB 欄位與 HTTP 200
- [x] 3.6 [整合測試] `test_non_admin_is_forbidden`role=member actingAsGET /api/admin/members 回傳 403
## 4. 通知直接觸發路徑測試
收件者依實際 controller 實作(已查閱原始碼 + app/Notifications/ 確認 class 存在):
- `BookingCreatedNotification``MemberBookingController::store``$provider->notify(new BookingCreatedNotification($booking))` → **教練**收到
- `BookingConfirmedNotification``ProviderBookingController::confirm``$booking->member->notify(...)` → **學員**收到
- `BookingRejectedNotification``ProviderBookingController::reject``$booking->member->notify(...)` → **學員**收到
- `BookingCancelledNotification`cancelledBy: 'member'):`MemberBookingController::destroy``$provider->notify(...)` → **教練**收到
- `BookingCancelledNotification`cancelledBy: 'provider'):`ProviderBookingController::cancel``$booking->member->notify(...)` → **學員**收到
- 注意:member/provider cancel 使用**同一個 class** `BookingCancelledNotification``assertSentTo` 只需確認 class 名稱與收件者即可
- [x] 4.1 [整合測試] 建立 `tests/Feature/NotificationTriggerTest.php`setUp 加 `Notification::fake()`;加入 helper 建立 member + provider(含 ProviderProfile + offer + scheduleopen, 未來日期) + pending booking
- [x] 4.2 [整合測試] `test_booking_created_notifies_provider`Member `POST /api/member/bookings``assertSentTo($provider, BookingCreatedNotification::class)`
- [x] 4.3 [整合測試] `test_booking_confirmed_notifies_member`Provider `PUT /api/provider/bookings/{id}/confirm``assertSentTo($member, BookingConfirmedNotification::class)`
- [x] 4.4 [整合測試] `test_booking_rejected_notifies_member`Provider `PUT /api/provider/bookings/{id}/reject``assertSentTo($member, BookingRejectedNotification::class)`
- [x] 4.5 [整合測試] `test_member_cancel_notifies_provider`Member `DELETE /api/member/bookings/{id}`pending, 遠未來日期) → `assertSentTo($provider, BookingCancelledNotification::class)`
- [x] 4.6 [整合測試] `test_provider_cancel_notifies_member`Provider `PUT /api/provider/bookings/{id}/cancel`(先 confirm 再 cancel`assertSentTo($member, BookingCancelledNotification::class)`
## 5. 預約列表端點測試
路由對應(已查閱 routes/api.php + EnsureAdmin middleware 確認):
- `GET /api/member/bookings` → middleware: `auth:sanctum`(無 role 檢查)→ `MemberBookingController::index`,隔離靠 `where member_id = auth()->id()`
- `GET /api/provider/bookings` → middleware: `auth:sanctum`(無 role 檢查)→ `ProviderBookingController::index`,隔離靠 `whereHas schedule.provider_id`
- `GET /api/admin/bookings` → middleware: `auth:sanctum` + `admin``EnsureAdmin` 強制 403)→ `AdminBookingController::index`
- [x] 5.1 [整合測試] 建立 `tests/Feature/BookingListTest.php`,加入 helpers
- `makeProviderWithSchedule(): array`(回傳 [provider, schedule],含 ProviderProfile + DivingOffer + CourseSchedule
- `makePendingBooking(User $member, CourseSchedule $schedule): Booking`
- [x] 5.2 [整合測試] `test_member_sees_only_own_bookings`MemberA 和 MemberB 各建一筆 pending booking(同一 schedule),actingAs MemberA → `GET /api/member/bookings`,斷言 data count=1 且 data[0].id = MemberA booking id
- [x] 5.3 [整合測試] `test_provider_sees_only_own_course_bookings`ProviderA 和 ProviderB 各有一筆 bookingactingAs ProviderA → `GET /api/provider/bookings`,斷言 data 中只含 ProviderA schedule 的 booking id
- [x] 5.4 [整合測試] `test_admin_sees_all_bookings`:建立 2 筆不同 member 的 bookingactingAs admin → `GET /api/admin/bookings`,斷言兩個 booking id 均在 data 中
- [x] 5.5 [整合測試] `test_unauthenticated_request_returns_401`:無 token `GET /api/member/bookings`401
## 6. 驗收
- [x] 6.1 容器內 `php artisan test` 全綠(基準 187 passed,預估完成後 ≥ 230 passed
- [x] 6.2 確認 5 個新測試類別的案例方法數與 spec scenarios 對應(可多不可少)
- [x] 6.3 確認 `phpunit.xml` 仍為 `CACHE_STORE=array`,未被意外改動
- [x] 6.4 將本 change 的 specs 增量套用至主規格 `openspec/specs/feature-test-coverage/spec.md`