# Redmine #1397／#1398／#1399 LIFF 人工 E2E 測試計畫

> 版本：2026-07-31 初版  
> 執行狀態：等待 #1399 Frontend 完成並部署後執行  
> 測試環境：Ken 的 Branch／Charge Point、docker1 HQ staging、#1399 LIFF 測試版  
> 測試起點：所有產品操作都必須從 LIFF Connector 列表或詳細頁開始

## 1. 目的與通過標準

本計畫驗證 #1397 定義的新版 `driverState`、semantic actions、RemoteStart／RemoteStop
operation lifecycle、WebSocket initial snapshot，以及私樁長期離峰與排隊行為。API、DB
與 log 只用來佐證 LIFF 操作的真實結果，不可取代 LIFF 畫面與實體充電行為。

整輪測試通過必須同時符合：

1. 適用的 P0 案例全部通過；Skip 不是 Pass。
2. LIFF 顯示的狀態、按鈕、modal 與 Backend 真實 operation／transaction 一致。
3. 實際開始與停止案例必須看到繼電器／電流變化、OCPP transaction 與 DB 收斂。
4. 公樁與私樁都完成測試；不能只用 API response 推定另一種樁型也正確。
5. 每個案例都留下時間、Connector ID、畫面、WebSocket／API、Branch／CP log 與 DB 證據。
6. 測試完成後，兩支 Connector 必須還原成測試前的公私樁、tariff、Charge Group 與離峰設定。

## 2. Ken 環境基線

2026-07-31 查核到的初始資料如下。正式執行前仍要重查一次，若不同，以當天快照為準。

| 項目 | CP001-001 | CP001-002 |
| --- | --- | --- |
| 廠牌 | CLOUDLINK | CLOUDLINK |
| Branch status | AVAILABLE | AVAILABLE |
| Branch area | public | public |
| CP area | public | public |
| Charge Group | NULL | NULL |
| Connector tariff | 未綁定 | 未綁定 |
| enableOffPeak | NULL | NULL |
| active transaction | 無 | 無 |
| LIFF 測試使用者關聯 | 已關聯 | 已關聯 |

環境路徑：

| 層級 | 環境 |
| --- | --- |
| LIFF | #1399 完成後由 Ryan 提供實際測試 URL／LIFF channel；執行紀錄必須填入版本或 commit |
| HQ | docker1 staging，`ems-hq-staging-api`，對外由 `hq-api.sylksoft.com` 提供 API／WebSocket |
| Branch | Ken 主機，`ems-branch-api`，host port `18081`／OCPP `18082` |
| Charge Point | Ken 主機，`ems-cp-api`，實體 Connector 為 `CP001-001`、`CP001-002` |
| Branch DB | Ken 主機 MySQL container 的 `ems_branch` |

## 3. 人員、帳號與設備

### 3.1 必要角色

- Tester A：Ken 的 LINE／LIFF 測試帳號；必須可進入測試 building，並對 `CP001-002`
  對應到 Branch user `U2601140001`。該 Branch user 已有有效 IdTag。
- Tester B：第二個 LINE／LIFF 帳號，只用於「指定使用者 operationResult」與公／私樁停止授權案例。
  執行前必須在 HQ 建立相同 building binding，Branch 建立另一位有效 IdTag user，並將其關聯到
  私樁 `CP001-002`。不得共用 Tester A 的 LINE identity。
- BE observer：觀察 HQ／Branch／CP log、DB 與實體設備；不得代替 Tester 在 API 工具直接發動產品操作。
- FE observer：確認 LIFF action mapping、modal、WebSocket reconnect 與 operationId 去重。

若 Tester B 尚未準備完成，標記 M1397-014、M1397-015 為 Blocked，不得填 Pass。

### 3.2 實體設備

- 一台可正常 AC 充電的測試車，或等效且能產生真實 StartTransaction、MeterValues、StopTransaction 的負載。
- `CP001-001`、`CP001-002` 的槍線均可使用。
- 可觀察 Relay／電流的 CP log 或實體量測方式。
- 兩支手機；測 targeted result 時兩個 LIFF session 要同時停留在同一 Connector 詳細頁。

## 4. 測試前安全檢查

先在 Ken 主機進入 MySQL：

```bash
sudo docker exec -it mysql mysql -uroot -p ems_branch
```

密碼只從既有安全設定取得，不可寫入測試報告、截圖或 Redmine。

