# Test Plan: Spec 2 階段抽象整合

> **產出日期**：2026-05-12
> **對應 spec**：[design.md](./design.md) v2、[../README.md](../README.md)
> **對應 plan**：[implementation-plan.md](./implementation-plan.md) v1
> **跨 repo**：compliance-manager-be / compliance-manager-fe / compliance-manager-test

---

## 1. Acceptance Criteria 對應

從 design.md §二 需求摘要 7 條 + §九 6 條決議萃取，作為測試覆蓋依據。

| AC ID | 條件 | 來源 |
|---|---|---|
| AC-1 | AP 建立時同步寫 `compliance.assessment_plan_extensions` 列（含 workflow_execution_uid + flow_template_snapshot_uid），1:1 強制 | design §9.1, plan §B.5 |
| AC-2 | AP 建立完成後 BPMN main workflow 啟動且停在 planning UserTask；AP.status 維持 `preparing` | design §9.5, plan §B.5 |
| AC-3 | `GET .../stage/info` 回傳當前 stage code / name / progress / precondition / user_can_advance | design §四, plan §B.3 |
| AC-4 | Progress bar 解析 BPMN snapshot 取出 4 stage 序列；已完成標 done、當前標 current、未到標 pending | design §四 |
| AC-5 | `POST .../stage/advance` 依 stage handler key 呼叫對應 OSCAL API（activate / launch_audit / confirm_audit / close_round） | design §9.3, plan §B.1 |
| AC-6 | planning 推進 → AP `preparing→active`；task_execution → `active→auditing`；audit → `auditing→remediation`（或 closed if no poam）；poam → `remediation→closed` | design §二 §9.5, GRC §2.1 |
| AC-7 | 推進前驗證使用者擁有 stage `default_main_roles`（或 BPMN UserTask 覆寫的 main_roles）；不足 → 403 `GRC_STAGE_ROLE_FORBIDDEN` | design §9.7, plan §B.3 |
| AC-8 | 推進前跑 precondition.check()；fail 且非 force → 412 `GRC_STAGE_PRECONDITION_FAILED` 含 reason_i18n_key + context | design §9.2 §9.7 |
| AC-9 | manager 可帶 `force=true` bypass precondition；非 manager 不可 | design §9.2 |
| AC-10 | task_execution precondition = TasksThresholdCheck（incomplete > 0 時 fail） | design §9.3 stage map |
| AC-11 | audit precondition = AllControlsVerdictedWithFinding（未判定或 fail/partial 無 finding → 412） | design §9.3, GRC `GRC_412003`/`GRC_412011` |
| AC-12 | poam precondition = AllPoamClosed（仍有 open POA&M → 412） | design §9.3, GRC `GRC_412005` |
| AC-13 | AP 未綁 workflow_execution → 404 `GRC_AP_NO_WORKFLOW_BINDING` | design §9.7 |
| AC-14 | BPMN 已到 EndEvent / 找不到下一節點 → 400 `GRC_STAGE_NO_NEXT_NODE` | design §9.7 |
| AC-15 | Stage handler key 未在 Registry 註冊 → 400 `GRC_STAGE_HANDLER_NOT_REGISTERED` | design §9.7 |
| AC-16 | BPMN UserTask 未綁定 stage object → 400 `GRC_STAGE_OBJECT_NOT_BOUND` | design §9.7 |
| AC-17 | EndEvent 觸發時不重複改 AP.status（close_round handler 為 single source of truth）；移除 raw SQL `ExtAssessmentPlanRepoImpl` | design §9.4, plan §B.6 |
| AC-18 | poam on_complete v1 直接走 close_round 結案；loop back 路徑 BPMN 圖可見但 runtime 不走 | design §9.4 |
| AC-19 | Spec 2 使用 hardcode `builtin-full-audit` 範本；`flow_template_snapshot_uid` 欄位預留供 Spec 3 user 選範本 | design §9.6, plan §B.5 |
| AC-20 | FE FlowPhaseBanner 渲染 stage name / progress / hint / 推進按鈕；precondition fail → 按鈕 disable + tooltip；manager 可 ConfirmDialog force | design §四 §9.2 |
| AC-21 | FE 4 個 routed view（settings / overview / audit / poam）掛 Banner；既有 4 個推進按鈕拆除；4 個導航按鈕不動 | design §三, plan §D.1~D.4 |
| AC-22 | task_execution stage Banner 掛在 overview 頁；待辦任務頁不掛 Banner | design §四, plan §D.5 |
| AC-23 | flow_engine 模組不 import grc / oscal；handler 透過 DI container 註冊到 Registry（架構約束） | design §9.3 |

---

## 2. BE 測試（pytest，`compliance-manager-be/test/`）

