6f446f601f
新增 34 個 Feature 測試(AuthLoginTest 19、AuthLockoutTest 11、 AuthOAuthTest 4)涵蓋 member/provider/admin 的註冊、登入、登出與跨角色 隔離;P2 帳號鎖定的 Fixed Window 計數、companion cache key 與 email 正規化;以及 P2 OAuth state 的 CSRF 驗證與一次性消耗。 同時新增 .gitea/workflows/test.yml 在 push/PR 時於 CI 自動執行測試 (含 GD 等擴充套件安裝),並補上對應的 OpenSpec 變更文件 (proposal/design/spec/tasks,34 項驗收情境與測試 1:1 對應)。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.2 KiB
8.2 KiB
1. AuthLoginTest — 三角色登入/註冊/登出
- 1.1 [整合測試] 建立
tests/Feature/AuthLoginTest.php,加入RefreshDatabasetrait 與 helper methods(createMember、createProvider、createAdmin) - 1.2 [整合測試] 實作
test_member_register_success:POST/api/member/register回傳 201 + DB 有對應 member 用戶 - 1.3 [整合測試] 實作
test_member_register_duplicate_email_returns_422:重複 email 回傳 422 - 1.4 [整合測試] 實作
test_member_login_success:正確帳密回傳 200 + token - 1.5 [整合測試] 實作
test_member_login_wrong_password_returns_401:錯誤密碼回傳 401 - 1.6 [整合測試] 實作
test_member_login_inactive_account_returns_403:is_active=false 回傳 403 - 1.7 [整合測試] 實作
test_provider_account_cannot_use_member_login:role=provider 帳號打 member login 回傳 401(查詢以 role 過濾,跨角色視同帳號不存在) - 1.8 [整合測試] 實作
test_member_logout_revokes_token:登出後 token 失效(DB 確認 token 已刪除),且以同一 token 再打/api/member/profile得 401(需先Auth::forgetGuards()清除RequestGuard在同一測試內快取的已解析 user,否則會讀到快取而誤判為 200) - 1.9 [整合測試] 實作
test_provider_logout_revokes_token:Provider 登出後 token 失效,且以同一 token 再打/api/provider/profile得 401(同樣需Auth::forgetGuards()) - 1.10 [整合測試] 實作
test_admin_logout_revokes_token:Admin 登出後 token 失效,且以同一 token 再打/api/admin/profile得 401(同樣需Auth::forgetGuards()) - 1.12 [整合測試] 實作
test_provider_login_success:Provider 正確帳密回傳 200 + token - 1.13 [整合測試] 實作
test_provider_login_inactive_account_returns_403:is_active=false 回傳 403 - 1.14 [整合測試] 實作
test_member_account_cannot_use_provider_login:role=member 帳號打 provider login 回傳 401(查詢以 role 過濾,跨角色視同帳號不存在) - 1.15 [整合測試] 實作
test_admin_login_success:Admin 正確帳密回傳 200 + token - 1.16 [整合測試] 實作
test_member_cannot_use_admin_login:role=member 帳號打 admin login 回傳 401(查詢以 role 過濾,跨角色視同帳號不存在) - 1.17 [整合測試] 實作
test_provider_cannot_use_admin_login:role=provider 帳號打 admin login 回傳 401(查詢以 role 過濾,跨角色視同帳號不存在) - 1.18 [整合測試] 實作
test_provider_register_success:POST/api/provider/register回傳 201 + DB 有對應 provider 用戶(design.md 載明三角色註冊均須覆蓋,原 1.x 僅含 member 註冊測試,此處補齊) - 1.19 [整合測試] 實作
test_provider_register_duplicate_email_returns_422:重複 email 回傳 422 - 1.20 [整合測試] 實作
test_admin_register_success:POST/api/admin/register回傳 201 + DB 有對應 admin 用戶 - 1.21 [整合測試] 實作
test_admin_register_duplicate_email_returns_422:重複 email 回傳 422
2. AuthLockoutTest — P2 帳號鎖定
- 2.1 [整合測試] 建立
tests/Feature/AuthLockoutTest.php,setUp()加入Cache::flush()確保測試間隔離 - 2.2 [整合測試] 實作
test_four_failures_do_not_lock_account:同一帳號失敗 4 次均回傳 401,帳號未鎖定 - 2.3 [整合測試] 實作
test_fifth_failure_triggers_423_with_locked_until:第 5 次失敗回傳 423,body 含locked_untilISO 8601 欄位 - 2.4 [整合測試] 實作
test_locked_account_rejects_correct_password:鎖定後送出正確密碼仍回傳 423 - 2.5 [整合測試] 實作
test_nonexistent_email_does_not_increment_counter:不存在 email 失敗 10 次後 Cache 無計數 key,不觸發鎖定 - 2.6 [整合測試] 實作
test_successful_login_clears_failure_counter:失敗 3 次後成功登入,再失敗 4 次仍回 401(計數從 0 重算) - 2.7 [整合測試] 實作
test_email_case_normalization_counts_same_account:大小寫變體 email 累計失敗到同一 key,第 5 次觸發鎖定 - 2.8 [整合測試] 實作
test_email_trim_normalization_counts_same_account:前後空白 email 累計失敗到同一 key,第 5 次觸發鎖定 - 2.9 [整合測試] 實作
test_member_login_attempts_do_not_affect_provider_lockout_for_same_email:僅存在 provider 帳號的 email,對 member login 失敗 4 次(不增加計數)+ 對 provider login 失敗 4 次(計數=4,未鎖定,仍可正常登入)。原情境因users.email全域 unique 無法同時擁有 member+provider 帳號,已改寫並同步更新 spec.md - 2.10 [整合測試] 實作
test_account_can_login_after_lockout_entry_removed:鎖定後Cache::forget()移除 entry,帳號恢復可登入(回傳 200) - 2.11 [整合測試] 實作
test_locked_until_comes_from_companion_cache_key:423 response 的locked_until等於login_expires_at:member:{email}cache key 的值 - 2.12 [整合測試] 實作
test_repeated_failures_do_not_extend_lockout_window:對應 spec.md「Fixed Window — 失敗不延長 TTL」情境(spec 已定義但原 tasks 未列出,此處補上)。第 1 次失敗寫入login_expires_at後,第 2~4 次失敗不應更動該值(驗證recordLoginFailure只在計數 key 不存在時才寫入 companion key,不隨後續失敗重設視窗)
3. AuthOAuthTest — P2 OAuth State 驗證
- 3.1 [整合測試] 建立
tests/Feature/AuthOAuthTest.php,加入RefreshDatabasetrait;引入Laravel\Socialite\Facades\Socialite - 3.2 [整合測試] 實作
test_oauth_callback_without_state_redirects_to_error:不帶 state 參數、session 無oauth_state,斷言 redirect 含error=oauth_failed,且 Socialiteuser()不被呼叫(使用shouldNotReceive) - 3.3 [整合測試] 實作
test_oauth_callback_with_wrong_state_redirects_to_error:session 設oauth_state=correct,帶state=wrong,斷言 redirect 含error=oauth_failed,且 Socialiteuser()不被呼叫 - 3.4 [整合測試] 實作
test_oauth_callback_with_correct_state_completes_login:session 設oauth_state=abc,帶state=abc,Socialite mock 回傳假 Google user,斷言 redirect URL 含#token= - 3.5 [整合測試] 實作
test_oauth_state_is_consumed_after_successful_callback:正確 state 完成一次登入後,再以相同 state 呼叫 callback,斷言第二次 redirect 含error=oauth_failed(state 一次性消耗)
4. 驗收確認
- 4.1 [整合測試] 執行
php artisan test --filter AuthLoginTest全數通過,無 skip(19 passed / 60 assertions) - 4.2 [整合測試] 執行
php artisan test --filter AuthLockoutTest全數通過,無 skip(11 passed / 44 assertions) - 4.3 [整合測試] 執行
php artisan test --filter AuthOAuthTest全數通過,無 skip(4 passed / 12 assertions) - 4.4 [整合測試] 在容器內執行
docker exec cfdive-app php artisan test(host PHP 缺 GD 擴充套件,須在容器內跑才能涵蓋CourseImageTest),結果:98 passed / 256 assertions,全數通過,無回歸。本次新增的 Auth 測試均已通過:AuthLoginTest 19、AuthLockoutTest 11、AuthOAuthTest 4(合計 34,OAuth 部分已於 4.3 單獨驗證)
過程中排除的兩類既存失敗(均已解決,非本次改動引入):
CourseImageTest(11 個):原本在 host PHP 跑會因缺 GD 擴充套件報LogicException: GD extension is not installed.;確認專案Dockerfile已內建 GD,改用docker exec cfdive-app php artisan test在容器內執行即全數通過 —— 純屬執行環境差異,非程式或測試邏輯問題,往後請統一在容器內跑測試AuthRateLimitTest(2 個):member/provider login exceeds limit returns 429原本預期 429 實得 401。根因是過期測試——commit2a0e9255(P2 帳號鎖定上線時)刻意將 member/provider 的 IP throttle 從5,1調整為10,1(避免 IP throttle 搶在帳號鎖定前觸發),但測試的迴圈次數與註解仍停留在舊版5,1。已同步修正測試的迴圈次數(5→10)與區塊註解,使其對齊現行throttle:10,1設定,修正後全數通過