### 4.1 留存基線快照

```sql
SELECT id, connector_type, status, area, enable_off_peak,
       off_peak_enabled_by_user_id, off_peak_suppressed_window_start,
       off_peak_suppressed_window_end, off_peak_suppression_reason,
       charge_group, current_transaction_id
FROM e_connectors
WHERE id IN ('CP001-001', 'CP001-002');

SELECT id, status, area, current_transaction_id, relay_status
FROM cp_connectors
WHERE id IN ('CP001-001', 'CP001-002');

SELECT id, tariff_id
FROM e_connector_tariff
WHERE id IN ('CP001-001', 'CP001-002');

SELECT connector_id, user_id
FROM e_connector_user
WHERE connector_id IN ('CP001-001', 'CP001-002')
ORDER BY connector_id, user_id;
```

將查詢結果貼入本次 Test Run report。不要只依本文件的 2026-07-31 基線還原，因為正式測試日的資料可能已改變。

### 4.2 確認沒有進行中的工作

```sql
SELECT transaction_id, connector_id, id_tag, status, start_timestamp
FROM e_transactions
WHERE connector_id IN ('CP001-001', 'CP001-002')
  AND status = 'ACTIVE';

SELECT id, operation_type, source, connector_id, requested_by_user_id,
       operation_status, deadline_at, grace_until
FROM e_remote_transaction_log
WHERE connector_id IN ('CP001-001', 'CP001-002')
  AND operation_status IN ('SENT', 'ACCEPTED');

SELECT id, connector_id, source, requested_by_user_id, charge_group,
       priority, charge_status, status_reason
FROM e_connector_priority
WHERE connector_id IN ('CP001-001', 'CP001-002');
```

通過條件：ACTIVE transaction、SENT／ACCEPTED operation 與既有 queue 都為空。
若不是空，不得直接刪除 operation 或 transaction；先查明是否仍在供電並正常停止／收斂。

### 4.3 確認服務與時間

```bash
date
sudo docker ps --format '{{.Names}} {{.Status}} {{.Ports}}'
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18081/api-docs
```

HQ、Branch、CP、手機時間差不得超過一分鐘，否則 operation deadline 與 log 對帳會混亂。

## 5. 建立專用測試資料

以下 fixture 讓兩支實體樁切換出三種模式：

| 模式 | CP001-001 | CP001-002 | 用途 |
| --- | --- | --- | --- |
| PUB | public | public | 公樁 LIFF、Start／Stop、無離峰 switch |
| PRI-0 | public，同 group | private，同 group | group 總 slot=1，扣除一支 public 後 private slot=0；測 MANUAL／OFF_PEAK QUEUED |
| PRI-1 | public，同 group | private，同 group | group 總 slot=2，扣除一支 public 後 private slot=1；測私樁實際 Start／Stop／離峰充電 |

### 5.1 建立 test-only Charge Group 與 tariffs

只在 4.2 全部為空後執行：

```sql
INSERT INTO e_charge_group
    (id, contract_capacity, allocated_power, rotation_cycle_active, reserved_slots)
VALUES
    ('CG_E2E_1397', 7.00, 7.00, 0, 0)
ON DUPLICATE KEY UPDATE
    contract_capacity = VALUES(contract_capacity),
    allocated_power = VALUES(allocated_power),
    rotation_cycle_active = 0,
    reserved_slots = 0;

INSERT INTO e_tariff_table
    (id, mode, name, connector_area, start_date, end_date,
     currency, enabled, version_no, is_template, editable)
VALUES
    ('E2E1397WAIT', 'RLT', 'E2E #1397 非當前離峰', 'private',
     '2026-01-01', '2099-12-31', 'TWD', 1, 1, 0, 1),
    ('E2E1397ACTIVE', 'RLT', 'E2E #1397 全日離峰', 'private',
     '2026-01-01', '2099-12-31', 'TWD', 1, 1, 0, 1)
ON DUPLICATE KEY UPDATE
    enabled = 1,
    connector_area = 'private',
    start_date = '2026-01-01',
    end_date = '2099-12-31';

DELETE FROM e_tariff_time_slot
WHERE tariff_id IN ('E2E1397WAIT', 'E2E1397ACTIVE');

INSERT INTO e_tariff_time_slot
    (id, tariff_id, season_type, day_type, slot_type,
     start_time, end_time, unit_price, sequence_no, cross_day,
     create_time, update_time)
VALUES
    ('E2E1397WAIT01', 'E2E1397WAIT', 'ALL', 'ALL', 'OFF_PEAK',
     '00:00:00', '00:01:00', 1.0000, 1, 0, NOW(), NOW()),
    ('E2E1397ACTIVE01', 'E2E1397ACTIVE', 'ALL', 'ALL', 'OFF_PEAK',
     '00:00:00', '23:59:59', 1.0000, 1, 0, NOW(), NOW());
```