### 2.1 Repository / Mapper 層

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_assessment_plan_extension_repo.py` | `test_get_by_ap_id_returns_entity` | 既有列可查 | AC-1 |
| 同上 | `test_get_by_ap_id_not_found_returns_none` | 無對應列回 None | AC-1 |
| 同上 | `test_get_by_ap_uid_via_join` | 透過 AP uid join 查到 ext | AC-1 |
| 同上 | `test_add_extension_persists_workflow_uid` | INSERT 後 workflow_execution_uid 寫入 | AC-1 |
| 同上 | `test_set_workflow_execution_uid_updates_audit_fields` | updated_user / updated_at 自動更新 | AC-1 |
| 同上 | `test_unique_constraint_blocks_duplicate_ap_id` | 同 ap_id INSERT 二次拋 IntegrityError | AC-1 |
| 同上 | `test_cascade_delete_when_ap_deleted` | AP delete 時 ext 跟著刪 | AC-1 |
| `test_assessment_plan_extension_mapper.py` | `test_entity_to_model_roundtrip` | Mapper 雙向轉換無 lossy | AC-1 |

### 2.2 Domain Registry / Interface 層

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_stage_completion_registry.py` | `test_register_handler_then_get_returns_same_instance` | register / get_handler | AC-23 |
| 同上 | `test_register_precondition_then_get` | register / get_precondition | AC-23 |
| 同上 | `test_get_handler_missing_key_returns_none` | 未註冊回 None | AC-15 |
| 同上 | `test_register_handler_overwrites_same_key` | 同 key 重註冊覆蓋 | AC-23 |
| 同上 | `test_precondition_result_default_passed_no_reason` | PreconditionResult dataclass 預設值 | AC-8 |

### 2.3 OSCAL Stage Handlers（4 個，`app/grc/service/oscal_stage_handlers.py`）

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_oscal_stage_handlers.py` | `test_planning_handler_key_is_oscal_planning_activate` | key 字串對齊 stage_objects.complete_handler_key | AC-5 |
| 同上 | `test_planning_handler_execute_calls_activate_project` | mock OscalAuditService.activate_project 被呼叫 + 參數正確 | AC-5, AC-6 |
| 同上 | `test_planning_handler_execute_passes_force_flag` | ctx.force=True 傳給 activate_project | AC-9 |
| 同上 | `test_task_execution_handler_key_correct` | key=`oscal.task_execution.launch_audit` | AC-5 |
| 同上 | `test_task_execution_handler_calls_launch_audit` | 呼叫 OscalAuditService.launch_audit | AC-5, AC-6 |
| 同上 | `test_audit_handler_key_correct` | key=`oscal.audit.confirm_audit` | AC-5 |
| 同上 | `test_audit_handler_calls_confirm_audit` | 呼叫 OscalAuditService.confirm_audit | AC-5, AC-6 |
| 同上 | `test_poam_handler_key_correct` | key=`oscal.poam.close_round` | AC-5 |
| 同上 | `test_poam_handler_calls_close_round` | 呼叫 OscalAuditService.close_round | AC-5, AC-6, AC-18 |
| 同上 | `test_handler_propagates_oscal_service_exception` | 下層 raise → handler 不吞 | AC-5 |

### 2.4 OSCAL Stage Preconditions（3 個，`app/grc/service/oscal_stage_preconditions.py`）

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_oscal_stage_preconditions.py` | `test_tasks_threshold_passed_when_all_complete` | incomplete=0 → passed=True | AC-10 |
| 同上 | `test_tasks_threshold_failed_returns_reason_and_context` | incomplete=12, total=50 → passed=False + reason_key + context={"incomplete":12} | AC-10, AC-8 |
| 同上 | `test_all_controls_verdicted_passes_when_all_done` | 所有控制項 verdicted + fail/partial 有 finding → passed=True | AC-11 |
| 同上 | `test_all_controls_verdicted_fails_when_unverdicted_exists` | 未判定 → passed=False, reason 對齊 GRC_412003 邏輯 | AC-11 |
| 同上 | `test_all_controls_verdicted_fails_when_fail_without_finding` | fail/partial 無 finding → passed=False, reason 對齊 GRC_412011 | AC-11 |
| 同上 | `test_all_poam_closed_passes_when_no_open_poam` | open=0 → passed=True | AC-12 |
| 同上 | `test_all_poam_closed_fails_with_open_count_in_context` | 有 open → passed=False + context={"open_count": N} | AC-12 |

