chore(openspec): 歸檔 auth-tests 變更並建立 auth-test-coverage 主規格
Run Tests / test (pull_request) Failing after 43s

34 項驗收情境與測試已 1:1 對應且全數通過,將變更移至
openspec/changes/archive/2026-06-07-auth-tests/,並依其內容建立
openspec/specs/auth-test-coverage/spec.md 作為主規格文件。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 04:02:05 +08:00
parent 6f446f601f
commit ca5b843362
6 changed files with 155 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
# auth-test-coverage Specification
## Purpose
TBD - created by archiving change auth-tests. Update Purpose after archive.
## Requirements
### Requirement: Auth 登入/註冊/登出測試覆蓋(三角色)
測試套件 SHALL 對 member、provider、admin 三個角色各自驗證以下場景,確保回歸時能即時偵測。
#### Scenario: Member 註冊成功
- **WHEN** 送出有效的 name / email / password / password_confirmation 至 `POST /api/member/register`
- **THEN** 回傳 HTTP 201body 包含 `{ status: true, data: { user } }`DB 存在對應 member 用戶
#### Scenario: Member 重複 Email 註冊失敗
- **WHEN** 送出已存在的 email 至 `POST /api/member/register`
- **THEN** 回傳 HTTP 422
#### Scenario: Member 登入成功
- **WHEN** 送出正確的 email / password 至 `POST /api/member/login`
- **THEN** 回傳 HTTP 200body 包含 `{ status: true, data: { token, token_type: "Bearer", user } }`
#### Scenario: Member 登入密碼錯誤
- **WHEN** 送出存在的 email 但錯誤的 password
- **THEN** 回傳 HTTP 401`{ status: false, message: "電子郵件或密碼錯誤" }`
#### Scenario: Member 非活躍帳號拒絕登入
- **WHEN** 送出 is_active=false 帳號的正確帳密
- **THEN** 回傳 HTTP 403
#### Scenario: Provider 帳號無法呼叫 Member 登入
- **WHEN** role=provider 的帳號嘗試 `POST /api/member/login`
- **THEN** 回傳 HTTP 401`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在,不洩漏帳號實際所屬角色)
#### Scenario: Member 登出成功
- **WHEN** 已登入 Member 送出 `POST /api/member/logout`(帶有效 Bearer token
- **THEN** 回傳 HTTP 200token 被撤銷(後續以此 token 請求受保護資源應得 401)
#### Scenario: Provider 登出成功
- **WHEN** 已登入 Provider 送出 `POST /api/provider/logout`(帶有效 Bearer token
- **THEN** 回傳 HTTP 200token 被撤銷(後續以此 token 請求受保護資源應得 401)
#### Scenario: Admin 登出成功
- **WHEN** 已登入 Admin 送出 `POST /api/admin/logout`(帶有效 Bearer token
- **THEN** 回傳 HTTP 200token 被撤銷(後續以此 token 請求受保護資源應得 401)
#### Scenario: Provider 帳號無法呼叫 Admin 登入
- **WHEN** role=provider 的帳號嘗試 `POST /api/admin/login`
- **THEN** 回傳 HTTP 401`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在)
#### Scenario: Provider 註冊成功
- **WHEN** 送出有效的 name / email / password / password_confirmation 至 `POST /api/provider/register`
- **THEN** 回傳 HTTP 201body 包含 `{ status: true, data: { user } }`DB 存在對應 provider 用戶
#### Scenario: Provider 重複 Email 註冊失敗
- **WHEN** 送出已存在的 email 至 `POST /api/provider/register`
- **THEN** 回傳 HTTP 422
#### Scenario: Provider 登入成功
- **WHEN** 送出正確的 email / password 至 `POST /api/provider/login`
- **THEN** 回傳 HTTP 200body 包含 `{ status: true, data: { token, token_type: "Bearer", user } }`
#### Scenario: Provider 非活躍帳號拒絕登入
- **WHEN** 送出 is_active=false 的 provider 帳號正確帳密
- **THEN** 回傳 HTTP 403
#### Scenario: Member 帳號無法呼叫 Provider 登入
- **WHEN** role=member 的帳號嘗試 `POST /api/provider/login`
- **THEN** 回傳 HTTP 401`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在)
#### Scenario: Admin 註冊成功
- **WHEN** 送出有效的 name / email / password / password_confirmation 至 `POST /api/admin/register`
- **THEN** 回傳 HTTP 201body 包含 `{ status: true, data: { user } }`DB 存在對應 admin 用戶
#### Scenario: Admin 重複 Email 註冊失敗
- **WHEN** 送出已存在的 email 至 `POST /api/admin/register`
- **THEN** 回傳 HTTP 422
#### Scenario: Admin 登入成功
- **WHEN** 送出正確的 email / password 至 `POST /api/admin/login`
- **THEN** 回傳 HTTP 200body 包含 `{ status: true, data: { token, token_type: "Bearer", user } }`
#### Scenario: Admin 登入非 admin 帳號失敗
- **WHEN** role=member 帳號嘗試 `POST /api/admin/login`
- **THEN** 回傳 HTTP 401`{ status: false, message: "電子郵件或密碼錯誤" }`(查詢以 role 過濾,跨角色帳號視同不存在)
---
### Requirement: 帳號鎖定測試覆蓋(P2 Fixed Window
測試套件 SHALL 驗證帳號鎖定邏輯的全部關鍵路徑,以防止回歸破壞安全機制。
#### Scenario: 失敗次數未達閾值不鎖定
- **WHEN** 同一帳號連續登入失敗 4 次
- **THEN** 每次均回傳 HTTP 401,帳號未鎖定
#### Scenario: 第 5 次失敗當場觸發鎖定
- **WHEN** 同一帳號連續登入失敗第 5 次
- **THEN** 回傳 HTTP 423body 包含 `{ status: false, locked_until: "<ISO8601>" }`
#### Scenario: 鎖定期間任何登入均被拒絕
- **WHEN** 帳號已鎖定,使用者送出(含正確密碼的)登入請求
- **THEN** 回傳 HTTP 423,不驗證密碼
#### Scenario: 不存在帳號不累計失敗計數
- **WHEN** 以不存在的 email 連續嘗試登入 10 次
- **THEN** 每次均回傳 HTTP 401Cache 中不建立計數 key,第 10 次後帳號不鎖定
#### Scenario: 登入成功後清除失敗計數
- **WHEN** 帳號失敗 3 次後成功登入,再失敗 4 次
- **THEN** 失敗計數從成功後重新累積,第 4 次仍回傳 401(不觸發鎖定)
#### Scenario: Email 大小寫視為同一帳號
- **WHEN** 同一帳號分別用 `user@example.com` 失敗 3 次、`User@Example.COM` 失敗 2 次
- **THEN** 第 5 次失敗(累計)觸發鎖定,回傳 HTTP 423
#### Scenario: Email 前後空白視為同一帳號
- **WHEN** 同一帳號分別用 `user@example.com` 失敗 3 次、` user@example.com `(含前後空白)失敗 2 次
- **THEN** 第 5 次失敗(累計)觸發鎖定,回傳 HTTP 423
#### Scenario: 鎖定 cache entry 不存在時帳號恢復可登入
- **WHEN** 帳號已鎖定,鎖定的 cache entry 被移除(模擬 TTL 自然過期)
- **THEN** 帳號可正常登入,回傳 HTTP 200
#### Scenario: 跨角色嘗試不會汙染或鎖定正確角色帳號
> 註:原情境假設「同一 email 同時擁有 member 與 provider 帳號」,但 `users.email` 在 DB 層級為全域 unique`database/migrations/2025_05_06_065906_create_orgin_table.php:18`),此前提不可能成立,故改寫為下列可達成、且驗證相同安全性質(角色間鎖定計數互相隔離)的版本。
- **WHEN** 一個僅存在 provider 帳號的 email,先對 `/api/member/login` 送出 4 次失敗嘗試,再對 `/api/provider/login` 送出 4 次失敗嘗試
- **THEN** member namespace 不建立計數 key(帳號不存在於該 namespace);provider namespace 計數累積為 4、未達閾值不鎖定;provider 帳號仍可用正確密碼正常登入
#### Scenario: Fixed Window — 失敗不延長 TTL
- **WHEN** 帳號失敗 4 次後,第 4 次失敗發生於 TTL 將到期前
- **THEN** Cache TTL 不被重設,`locked_until` 仍為第一次失敗時寫入的時間
#### Scenario: `locked_until` 來自 companion key
- **WHEN** 帳號觸發鎖定,回傳 HTTP 423
- **THEN** response body 的 `locked_until` 等於 `login_expires_at:{role}:{email}` cache key 的值
---
### Requirement: OAuth State 驗證測試覆蓋(P2
測試套件 SHALL 驗證 OAuth callback 的 state 比對邏輯,涵蓋正常、缺失、不符三種情境。
#### Scenario: State 缺失時 redirect 至錯誤頁,且不呼叫 Socialite
- **WHEN** 直接呼叫 `GET /auth/google/callback` 而不帶 `state` 參數(session 無 `oauth_state`
- **THEN** 後端 redirect 至包含 `error=oauth_failed` 的 URL,不繼續登入流程;Socialite 的 `user()` 不被呼叫
#### Scenario: State 不符時 redirect 至錯誤頁,且不呼叫 Socialite
- **WHEN** callback 帶 `state=wrong_value`,但 session 中 `oauth_state` 為不同值
- **THEN** 後端 redirect 至包含 `error=oauth_failed` 的 URLSocialite 的 `user()` 不被呼叫
#### Scenario: State 正確時完成 OAuth 登入
- **WHEN** callback 帶的 `state` 與 session 中 `oauth_state` 一致,且 Socialite 回傳有效 Google user
- **THEN** 後端完成登入,redirect 至前端並附帶 tokenURL fragment `#token=...`
#### Scenario: State 成功驗證後從 session 消耗
- **WHEN** callback 帶正確 `state` 完成一次登入後,再次以相同 `state` 呼叫 callback
- **THEN** session 中已無 `oauth_state`,視為 state 缺失,redirect 至 `error=oauth_failed`