`E2E1397WAIT` 用來穩定製造「長期離峰已開啟、目前不在離峰 window」；除非測試剛好在
00:00～00:01 執行。`E2E1397ACTIVE` 用來讓任何測試時間都處於離峰 window。

### 5.2 切換 PUB 模式

```sql
UPDATE e_connectors
SET area = 'public', enable_off_peak = NULL,
    off_peak_enabled_by_user_id = NULL,
    off_peak_suppressed_window_start = NULL,
    off_peak_suppressed_window_end = NULL,
    off_peak_suppression_reason = NULL,
    charge_group = NULL
WHERE id IN ('CP001-001', 'CP001-002');

UPDATE cp_connectors
SET area = 'public'
WHERE id IN ('CP001-001', 'CP001-002');

DELETE FROM e_connector_priority
WHERE connector_id IN ('CP001-001', 'CP001-002');
```

變更後關閉舊詳細頁，再由 LIFF 列表重新進入，讓 WebSocket initial snapshot 讀取最新資料。

### 5.3 切換 PRI-0 模式（private slot=0）

```sql
UPDATE e_charge_group
SET contract_capacity = 7.00, allocated_power = 7.00,
    reserved_slots = 0, rotation_cycle_active = 0
WHERE id = 'CG_E2E_1397';

UPDATE e_connectors
SET area = 'public', enable_off_peak = NULL, charge_group = 'CG_E2E_1397'
WHERE id = 'CP001-001';

UPDATE e_connectors
SET area = 'private', enable_off_peak = 0,
    off_peak_enabled_by_user_id = NULL,
    off_peak_suppressed_window_start = NULL,
    off_peak_suppressed_window_end = NULL,
    off_peak_suppression_reason = NULL,
    charge_group = 'CG_E2E_1397'
WHERE id = 'CP001-002';

UPDATE cp_connectors SET area = 'public'  WHERE id = 'CP001-001';
UPDATE cp_connectors SET area = 'private' WHERE id = 'CP001-002';

INSERT INTO e_connector_tariff (id, tariff_id)
VALUES ('CP001-002', 'E2E1397WAIT')
ON DUPLICATE KEY UPDATE tariff_id = VALUES(tariff_id);

DELETE FROM e_connector_priority
WHERE connector_id IN ('CP001-001', 'CP001-002');
```

此時 `floor(7/7)=1`，再扣除同 group 的一支 public connector，private 可用 slot 為 0。

### 5.4 切換 PRI-1 模式（private slot=1）

```sql
UPDATE e_charge_group
SET contract_capacity = 14.00, allocated_power = 7.00,
    reserved_slots = 0, rotation_cycle_active = 0
WHERE id = 'CG_E2E_1397';
```

此時 `floor(14/7)=2`，扣除一支 public connector 後，剩一個 private slot。

### 5.5 切換 private tariff

不在離峰 window：

```sql
UPDATE e_connector_tariff
SET tariff_id = 'E2E1397WAIT'
WHERE id = 'CP001-002';
```

強制處於離峰 window：

```sql
UPDATE e_connector_tariff
SET tariff_id = 'E2E1397ACTIVE'
WHERE id = 'CP001-002';
```

每次切換 tariff 後，關閉 Connector 詳細頁，再由 LIFF 列表重新進入。

## 6. 執行證據規則

每個案例至少記錄：

- LIFF 操作開始／結束時間與 Tester A／B。
- Connector ID、當時模式 PUB／PRI-0／PRI-1、車輛是否插槍。
- 操作前畫面、等待狀態、結果 modal、操作後畫面。
- command HTTP status／body；涉及非同步操作時記錄 `operationId`。
- WebSocket initial snapshot 與後續 UPDATED／operationResult 的關鍵欄位。
- `e_remote_transaction_log`、`e_connector_priority`、`e_transactions` 的對應資料。
- 實際 Relay／電流與 OCPP RemoteStart／StartTransaction／RemoteStop／StopTransaction。