### 2.5 StageAdvanceService（核心 app service）

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_stage_advance_service.py` | `test_get_current_stage_info_happy_path` | 回傳 current_stage + progress + precondition + user_can_advance | AC-3, AC-4 |
| 同上 | `test_get_current_stage_info_progress_marks_done_current_pending` | 4 個 stage 狀態正確標記 | AC-4 |
| 同上 | `test_get_current_stage_info_user_cannot_advance_when_role_mismatch` | auditor 在 planning → user_can_advance=False | AC-7 |
| 同上 | `test_get_current_stage_info_precondition_attached_for_task_execution` | task_execution stage 回傳 precondition 結果 | AC-8 |
| 同上 | `test_get_current_stage_info_raises_when_ap_has_no_extension` | AP 無 ext → 404 `GRC_AP_NO_WORKFLOW_BINDING` | AC-13 |
| 同上 | `test_advance_stage_planning_happy_path_manager` | manager 推 planning → handler.execute 被呼叫 + complete_job 被呼叫 + AP.status → active | AC-5, AC-6 |
| 同上 | `test_advance_stage_role_forbidden_auditor_on_planning` | auditor 推 planning → 403 `GRC_STAGE_ROLE_FORBIDDEN` | AC-7 |
| 同上 | `test_advance_stage_role_forbidden_non_participant` | 非專案參與者 → 403 `GRC_STAGE_ROLE_FORBIDDEN` | AC-7 |
| 同上 | `test_advance_stage_precondition_failed_returns_412` | task_execution 有未完成 task → 412 `GRC_STAGE_PRECONDITION_FAILED` + reason | AC-8 |
| 同上 | `test_advance_stage_force_true_by_manager_bypasses_precondition` | manager + force=True → 跳過 check 推進成功 | AC-9 |
| 同上 | `test_advance_stage_force_true_by_non_manager_rejected` | auditor 帶 force=True → 仍 403 | AC-9 |
| 同上 | `test_advance_stage_calls_handler_before_complete_job` | handler.execute 先於 workflow complete_job（順序保證資料一致） | plan §十一 risk 3 |
| 同上 | `test_advance_stage_no_workflow_binding_returns_404` | AP 未綁 workflow → 404 | AC-13 |
| 同上 | `test_advance_stage_handler_key_not_registered_returns_400` | stage_object.complete_handler_key 在 Registry 找不到 → 400 `GRC_STAGE_HANDLER_NOT_REGISTERED` | AC-15 |
| 同上 | `test_advance_stage_user_task_not_bound_to_stage_object_returns_400` | BPMN UserTask 沒 stage code → 400 `GRC_STAGE_OBJECT_NOT_BOUND` | AC-16 |
| 同上 | `test_advance_stage_bpmn_at_end_event_returns_400` | 已到終點再 advance → 400 `GRC_STAGE_NO_NEXT_NODE` | AC-14 |
| 同上 | `test_advance_stage_bpmn_uses_user_task_main_role_override` | UserTask 自訂 mainRole=auditor → auditor 可推進；manager 不行 | AC-7 |
| 同上 | `test_advance_stage_poam_completion_closes_ap` | poam handler 走 close_round → AP.status=closed | AC-6, AC-18 |
| 同上 | `test_advance_stage_audit_no_poam_path_closes_ap` | audit → 無 poam path → AP.status=closed | AC-6, AC-18 |

### 2.6 API Route 層整合測試（`api/flow_engine/routes/stage_advance_route.py`）

| Test File | Test Method | Endpoint | 覆蓋 AC |
|---|---|---|---|
| `test_api_stage_advance_route.py` | `test_get_stage_info_200_manager` | GET /project/<p>/ap/<a>/stage/info | AC-3 |
| 同上 | `test_get_stage_info_200_auditor_user_cannot_advance` | GET 同上，response.user_can_advance=False | AC-7 |
| 同上 | `test_get_stage_info_404_when_ap_not_found` | AP uid 不存在 | AC-13 |
| 同上 | `test_get_stage_info_404_when_ap_no_workflow_binding` | AP 無 ext / workflow_execution_uid 為 NULL | AC-13 |
| 同上 | `test_get_stage_info_401_no_jwt` | 未帶 token | 安全基本 |
| 同上 | `test_post_advance_stage_200_manager_planning_to_active` | POST advance 成功 | AC-5, AC-6 |
| 同上 | `test_post_advance_stage_403_auditor_on_planning` | 角色擋 | AC-7 |
| 同上 | `test_post_advance_stage_403_outsider_no_participant` | 非 project 參與者 | AC-7 |
| 同上 | `test_post_advance_stage_412_precondition_failed_payload_has_reason_context` | response 含 reason_i18n_key + context | AC-8 |
| 同上 | `test_post_advance_stage_200_force_true_manager_bypass` | manager + force=true | AC-9 |
| 同上 | `test_post_advance_stage_403_force_true_auditor_rejected` | auditor force=true | AC-9 |
| 同上 | `test_post_advance_stage_400_handler_key_not_registered` | 400 | AC-15 |
| 同上 | `test_post_advance_stage_400_no_next_node` | EndEvent 後再 advance | AC-14 |
| 同上 | `test_post_advance_stage_400_user_task_not_bound` | UserTask 無 stage code | AC-16 |
| 同上 | `test_post_advance_stage_400_invalid_payload` | force 非 bool / ctx 非 dict | 400 基本 |

### 2.7 `start_oscal_project` Step 22 修改

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_start_oscal_project_stage_integration.py` | `test_start_creates_ap_extension_row` | AP 建立後 compliance.assessment_plan_extensions 有新列 | AC-1 |
| 同上 | `test_start_main_workflow_execution_created_and_uid_recorded` | workflow_execution_uid 寫入 ext | AC-1, AC-2 |
| 同上 | `test_start_flow_template_snapshot_uid_is_builtin_full_audit` | snapshot uid 對應 builtin-full-audit 範本 | AC-19 |
| 同上 | `test_start_workflow_stops_at_planning_user_task` | BPMN 當前節點 = planning UserTask | AC-2 |
| 同上 | `test_start_ap_status_remains_preparing` | AP.status 沒被自動推進 | AC-2 |
| 同上 | `test_start_workflow_failure_does_not_rollback_ap_creation` | Step 22 try/except：raise → AP 仍建成，log error（同 Step 7 容錯 pattern） | plan §B.5 |

