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

7.9 KiB
Raw Blame History

0. 前置確認(實作前一次性)

  • 0.1 確認 phpunit.xmlCACHE_STORE=arrayLaravel 11 array store 支援 tags,若非 array/redis 須先修正再實作)
  • 0.2 確認現有認證模式:所有新測試使用 $this->actingAs($user) + postJson/putJson,不手動建立 Sanctum token
  • 0.3 確認 factory 狀況:DivingOfferCourseScheduleBooking 均無 factory,一律使用 Model::create([...]) 直接建立,不新增 factory

1. Provider 課程 CRUD 測試

  • 1.1 [整合測試] 建立 tests/Feature/ProviderOfferCrudTest.php,加入 private helpers
    • makeProvider(): UserUser::create + ProviderProfile::createis_verified=true
    • makeOffer(User $provider): DivingOfferDivingOffer::create 最小必填欄位)
  • 1.2 [整合測試] test_provider_creates_offer_successfullyPOST 201data.provider_id = auth provider idDB 存在
  • 1.3 [整合測試] test_create_offer_missing_required_field_returns_422:缺 title 送出,回傳 422(代表性一個欄位即可)
  • 1.4 [整合測試] test_provider_updates_own_offerPUT 200DB 欄位已變更
  • 1.5 [整合測試] test_provider_cannot_update_others_offerProvider B 更新 Provider A 的課程,403
  • 1.6 [整合測試] test_provider_deletes_own_offerDELETE 200DB 記錄不存在
  • 1.7 [整合測試] test_provider_cannot_delete_others_offer403
  • 1.8 [整合測試] test_unauthenticated_request_is_rejected:無 token POST401

2. Provider 時段管理測試

  • 2.1 [整合測試] 建立 tests/Feature/ProviderScheduleCrudTest.php,加入 private helpers
    • makeProvider(): User
    • makeOffer(User $provider): DivingOffer
    • makeSchedule(DivingOffer $offer, int $currentParticipants = 0): CourseSchedule
  • 2.2 [整合測試] test_provider_creates_schedule_successfullyPOST 201status=open
  • 2.3 [整合測試] test_provider_cannot_create_schedule_for_others_offer403
  • 2.4 [整合測試] test_past_date_returns_422scheduled_date = yesterday422(代表性驗證案例)
  • 2.5 [整合測試] test_provider_updates_schedule_max_participantsPUT 200DB 更新
  • 2.6 [整合測試] test_max_participants_below_current_returns_422current_participants=3PUT max_participants=2422
  • 2.7 [整合測試] test_provider_cannot_update_others_schedule403
  • 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 使用者管理測試

  • 3.1 [整合測試] 建立 tests/Feature/AdminUserManagementTest.php,加入 private helpers
    • makeAdmin(): Userrole=admin
    • makeMember(): Userrole=member + MemberProfile
    • makeProvider(bool $isVerified = false): Userrole=provider + ProviderProfile
  • 3.2 [整合測試] test_admin_lists_members_only:建立 member + provider 各一,GET /api/admin/members 回傳 data 只含 member role
  • 3.3 [整合測試] test_admin_lists_providers_onlyGET /api/admin/providers 回傳 data 只含 provider role
  • 3.4 [整合測試] test_admin_toggles_member_active_status:目標 is_active=true → POST toggle-active → DB is_active=false
  • 3.5 [整合測試] test_admin_toggles_provider_verified_status:目標 is_verified=false → POST toggle-verified → DB is_verified=true(不重複 DivingOfferVisibilityTest 的快取副作用驗證,只斷言 DB 欄位與 HTTP 200
  • 3.6 [整合測試] test_non_admin_is_forbiddenrole=member actingAsGET /api/admin/members 回傳 403

4. 通知直接觸發路徑測試

收件者依實際 controller 實作(已查閱原始碼 + app/Notifications/ 確認 class 存在):

  • BookingCreatedNotificationMemberBookingController::store$provider->notify(new BookingCreatedNotification($booking))教練收到

  • BookingConfirmedNotificationProviderBookingController::confirm$booking->member->notify(...)學員收到

  • BookingRejectedNotificationProviderBookingController::reject$booking->member->notify(...)學員收到

  • BookingCancelledNotificationcancelledBy: 'member'):MemberBookingController::destroy$provider->notify(...)教練收到

  • BookingCancelledNotificationcancelledBy: 'provider'):ProviderBookingController::cancel$booking->member->notify(...)學員收到

  • 注意:member/provider cancel 使用同一個 class BookingCancelledNotificationassertSentTo 只需確認 class 名稱與收件者即可

  • 4.1 [整合測試] 建立 tests/Feature/NotificationTriggerTest.phpsetUp 加 Notification::fake();加入 helper 建立 member + provider(含 ProviderProfile + offer + scheduleopen, 未來日期) + pending booking

  • 4.2 [整合測試] test_booking_created_notifies_providerMember POST /api/member/bookingsassertSentTo($provider, BookingCreatedNotification::class)

  • 4.3 [整合測試] test_booking_confirmed_notifies_memberProvider PUT /api/provider/bookings/{id}/confirmassertSentTo($member, BookingConfirmedNotification::class)

  • 4.4 [整合測試] test_booking_rejected_notifies_memberProvider PUT /api/provider/bookings/{id}/rejectassertSentTo($member, BookingRejectedNotification::class)

  • 4.5 [整合測試] test_member_cancel_notifies_providerMember DELETE /api/member/bookings/{id}pending, 遠未來日期) → assertSentTo($provider, BookingCancelledNotification::class)

  • 4.6 [整合測試] test_provider_cancel_notifies_memberProvider PUT /api/provider/bookings/{id}/cancel(先 confirm 再 cancelassertSentTo($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 + adminEnsureAdmin 強制 403)→ AdminBookingController::index

  • 5.1 [整合測試] 建立 tests/Feature/BookingListTest.php,加入 helpers

    • makeProviderWithSchedule(): array(回傳 [provider, schedule],含 ProviderProfile + DivingOffer + CourseSchedule
    • makePendingBooking(User $member, CourseSchedule $schedule): Booking
  • 5.2 [整合測試] test_member_sees_only_own_bookingsMemberA 和 MemberB 各建一筆 pending booking(同一 schedule),actingAs MemberA → GET /api/member/bookings,斷言 data count=1 且 data[0].id = MemberA booking id

  • 5.3 [整合測試] test_provider_sees_only_own_course_bookingsProviderA 和 ProviderB 各有一筆 bookingactingAs ProviderA → GET /api/provider/bookings,斷言 data 中只含 ProviderA schedule 的 booking id

  • 5.4 [整合測試] test_admin_sees_all_bookings:建立 2 筆不同 member 的 bookingactingAs admin → GET /api/admin/bookings,斷言兩個 booking id 均在 data 中

  • 5.5 [整合測試] test_unauthenticated_request_returns_401:無 token GET /api/member/bookings401

6. 驗收

  • 6.1 容器內 php artisan test 全綠(基準 187 passed,預估完成後 ≥ 230 passed
  • 6.2 確認 5 個新測試類別的案例方法數與 spec scenarios 對應(可多不可少)
  • 6.3 確認 phpunit.xml 仍為 CACHE_STORE=array,未被意外改動
  • 6.4 將本 change 的 specs 增量套用至主規格 openspec/specs/feature-test-coverage/spec.md