結果只能填 `PASS`、`FAIL`、`BLOCKED`、`NOT APPLICABLE`。不得把沒有執行寫成 PASS。

## 7. 公樁案例（PUB）

### M1397-001：詳細頁只靠 WebSocket initial snapshot

對應：UI-001、UI-002、UI-010、CHG-025。

1. 套用 PUB 模式，確認兩樁 AVAILABLE、無 transaction／operation／queue。
2. Tester A 從 LIFF 充電樁列表進入 `CP001-001` 詳細頁。
3. 用 FE debug log／Network 確認只建立 Connector WebSocket；不得另外呼叫 Connector detail REST API。
4. 確認第一份 snapshot 為目前 Connector 資料，不是舊 cache。

預期：

- `areaType=public`、raw `status=AVAILABLE`、`driverState.code=READY`。
- title／description 表示可開始充電，主要 action 為 `START_CHARGING`／「開始充電」。
- `activeOperation=null`。
- 公樁不顯示離峰 switch。
- 列表與詳細頁的狀態一致。

### M1397-002：CloudLink 先插槍、尚未按開始

對應：CHG-001、UI-002。

1. Tester A 先把車插到 `CP001-001`，不要按 LIFF 開始。
2. 等待設備狀態穩定後，由 LIFF 列表重新進入詳細頁。
3. 記錄 raw OCPP status 與 LIFF driverState。

預期：即使設備因插槍回報與待命相關的 raw status，沒有未完成 RemoteStart operation 時，
LIFF 仍顯示 `READY`／「開始充電」，不得誤顯示「準備中」。

### M1397-003：公樁 RemoteStart lifecycle

對應：CHG-001、CHG-020、CHG-029、CHG-032、CHG-034。

1. 承 M1397-002，Tester A 按「開始充電」。
2. 若有確認 modal，確認開始。
3. 按下後立即觀察按鈕、title 與 description，直到開始充電。
4. 持續至少一個 MeterValues 上傳週期。

預期：

- command 回 HTTP 202，含新的 `operationId`。
- LIFF 先進入 `PREPARING`／「準備中」，不能重複開始。
- `activeOperation.operationId` 與 response、DB `e_remote_transaction_log.id` 相同。
- 收到 StartTransaction 後轉 `CHARGING`，主要 action 改為 `STOP_CHARGING`／「停止充電」。
- 只有一筆 ACTIVE transaction；Relay 開啟且有合理電流／MeterValues。
- terminal success operationResult 只顯示一次，不因後續 UPDATED event 重複彈出。

### M1397-004：公樁 RemoteStop lifecycle

對應：CHG-001、CHG-020、CHG-029、CHG-032、CHG-034。

1. 承 M1397-003，由啟動交易的 Tester A 按「停止充電」並確認。
2. 立即觀察狀態，直到實體停止。

預期：

- command 回 HTTP 202，取得 STOP `operationId`。
- LIFF 先進入 `FINISHING`／「停止中」，Start／Stop 都不可重複執行。
- 收到 StopTransaction 後，Relay 關閉、transaction 轉 COMPLETED。
- Connector 回到 `READY`；停止成功 modal 只顯示一次。

### M1397-005：CloudLink PREPARING 取消啟動

對應：CHG-007、CHG-034。

1. 車輛先不要建立充電需求，從 READY 按「開始充電」。
2. LIFF 顯示 PREPARING 後，在 StartTransaction 前按「取消啟動」並確認。
3. 觀察 operation、Relay 與後續 60 秒狀態。

預期：

- action code 為 `CANCEL_START`，不是依按鈕文字猜 command。
- 取消成功後 START operation 為 CANCELLED，LIFF 回 `READY`。
- 不建立 ACTIVE transaction，設備不應在取消後又自行開始。

若硬體太快產生 StartTransaction，記錄 Blocked，改用未插車且能進入 waiting mode 的方式重測；
不得把已進入 CHARGING 的停止流程算成取消 PREPARING。

### M1397-006：WebSocket 中斷時禁止沿用舊 action

對應：UI-010。

1. 在 READY 詳細頁確認「開始充電」可操作。
2. 關閉手機網路，或用可控代理只阻斷 Connector WebSocket。
3. 斷線後立即點原本位置的「開始充電」。
4. 等待 FE 的一次短暫重連失敗，再恢復網路。