### 2.8 EndEvent 收口 / 舊 raw SQL 移除

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_end_event_ap_closure.py` | `test_end_event_does_not_modify_ap_status_directly` | mock BPMN engine 觸發 _on_end_event_reached → AP.status 不被改第二次 | AC-17 |
| 同上 | `test_ext_assessment_plan_repo_impl_files_removed` | grep 確認 `domain/flow_engine/repository/i_ext_assessment_plan_repo.py` 與 `infra/.../ext_assessment_plan_repo_impl.py` 不存在 | AC-17 |

### 2.9 Stage Object Seed 補欄位驗證

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_stage_object_handler_keys_seed.py` | `test_planning_handler_key_is_oscal_planning_activate` | 查 DB row | AC-5 |
| 同上 | `test_task_execution_precondition_key_is_tasks_threshold` | 同上 | AC-10 |
| 同上 | `test_audit_precondition_key_is_all_controls_verdicted` | 同上 | AC-11 |
| 同上 | `test_poam_precondition_key_is_all_poam_closed` | 同上 | AC-12 |

### 2.10 DI Wiring / 架構約束

| Test File | Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|---|
| `test_stage_registry_di_wiring.py` | `test_grc_container_registers_4_handlers_at_startup` | 啟動後 Registry 有 4 個 handler key | AC-15, AC-23 |
| 同上 | `test_grc_container_registers_3_preconditions_at_startup` | 3 個 precondition key | AC-23 |
| 同上 | `test_flow_engine_module_does_not_import_grc_or_oscal` | grep `domain/flow_engine/` + `app/flow_engine/` 不含 `from app.grc` / `from app.oscal` import | AC-23 |

### 2.11 Error code 覆蓋（必檢）

| Error Code | HTTP | 對應測試 |
|---|---|---|
| `GRC_412021` | 412 | 2.6 `test_post_advance_stage_412_precondition_failed_*` |
| `GRC_403050` | 403 | 2.6 `test_post_advance_stage_403_*` |
| `GRC_400050` | 400 | 2.6 `test_post_advance_stage_400_no_next_node` |
| `GRC_404029` | 404 | 2.6 `test_get_stage_info_404_when_ap_no_workflow_binding` |
| `GRC_400051` | 400 | 2.6 `test_post_advance_stage_400_handler_key_not_registered` |
| `GRC_400052` | 400 | 2.6 `test_post_advance_stage_400_user_task_not_bound` |

### 2.12 權限三組驗證（CLAUDE.md 規範）

每個寫入 endpoint 至少三組權限測試：

| Endpoint | manager | auditor | 無 participant |
|---|---|---|---|
| `POST /stage/advance` (planning) | 200 ✓ | 403 ✓ | 403 ✓ |
| `POST /stage/advance` (task_execution) | 200 / 412（依 precondition） ✓ | 403 ✓ | 403 ✓ |
| `POST /stage/advance` (audit) | 視 BPMN UserTask main_role 覆寫；預設 auditor → 角色相反 ✓ | 200 / 412 ✓ | 403 ✓ |
| `POST /stage/advance` (poam) | 200 / 412 ✓ | 403（預設） ✓ | 403 ✓ |

> 註：stage 預設 main_roles 來自 `default_main_roles`；audit 預設 main_role=auditor、其他=manager（依 Spec 1 §3.2 定義）。BPMN UserTask 覆寫 case 在 `test_advance_stage_bpmn_uses_user_task_main_role_override` 處理。

---

## 3. FE 元件測試（Vitest，`compliance-manager-fe/`，optional）

### 3.1 `FlowPhaseBanner.vue`（`src/components/grc/__tests__/`）

| Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|
| `renders stage name from i18n key` | mock stageInfo → 顯示中文名 | AC-20 |
| `renders 4-dot progress bar with current dot highlighted` | progress 解析正確 | AC-4, AC-20 |
| `shows advance button when user_can_advance=true` | manager / 對應角色 | AC-20 |
| `hides advance button when user_can_advance=false` | auditor on planning | AC-7, AC-21 |
| `disables button when precondition.passed=false (non-manager)` | reviewer/auditor 無 force | AC-20 |
| `shows tooltip with reason_i18n_key and context when precondition fails` | tooltip 文字 | AC-20 |
| `manager can force-click disabled-looking button → opens ConfirmDialog` | dialog 出現 | AC-9, AC-20 |
| `confirms force in dialog → emits advance with force=true payload` | payload 正確 | AC-9 |
| `emits stage-advanced event after successful advance` | parent view 可 refresh | AC-20 |
| `renders different copy when stage.kind=stateful (task_execution)` | banner 文案差異 | AC-22 |

### 3.2 `useStageInfo` composable（`src/composables/__tests__/`）

| Test Method | 驗證對象 | 覆蓋 AC |
|---|---|---|
| `fetchStageInfo populates stageInfo ref` | mock service → ref 更新 | AC-3 |
| `fetchStageInfo sets error on 404` | error.value 含 GRC_AP_NO_WORKFLOW_BINDING | AC-13 |
| `advance with force=false calls service with correct payload` | payload 結構 | AC-5 |
| `advance with force=true sends force flag` | force 傳出 | AC-9 |
| `advance refetches stageInfo on success` | call sequence | AC-20 |
| `loading flag toggles around async ops` | UX 細節 | — |

### 3.3 View-level smoke（optional，由 E2E 涵蓋大部分）

僅在元件 unit test 不易覆蓋的整合點補；多數 view 改動由 E2E 驗證。

---

## 4. E2E BDD 測試（Cucumber + Playwright，`compliance-manager-test/`）

### 4.1 Feature 檔案

**位置**：`features/flow-engine/stage-advance.feature`

```gherkin
Feature: Spec 2 — 階段抽象整合（Stage Advance）
  As a 稽核專案的 PM / 稽核員
  I want to 透過 Banner 推進當前階段
  So that AP 的 lifecycle 由 BPMN 流程引擎驅動

  Background:
    Given 系統有一個 manager 帳號 "pm_a" 與 auditor 帳號 "auditor_b"
    And 已建立合規框架 "CMMC-2.0-L1" 與專案 "Spec2-E2E-Project"
    And 專案套用 default 範本 "builtin-full-audit"

  @happy-path
  Scenario: 完整 4 stage 推進到結案
    Given pm_a 登入並進入專案 "Spec2-E2E-Project" 的 AP 規劃頁（settings）
    Then 看到 FlowPhaseBanner 顯示「規劃」階段
    And progress 第 1 點 highlighted
    When pm_a 點擊 Banner 的「啟動專案」
    Then AP.status 變為 "active"
    And Banner 階段名變為「執行任務」
    When pm_a 進入專案總覽頁
    And 所有任務已完成
    And pm_a 點擊 Banner 的「啟動稽核」
    Then AP.status 變為 "auditing"
    When auditor_b 登入並進入稽核頁
    And 所有控制項已 verdicted 且 fail 控制項有 finding
    And auditor_b 點擊 Banner 的「提交稽核」
    Then AP.status 變為 "remediation"
    When pm_a 進入缺失改善頁
    And 所有 POA&M 已 closed
    And pm_a 點擊 Banner 的「完成改善」
    Then AP.status 變為 "closed"
    And Banner 顯示「已結案」

  @precondition-fail
  Scenario: task_execution 階段有未完成任務 — 按鈕 disabled + tooltip
    Given AP.status=active 且有 12 / 50 任務未完成
    When pm_a 進入專案總覽頁
    Then Banner「啟動稽核」按鈕為 disabled 狀態
    And tooltip 顯示「尚有 12 / 50 任務未完成」

  @role-blocking
  Scenario: auditor 在 planning 階段看不到推進按鈕
    Given AP.status=preparing
    When auditor_b 登入並進入專案規劃頁（settings）
    Then Banner 顯示「規劃」階段
    But 不顯示「啟動專案」推進按鈕

  @force-override
  Scenario: manager 透過 ConfirmDialog 強制推進
    Given AP.status=active 且有 12 / 50 任務未完成
    When pm_a 進入總覽頁
    And pm_a 點擊 Banner「啟動稽核」(disabled 但 manager 可點)
    Then 顯示 ConfirmDialog 列出未完成任務數
    When pm_a 確認 ConfirmDialog
    Then AP.status 變為 "auditing"

  @navigation-buttons-intact
  Scenario: 既有 4 個導航按鈕（專案規劃/進入稽核/查看稽核/改善計畫）仍存在
    Given AP.status=active
    When pm_a 進入專案總覽頁
    Then Banner 內不含「啟動稽核」舊按鈕位置（已搬至 Banner）
    But 右上角仍可見「導航按鈕：進入稽核」按鈕（沿用既有 v-if 邏輯）
```

