US-002 角色權限控制 - 手動測試指南
測試日期: ____________ 測試人員: ____________ 測試環境:
npm run dev(localhost:3000)
測試目標
- 驗證不同角色登入後,Application Launcher 依據角色層級正確顯示可用 Applet
- 驗證 Mock API 的權限檢查中間件是否正確運作
- 確保不同角色的用戶只能訪問其授權的 API 和頁面
測試前準備
1. 啟動開發伺服器
npm run dev
2. 開啟瀏覽器工具
- 開發者工具(F12)→ Network 標籤(檢視 API 請求)
- 開發者工具(F12)→ Console 標籤(查看審計日誌)
3. 測試帳號
根據 src/mocks/data/seeds.ts,可用測試帳號:
| 角色 | 中文名稱 | Username (Email) | Password | 權限層級 |
|---|---|---|---|---|
| ROLE_ADMIN | 系統管理員 | admin@florist.com | Password123! | 5 (最高) |
| ROLE_OWNER | 店主 | owner@florist.com | Password123! | 4 |
| ROLE_SALES | 店員 | staff@florist.com | Password123! | 3 |
| ROLE_FLORIST | 花藝設計師 | designer@florist.com | Password123! | 2 |
| ROLE_DELIVERY | 送貨員 | delivery@florist.com | Password123! | 1 (最低) |
測試場景
Scenario 0: Application Launcher 依角色層級顯示 Applet ✅
目標: 驗證不同角色登入後,Application Launcher 依據角色層級正確過濾可用 Applet
操作步驟
-
以系統管理員登入
- 用戶名:
admin@florist.com - 密碼:
Password123! - 點擊 Header 右側的九宮格圖示(Grid3x3)開啟 Application Launcher
- ✅ 驗證: Application Launcher 顯示完整 Applet 列表:
- 首頁、訂單管理、客戶管理、商品管理、報表分析、系統設定
- 用戶名:
-
登出並以店主登入
- 用戶名:
owner@florist.com - 點擊九宮格圖示開啟 Application Launcher
- ✅ 驗證: Application Launcher 顯示:
- 首頁、訂單管理、客戶管理、商品管理、報表分析
- ✅ 驗證: 「系統設定」Applet 不可見
- 用戶名:
-
登出並以店員登入
- 用戶名:
staff@florist.com - 點擊九宮格圖示開啟 Application Launcher
- ✅ 驗證: Application Launcher 顯示:
- 首頁、訂單管理、客戶管理
- ✅ 驗證: 「商品管理」、「報表分析」、「系統設定」Applet 不可見
- 用戶名:
-
登出並以設計師登入
- 用戶名:
designer@florist.com - 點擊九宮格圖示開啟 Application Launcher
- ✅ 驗證: Application Launcher 僅顯示:首頁
- 用戶名:
-
登出並以送貨員登入
- 用戶名:
delivery@florist.com - 點擊九宮格圖示開啟 Application Launcher
- ✅ 驗證: Application Launcher 僅顯示:首頁
- 用戶名:
預期結果
- ✅ 角色層級越高,可見的 Applet 數量越多
- ✅ 高權限角色可看到低權限角色的所有 Applet
- ✅ Applet 依據
requiredRoles正確過濾
Scenario 1: 店員 (ROLE_SALES) 可以創建訂單 ✅
預期結果: 成功 (201 Created)
操作步驟:
- 以
staff@florist.com/Password123!登入 - 在瀏覽器 Console 執行以下代碼獲取 Access Token:
// 獲取當前 Redux store 中的 Access Token
const token = window.__REDUX_STORE__.getState().iam.me.authorization.access_token;
console.log('Access Token:', token);
- 使用 Token 創建訂單:
// 創建訂單 (需要 ROLE_SALES 或更高角色)
const token = window.__REDUX_STORE__.getState().iam.me.authorization.access_token;
function getFormattedDatePlusThreeDays() {
const today = new Date();
today.setDate(today.getDate() + 3);
const year = today.getFullYear();
const month = (today.getMonth() + 1).toString().padStart(2, '0'); // Months are 0-indexed
const day = today.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
}
const deliveryDate = getFormattedDatePlusThreeDays();
fetch('/api/v1/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
customerId: 'cust-123', // 李大華(種子數據中的測試客戶)
deliveryAddress: '台北市信義區',
deliveryPhone: '0912345678',
deliveryDate: deliveryDate,
deliveryTimeSlot: 'morning', // 配送時段:morning/afternoon/evening
items: [
{
productId: 'prod-1',
quantity: 1,
}
],
})
})
.then(r => r.json())
.then(console.log)
.catch(console.error);
驗證:
- Response status = 201
- Response body 包含新訂單資料(orderNumber, id, status = 'pending_confirmation')
- Console 顯示成功訊息
Scenario 2: 花藝設計師 (ROLE_FLORIST) 嘗試創建訂單被拒絕 ❌
預期結果: 失敗 (403 Forbidden)
操作步驟:
- 登出當前用戶
- 以
designer@florist.com/Password123!登入 - 在瀏覽器 Console 執行(同 Scenario 1 步驟 3 的代碼)
驗證:
- Response status = 403
- Response body 包含錯誤訊息:
{
"message": "權限不足:此操作需要 ROLE_SALES 或更高角色,您的角色為 ROLE_FLORIST",
"status": 403,
"error": "PERMISSION_DENIED",
...
}
- Browser Console 顯示審計日誌:
[MSW Audit] 403 Forbidden - User role: ROLE_FLORIST, Method: POST, Path: /api/v1/orders, Required: ROLE_SALES
Scenario 3: 店主 (ROLE_OWNER) 可以刪除訂單 ✅
預期結果: 成功 (200 OK)
操作步驟:
- 登出當前用戶
- 以
owner@florist.com/Password123!登入 - 先查看訂單列表,獲取訂單 ID:
const token = window.__REDUX_STORE__.getState().iam.me.authorization.access_token;
let orderId;
fetch('/api/v1/orders', {
headers: {
'Authorization': `Bearer ${token}`,
}
})
.then(r => r.json())
.then(orders => {
console.log('訂單列表:', orders);
if (orders.length > 0) {
console.log('第一筆訂單 ID:', orders[0].id);
orderId = orders[0].id;
}
});
- 刪除訂單(需要 ROLE_OWNER 或更高角色):
const token = window.__REDUX_STORE__.getState().iam.me.authorization.access_token;
fetch(`/api/v1/orders/${orderId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
}
})
.then(r => r.json())
.then(console.log)
.catch(console.error);
驗證:
- Response status = 200
- Response body =
{ "message": "訂單已刪除" }
Scenario 4: 店員 (ROLE_SALES) 嘗試刪除訂單被拒絕 ❌
預期結果: 失敗 (403 Forbidden)
操作步驟:
- 登出當前用戶
- 以
staff@florist.com/Password123!登入 - 執行同 Scenario 3 步驟 4 的刪除操作
驗證:
- Response status = 403
- Response body 包含權限不足錯誤訊息
- Console 顯示審計日誌
Scenario 5: 未登入用戶訪問訂單 API 被拒絕 ❌
預期結果: 失敗 (401 Unauthorized)
操作步驟:
- 登出所有用戶(或使用無痕模式)
- 在瀏覽器 Console 執行(不帶 Authorization header):
fetch('/api/v1/orders')
.then(async r => {
console.log('Status:', r.status);
console.log('Body:', await r.json());
})
.catch(console.error);
驗證:
- Response status = 401
- Response body 包含 "Unauthorized" 錯誤訊息
Scenario 6: 驗證 Token 自動附加功能 ✅
預期結果: 透過應用程式 UI 發出的 API 請求會自動附加 Authorization header
操作步驟:
- 以任意角色登入(例如:
staff@florist.com/Password123!) - 在應用程式中瀏覽訂單列表頁面(透過 Application Launcher 開啟「訂單管理」)
- 開啟瀏覽器開發者工具(F12) → Network 標籤
- 刷新頁面(F5)或執行任何觸發 API 請求的操作
- 在 Network 標籤中找到
orders請求 - 點擊該請求 → Headers 標籤
驗證:
- Request Headers 應包含:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
備註:
- ✅ 應用程式內的
apiClient(Axios) 會自動從 Redux store 讀取 token 並附加到請求中 - ✅ 這個機制在
src/services/apiClient.ts的 request interceptor 實作 - ❌ 在 Console 中手動執行的
fetch()不會自動附加 Token(需手動設置)
替代測試方式(透過 Console 驗證 Redux 中的 Token):
// 檢查 Redux store 中是否有 token
const token = window.__REDUX_STORE__.getState().iam.me.authorization.access_token;
console.log('Token exists:', !!token);
console.log('Token preview:', token?.substring(0, 50) + '...');
權限矩陣
| API 端點 | System Admin | Shop Owner | Sales Staff | Florist | Delivery |
|---|---|---|---|---|---|
| POST /orders | ✅ | ✅ | ✅ | ❌ | ❌ |
| GET /orders | ✅ | ✅ | ✅ | ❌ | ❌ |
| PUT /orders/:id | ✅ | ✅ | ✅ | ❌ | ❌ |
| DELETE /orders/:id | ✅ | ✅ | ❌ | ❌ | ❌ |
| GET /products | ✅ | ✅ | ✅ | ❌ | ❌ |
| POST /products | ✅ | ✅ | ❌ | ❌ | ❌ |
| GET /settings | ✅ | ❌ | ❌ | ❌ | ❌ |
測試結果記錄
完成測試後,請在下方記錄結果:
✅ 通過的場景
前端權限(Application Launcher 過濾)
- Scenario 0: Application Launcher 依角色層級顯示 Applet
API 權限(Mock API)
- Scenario 1: 店員可以創建訂單
- Scenario 2: 設計師無法創建訂單
- Scenario 3: 店主可以刪除訂單
- Scenario 4: 店員無法刪除訂單
- Scenario 5: 未登入用戶被拒絕
- Scenario 6: Token 自動附加
⚠️ 發現的問題
(記錄任何異常或錯誤)
測試檢查清單
Application Launcher 權限過濾
- 系統管理員看到所有 Applet(6 項)
- 店主看到 5 項 Applet(無系統設定)
- 店員看到 3 項 Applet(首頁、訂單、客戶)
- 設計師僅看到首頁
- 送貨員僅看到首頁
API 權限矩陣
- 高層級角色可執行低層級角色的操作
- 未授權請求返回 403 Forbidden
- 未登入請求返回 401 Unauthorized
- 審計日誌正確記錄權限拒絕事件
UI/UX
- Application Launcher 使用類似 macOS Launchpad 的視覺設計
- Applet 圖示使用彩色圓角背景
- 分類標籤可過濾 Applet
- 搜尋功能可快速定位 Applet
測試結論
- 測試通過: ☐ 是 / ☐ 否
- 備註: ___________________________________________________________
- 測試人員簽名: ________________ 日期: ________________
最後更新: 2025-12-10