預期：

- 斷線後舊 `driverState.actions` 立即不可用，不送出 command。
- 一次短暫重連仍失敗後，退回列表並顯示連線失敗 modal。
- 不以 REST detail 或舊 snapshot 留在可操作狀態。
- 重新進入詳細頁前，DB 不新增 RemoteStart operation。

## 8. 私樁排隊與離峰案例（PRI-0）

### M1397-007：私樁畫面與手動零 slot 排隊

對應：CHG-037、ROT-004、UI-002。

1. 套用 PRI-0，tariff 使用 `E2E1397WAIT`，off-peak 保持 OFF。
2. Tester A 從列表進入 `CP001-002`。
3. 確認顯示離峰 switch，driverState 為 READY。
4. 按「開始充電」。

預期：

- private area 顯示離峰 switch。
- 因 private slot=0，command 同步回 HTTP 200，不建立 RemoteStart operation。
- 建立 `source=MANUAL`、`requestedByUserId=Tester A Branch user` 的 Priority。
- driverState 轉 `QUEUED`，主要操作顯示「取消排隊」，action 為 `CANCEL_MANUAL_START`。
- 點「取消排隊」後只刪除 MANUAL queue，回 READY；不改變 off-peak switch。

### M1397-008：開啟長期離峰立即 OFF_PEAK_WAITING

對應：ROT-001、ROT-021。

1. 確認沒有 MANUAL queue，tariff 使用 `E2E1397WAIT`。
2. Tester A 將離峰 switch 切成 ON，完成 FE 確認。
3. API 成功後立即觀察畫面與 DB，不等待 scheduler。

預期：

- command 為 `ENABLE_OFF_PEAK`，HTTP 200。
- `enableOffPeak=true`，啟用者為 Tester A。
- driverState 立即為 `OFF_PEAK_WAITING`；主要操作顯示「取消排隊」。
- 因目前不在 OFF_PEAK window，`e_connector_priority` 尚未建立。
- 「取消排隊」與 switch OFF 都送 `DISABLE_OFF_PEAK`；成功後回 READY，清除 off-peak actor。

### M1397-009：進入離峰 window 後轉 QUEUED

對應：ROT-001、ROT-004、ROT-021。

1. tariff 改為 `E2E1397ACTIVE`，保持 PRI-0。
2. 從 LIFF 將離峰切為 ON。
3. 先確認立即回 OFF_PEAK_WAITING。
4. 等待最多 10 分鐘的正常 off-peak scheduler；為縮短人工測試，可由 BE 呼叫既有維運 enqueue endpoint，
   但 Tester 的產品操作仍必須是 LIFF switch ON。
5. 觀察 WebSocket 更新。

預期：

- scheduler 建立 `source=OFF_PEAK`、requester=啟用者的 Priority。
- driverState 由 OFF_PEAK_WAITING 轉 QUEUED，操作仍是「取消排隊」。
- private slot=0，不 dispatch RemoteStart，不出現幽靈 PREPARING。
- 取消後關閉長期離峰並刪除 OFF_PEAK queue；不得刪除其他來源 queue。

## 9. 私樁實際充電案例（PRI-1）

### M1397-010：私樁手動 Start／Stop

對應：CHG-005、CHG-020、CHG-029、CHG-034、ROT-002。

1. 先將 off-peak 關閉並清空 queue，切換 PRI-1。
2. Tester A 插槍後從 LIFF 按「開始充電」。
3. 觀察 READY → PREPARING → CHARGING 與實際供電。
4. 再按「停止充電」，觀察 CHARGING → FINISHING → READY。

預期與 M1397-003／004 相同，另須確認 private slot 有效、沒有先建立 MANUAL queue，
且整個 transaction 的 area 為 private。

### M1397-011：離峰自動開始、停止後抑制本 window

對應：ROT-008、ROT-021、ROT-022、CHG-034。

1. PRI-1、tariff=`E2E1397ACTIVE`，車輛插在 `CP001-002`，off-peak 起始為 OFF。
2. Tester A 從 LIFF 開啟離峰。
3. 等待正常 scheduler enqueue／dispatch；必要時 BE 只觸發既有 enqueue 與 rotation tick。
4. 觀察 OFF_PEAK_WAITING → QUEUED → PREPARING → CHARGING。
5. CHARGING 時嘗試操作離峰 switch。
6. 按「停止充電」，閱讀 modal 後確認。
7. StopTransaction 完成後，至少跨兩次 scheduler cycle 觀察是否重啟。
8. 再將 off-peak OFF，然後 ON，確認可重新加入本 window。