### 4.2 Step Definitions 拆解

| Step File | Step 摘要 | 對應 Page Object 方法 |
|---|---|---|
| `steps/flow-engine/stage-advance-steps.js` | `Given <role> 登入並進入專案 ".." 的 .. 頁` | `LoginPage::loginAs()` + `ProjectNavPage::goToSettings()` |
| 同上 | `Then 看到 FlowPhaseBanner 顯示「<stage>」階段` | `FlowPhaseBannerComponent::getStageName()` |
| 同上 | `When .. 點擊 Banner 的「<button>」` | `FlowPhaseBannerComponent::clickAdvance()` |
| 同上 | `Then AP.status 變為 "<status>"` | `ApDetailPage::getApStatus()` 或 API assertion |
| 同上 | `Then Banner「<button>」按鈕為 disabled 狀態` | `FlowPhaseBannerComponent::isAdvanceDisabled()` |
| 同上 | `And tooltip 顯示「<text>」` | `FlowPhaseBannerComponent::getTooltipText()` |
| 同上 | `Then 顯示 ConfirmDialog 列出 ..` | `ConfirmDialogComponent::isVisible()` + `getMessage()` |
| 同上 | `When .. 確認 ConfirmDialog` | `ConfirmDialogComponent::confirm()` |
| 同上 | `Then 不顯示「<button>」推進按鈕` | `FlowPhaseBannerComponent::hasAdvanceButton()` |
| 同上 | `Then 右上角仍可見「導航按鈕：<text>」按鈕` | `ProjectOverviewPage::getLegacyNavButtons()` |

### 4.3 Page Object 清單

| Page Object | 路徑 | 主要 method |
|---|---|---|
| `FlowPhaseBannerComponent` | `pages/grc/FlowPhaseBannerComponent.js` | `getStageName()`, `getProgressDotStatus(index)`, `clickAdvance()`, `isAdvanceDisabled()`, `getTooltipText()`, `hasAdvanceButton()` |
| `ProjectSettingsPage` | `pages/grc/ProjectSettingsPage.js`（新增） | `gotoSettings(projectUid)`, `banner: FlowPhaseBannerComponent` |
| `ProjectOverviewPage` | `pages/grc/ProjectOverviewPage.js`（既有，加 banner ref） | `gotoOverview()`, `banner`, `getLegacyNavButtons()` |
| `AuditReviewPage` | `pages/grc/AuditReviewPage.js`（既有，加 banner ref） | `gotoAuditPage()`, `banner` |
| `PoamPage` | `pages/grc/PoamPage.js`（既有，加 banner ref） | `gotoPoamPage()`, `banner` |
| `ConfirmDialogComponent` | `pages/common/ConfirmDialogComponent.js`（若無則新增） | `isVisible()`, `getMessage()`, `confirm()`, `cancel()` |
| `ApDetailPage`（API helper） | `pages/grc/ApDetailPage.js` | `getApStatus(apUid)` via REST API（fast assertion，不走 UI） |

### 4.4 測試資料準備

- Cucumber `Background` 透過 BE seeding API（或直接 SQL fixture）建：
  - 1 個專案、1 個 AP（status=preparing）、套用 `builtin-full-audit` 範本
  - 50 個 task（依 scenario 分配未完成數）
  - 控制項 / verdict / finding / POA&M 狀態依 scenario 預備
- 推薦：`compliance-manager-test/seeds/spec2-e2e/` 放可重用 SQL fixture

### 4.5 不在 E2E 範圍（已由 BE / FE 元件層覆蓋）

- AC-13 / AC-14 / AC-15 / AC-16（error code 細節）：BE 整合測試已覆蓋，E2E 不重複觸發異常 BPMN 狀態
- AC-1 / AC-2（DB schema 寫入細節）：BE 單元測試已覆蓋
- AC-23（架構約束 import）：靜態檢查，無 E2E 意義

---

## 5. Traceability Matrix