預期：

- CHARGING 時 FE 不允許關閉 off-peak switch。
- 停止 modal 清楚告知：會停止目前充電、本 window 不再自動啟動、長期偏好仍保持。
- StopTransaction 後 `enableOffPeak=true`，driverState=`OFF_PEAK_WAITING`，
  reason=`USER_CANCELLED_FOR_CURRENT_WINDOW`。
- suppression window 持久化；同一 window scheduler 不得重新 dispatch。
- OFF → ON 明確重設 suppression 後，才可重新 OFF_PEAK_WAITING／QUEUED／dispatch。

### M1397-012：離峰 transaction 非使用者停止而結束

對應：CHG-003、ROT-003、ROT-022。

1. 重新建立一筆離峰 CHARGING transaction。
2. 不按 LIFF 停止，改用拔槍或讓設備產生真實 StopTransaction。
3. 觀察 LIFF、DB 與後續 scheduler。

預期：

- 不猜測滿電或拔槍，不顯示「已充滿」或「槍未正確連接」。
- `enableOffPeak=true`，driverState=`OFF_PEAK_WAITING`，
  reason=`TRANSACTION_ENDED_FOR_CURRENT_WINDOW`。
- 本 window 不再自動開始；下一個獨立 window 才恢復排程。

### M1397-013：RemoteStart／RemoteStop timeout 與一次性失敗提示

對應：CHG-009、CHG-018、CHG-032、CHG-034、CHG-035。

RemoteStart：

1. PRI-1、off-peak OFF，使用不會建立充電需求的狀態，從 LIFF 按開始。
2. 記錄 operationId，等待 CloudLink 既有 RemoteStart deadline 加 30 秒 reconciliation grace。
3. 不重複按開始。

預期：PREPARING 期間不盲目重送；最終 timeout result 只顯示一次，手動來源回 READY。
若 grace 後才收到 StartTransaction，實際 transaction 優先轉 CHARGING，但不重播 timeout modal。

RemoteStop：

1. 在可控制的測試 transaction 中暫時製造 StopTransaction 不回或晚回。
2. 從 LIFF 按停止，等待 30 秒 deadline。

預期：30 秒內 FINISHING；deadline 時 transaction 仍 ACTIVE 則顯示一次停止失敗並回 CHARGING，
允許新的明確停止。晚到 StopTransaction 仍完成 transaction reconciliation，但不重播舊 modal。

本案例需要 BE／CP observer 可安全製造 timeout；若無法控制，標記 Blocked，不得用單元測試代替。

## 10. 多使用者與重啟案例

### M1397-014：operationResult 只送給發起者

對應：CHG-035。

1. Tester A、B 同時開啟同一支 private `CP001-002` 詳細頁。
2. Tester A 從 LIFF 發起 Start 或 Stop，製造一個 terminal success／failure result。
3. 同時錄製兩支手機畫面與 WebSocket event。

預期：A、B 都收到共用 driverState UPDATED；只有 A 收到該 operationResult modal。
兩邊都不得看到 browser payload 中暴露另一人的 Branch user identity。

### M1397-015：PUBLIC／PRIVATE 停止授權差異

對應：CHG-020、CHG-021、CHG-036。

PUBLIC：

1. PUB 模式，由 Tester A 啟動 `CP001-002`。
2. Tester B 開啟同樁並嘗試停止。

預期：B 可看到共用 CHARGING presentation，但 API 回 403
`TRANSACTION_OWNED_BY_OTHER_USER`；不 dispatch RemoteStop，設備繼續充電。最後由 A 正常停止。

PRIVATE：

1. PRI-1 模式，確認 A、B 都是目前關聯住戶。
2. 由 A 啟動 `CP001-002`，再由 B 停止。

預期：B 可以停止；原 transaction owner 不變，但 operation audit 的 stop requester 為 B。

### M1397-016：重啟恢復與 terminal result 不重播

對應：CHG-034、CHG-035、UI-010、REL-004。

1. 從 LIFF 建立可維持數秒以上的 PREPARING 或 FINISHING，記錄 operationId。
2. BE observer 依 Ken 環境 SOP 重啟 `ems-branch-api`。
3. 服務恢復後，Tester 重新連入詳細頁。
4. 完成該 operation，關閉頁面再重新進入。

預期：

- 重啟後 initial snapshot 從持久化 operation 還原 PREPARING／FINISHING 與相同 operationId。
- 重啟不建立第二筆 operation、不重複送 Start／Stop。
- operation 終結後重新開頁，initial snapshot 不重播 terminal operationResult。
- 仍生效的問題只由目前 driverState reason／description 表達。

## 11. 每案 DB／log 對帳

### 11.1 DB 查詢

```sql
SELECT id, operation_type, source, requested_by_user_id, connector_id,
       transaction_id, request_time, accepted_at, deadline_at, grace_until,
       completed_at, status, operation_status, failure_code, remark
FROM e_remote_transaction_log
WHERE connector_id = 'CP001-002'
ORDER BY id DESC
LIMIT 10;

SELECT id, connector_id, source, requested_by_user_id, charge_group,
       priority, start_charging_time, charge_status, status_reason,
       create_time, update_time
FROM e_connector_priority
WHERE connector_id = 'CP001-002'
ORDER BY create_time DESC;

SELECT transaction_id, connector_id, id_tag, start_timestamp, stop_timestamp,
       meter_start, meter_stop, reason, status, energy_consumed
FROM e_transactions
WHERE connector_id = 'CP001-002'
ORDER BY transaction_id DESC
LIMIT 10;

SELECT id, area, enable_off_peak, off_peak_enabled_by_user_id,
       off_peak_suppressed_window_start, off_peak_suppressed_window_end,
       off_peak_suppression_reason, charge_group, current_transaction_id
FROM e_connectors
WHERE id = 'CP001-002';
```

### 11.2 log 位置

- Branch：`/opt/ems/ems_branch/api/logs/`
- Charge Point：`/opt/ems/ems_cp/api/logs/`
- HQ staging：docker1 的 `ems-hq-staging-api` container log

每個 operationId 要能串起 LIFF command、HQ、Branch Remote operation、OCPP command 與 transaction。

## 12. 測後還原

1. 從 LIFF 正常停止所有 transaction，等兩樁 AVAILABLE、Relay OFF。
2. 若 private off-peak 仍為 ON，先從 LIFF 切 OFF，確認 queue 清除。
3. 依 4.1 當天留下的基線還原兩樁；若當天基線仍是 2026-07-31 狀態，可執行：

```sql
DELETE FROM e_connector_priority
WHERE connector_id IN ('CP001-001', 'CP001-002');

DELETE FROM e_connector_tariff
WHERE id IN ('CP001-001', 'CP001-002');

UPDATE e_connectors
SET area = 'public', enable_off_peak = NULL,
    off_peak_enabled_by_user_id = NULL,
    off_peak_suppressed_window_start = NULL,
    off_peak_suppressed_window_end = NULL,
    off_peak_suppression_reason = NULL,
    charge_group = NULL
WHERE id IN ('CP001-001', 'CP001-002');

UPDATE cp_connectors
SET area = 'public'
WHERE id IN ('CP001-001', 'CP001-002');

DELETE FROM e_tariff_time_slot
WHERE tariff_id IN ('E2E1397WAIT', 'E2E1397ACTIVE');

DELETE FROM e_tariff_table
WHERE id IN ('E2E1397WAIT', 'E2E1397ACTIVE');

DELETE FROM e_charge_group
WHERE id = 'CG_E2E_1397';
```

4. 重跑 4.1、4.2 查詢，確認與基線一致。
5. 從 LIFF 重新進入兩樁詳細頁，確認皆為 public、READY、不顯示離峰 switch。

不得刪除 `e_remote_transaction_log`、`e_transactions` 或 OCPP log；它們是本輪 E2E 證據。

## 13. Test Run 報告要求

正式執行時另建 `test_report/YYYYMMDD_NNN_redmine_1397_liff_manual_e2e.md`，逐案填：

- Test case ID／Catalog case ID。
- 實際前置資料與 fixture mode。
- LIFF 完整操作步驟。
- Expected／Actual 畫面與狀態轉換。
- API status／body、WebSocket event、operationId。
- DB／log／實體 Relay 與電流證據。
- PASS／FAIL／BLOCKED 與 defect ticket。
- 清理與還原結果。

本計畫是待執行步驟，不得預先填寫 PASS。