| AC ID | BE Unit | BE Integration | FE Component | E2E | 備註 |
|---|---|---|---|---|---|
| AC-1 | ✓ test_assessment_plan_extension_repo.* | ✓ test_start_oscal_project_stage_integration | — | — | DB 層；E2E 不重複 |
| AC-2 | — | ✓ test_start_workflow_stops_at_planning_user_task | — | ✓ Background | |
| AC-3 | ✓ test_get_current_stage_info_happy_path | ✓ test_get_stage_info_200_manager | ✓ useStageInfo.fetchStageInfo | ✓ Happy path | |
| AC-4 | ✓ test_get_current_stage_info_progress_marks_done_current_pending | — | ✓ renders 4-dot progress bar | ✓ progress dot assertion | |
| AC-5 | ✓ test_oscal_stage_handlers.* | ✓ test_post_advance_stage_200_manager_planning_to_active | — | ✓ Happy path | |
| AC-6 | ✓ test_advance_stage_*_happy_path | ✓ test_post_advance_stage_200_* | — | ✓ Happy path | 4 stage 連推 |
| AC-7 | ✓ test_advance_stage_role_forbidden_* | ✓ test_post_advance_stage_403_* | ✓ hides advance button | ✓ @role-blocking | |
| AC-8 | ✓ test_advance_stage_precondition_failed_returns_412 | ✓ test_post_advance_stage_412_* | ✓ shows tooltip with reason | ✓ @precondition-fail | |
| AC-9 | ✓ test_advance_stage_force_true_by_manager_bypasses_precondition | ✓ test_post_advance_stage_200_force_true_manager_bypass | ✓ manager can force-click | ✓ @force-override | |
| AC-10 | ✓ test_tasks_threshold_* | — | — | ✓ @precondition-fail | |
| AC-11 | ✓ test_all_controls_verdicted_* | — | — | ✓ Happy path（implicit） | |
| AC-12 | ✓ test_all_poam_closed_* | — | — | ✓ Happy path（implicit） | |
| AC-13 | ✓ test_get_current_stage_info_raises_when_ap_has_no_extension | ✓ test_get_stage_info_404_when_ap_no_workflow_binding | ✓ useStageInfo.fetchStageInfo error | ⚠️ 無 | 異常路徑，BE 已驗 |
| AC-14 | ✓ test_advance_stage_bpmn_at_end_event_returns_400 | ✓ test_post_advance_stage_400_no_next_node | — | ⚠️ 無 | 異常路徑 |
| AC-15 | ✓ test_get_handler_missing_key_returns_none + test_advance_stage_handler_key_not_registered_returns_400 | ✓ test_post_advance_stage_400_handler_key_not_registered | — | ⚠️ 無 | 異常路徑 |
| AC-16 | ✓ test_advance_stage_user_task_not_bound_to_stage_object_returns_400 | ✓ test_post_advance_stage_400_user_task_not_bound | — | ⚠️ 無 | 異常路徑 |
| AC-17 | ✓ test_end_event_does_not_modify_ap_status_directly + test_ext_assessment_plan_repo_impl_files_removed | — | — | ✓ Happy path（implicit verify AP.status closed once） | |
| AC-18 | ✓ test_advance_stage_poam_completion_closes_ap + test_advance_stage_audit_no_poam_path_closes_ap | — | — | ✓ Happy path | |
| AC-19 | ✓ test_start_flow_template_snapshot_uid_is_builtin_full_audit | — | — | ✓ Background | |
| AC-20 | — | — | ✓ FlowPhaseBanner.* | ✓ Happy path | |
| AC-21 | — | — | ⚠️ 限元件層 | ✓ @navigation-buttons-intact | grep code 確認舊按鈕拆除（人工 / lint） |
| AC-22 | — | — | ✓ stage.kind=stateful copy 變化 | ✓ Happy path（task_execution stage on overview） | MyTasksView 不掛 Banner 由 grep 驗證 |
| AC-23 | ✓ test_flow_engine_module_does_not_import_grc_or_oscal | — | — | ⚠️ 無 | 架構約束，靜態 check |

**未覆蓋彙整**：
- AC-13/14/15/16 無 E2E：⚠️ 異常路徑、UI 不可達；BE 整合測試已驗，E2E 不重複觸發
- AC-21 部分：⚠️ 「舊按鈕完全拆除」靠 grep code + E2E `@navigation-buttons-intact` scenario 雙重驗證；元件層難覆蓋
- AC-23：⚠️ 架構約束無 E2E 意義；靜態測試 + code review 守

**覆蓋率**：23 個 AC，至少有一層 test 覆蓋 = 100%。其中 17 個有 E2E 直接驗、6 個僅靠 BE / 元件層（標 ⚠️）。

---

## 6. 執行順序與閘門

```
1. Pre-flight 假設驗證（implementation-plan.md §〇 5 條 grep）
       ↓ 全 pass 才開工
2. BE Phase A.1~A.4 → BE pytest 單元測試（2.1 / 2.2 / 2.9）全綠
       ↓
3. BE Phase B.1~B.7 → BE pytest 整合測試（2.3~2.8 / 2.10~2.12）全綠
       ↓
4. BE manual smoke（curl + dev DB，4 stage 連推到 closed）
       ↓
5. FE Phase C → Vitest 元件測試（3.1 / 3.2）全綠
       ↓
6. FE Phase D → manual smoke（4 個 view 都看到 Banner）
       ↓
7. E2E（compliance-manager-test）→ Cucumber 5 scenario 全綠
       ↓
8. Phase F code review（pr-review-toolkit 4-agent parallel）
       ↓
9. Push + dev DB 完整 rollback / re-migrate / 重啟 BE 重跑 E2E
```

**閘門條件**：
- 步驟 3 / 5 / 7 任一全綠才能進下一階段
- 步驟 8 review 發現 high-severity → 回對應 phase 修
- 步驟 9 rollback 復原失敗 → 暫停 push 等修補

---

## 7. 不在本次測試範圍

明確列出：

- **壓力測試 / 性能測試**：spec 未要求；stage advance 非高頻 API（每個 AP 一輪 4 次）
- **安全測試（XSS / SQL injection）**：sqlalchemy parameterized + flask-restful schema 已涵蓋；本 spec 無新增 raw SQL
- **跨瀏覽器相容**：sticking to project default browser matrix（不擴）
- **i18n 切換完整測試**：僅驗 4 個新 i18n key（reason_i18n_key）正確帶到 FE；不全量切 en/zh
- **Spec 1 範本 CRUD**：已在 Spec 1 test-plan 覆蓋
- **Spec 3 範本 wizard**：未實作
- **BPMN loop back PM Dialog**：v1 不做
- **觀察者 / viewer 角色完整 UI 行為**：沿用既有 v-if，無新 logic
- **`compliance.project_extensions.default_flow_template_uid`**：Spec 3 範圍

---

## 8. 風險與待釐清

| 風險 | 影響 | 緩解 / 待釐清 |
|---|---|---|
| `workflow_executions` 表是否支援 main + task 雙層（`is_main` 欄位） | 高 — 影響 AP build 流程 | Pre-flight `\d compliance.workflow_executions` 確認；若缺欄位 → 回 plan 修 |
| `@transaction` 嵌套（StageAdvanceService → OscalAuditService.confirm_audit → close_round） | 中 — 可能 commit 衝突 | 既有 `confirm_audit→close_round` 已嵌套運作；新增 test `test_advance_stage_calls_handler_before_complete_job` 驗順序 |
| BPMN UserTask 自訂 `mainRole` extension 解析方式 | 中 — 影響角色覆寫測試 | Spec 1 BPMN 編輯器存的 attribute name 待確認（`mainRole` vs `main_roles` vs `<extensionElements>`）；test 用 fixture BPMN |
| Precondition reason_i18n_key 與既有 launch_audit error message 對齊度 | 中 — UX 一致性 | 整理 4 個 reason key 對應的既有 GRC error code（412003 / 412005 / 412011）；FE i18n file 集中管理 |
| backfill SQL 對既有 dev/stg 環境影響 | 低 | INSERT ... ON CONFLICT DO NOTHING，可重跑；test 環境 fresh DB 不受影響 |
| `pm_decides_loopback` placeholder gateway default 行為 | 中 | Phase B 確認 BPMN engine evaluate 未綁變數的 gateway → 預期走 default flow (→ closed) |
| Spec 2 落地後 force=true 觸發既有 launch_audit 內部 `force` 邏輯 | 低 | OscalAuditService.launch_audit 已支援 force param；handler 直傳即可 |
| Step 22 容錯 (workflow 啟動失敗不 rollback AP) 留下「孤兒 AP」處理 | 中 — UX 問題 | test 涵蓋 `test_start_workflow_failure_does_not_rollback_ap_creation`；UX 補 retry 入口（follow-up） |

---

## 9. 測試規模摘要

| 層級 | 檔案數 | Test method 數（估） |
|---|---|---|
| BE Repository / Mapper | 2 | 8 |
| BE Domain Registry / Interface | 1 | 5 |
| BE OSCAL Stage Handlers | 1 | 10 |
| BE OSCAL Stage Preconditions | 1 | 7 |
| BE StageAdvanceService | 1 | 19 |
| BE API Route 整合 | 1 | 15 |
| BE start_oscal_project Step 22 | 1 | 6 |
| BE EndEvent / 舊檔移除 | 1 | 2 |
| BE Stage Object Seed | 1 | 4 |
| BE DI Wiring / 架構約束 | 1 | 3 |
| **BE 小計** | **11** | **79** |
| FE FlowPhaseBanner 元件 | 1 | 10 |
| FE useStageInfo composable | 1 | 6 |
| **FE 小計** | **2** | **16** |
| E2E feature file | 1 | 5 scenario |
| E2E step / page objects | ~7 檔 | — |
| **E2E 小計** | **~8** | **5 scenarios** |

**總計**：BE 11 test files / ~79 test methods + FE 2 test files / ~16 test methods + E2E 5 scenarios。

---

## 10. 文件版本

| 版本 | 日期 | 變更 |
|---|---|---|
| v1 | 2026-05-12 | 初版 test-plan（Phase 4 產出，依 design.md v2 §九 + implementation-plan.md v1） |
