# LINE / Telegram 驗證整合煙霧測試模式

> 這份文件給未來要補或修改 smoke / integration test 的人看。
>
> 目標不是「再寫一批單元測試」，而是驗證：**真事件路徑接起來後，整條驗證 / 授權 / handoff 水管沒有漏水。**

相關背景：
- `references/line-telegram-verification-lifecycle.md`
- `references/line-telegram-test-map.md`
- `references/line-telegram-must-run-commands.md`

---

## 什麼情況該寫 smoke test

當你改到下面任一類時，不能只靠 helper / route unit tests：

- LINE / Telegram adapter intake gate
- onboarding / verification state machine
- verified email handoff guard
- quota fallback / email fallback
- admin unbind / pairing revoke
- runner authz 與 verified binding 的交界

一句話判斷：

> 如果這個改動需要同時假設「adapter 有正確 dispatch」和「store / authz 也會照預期接住」，就該補 smoke test。

---

## Smoke test 的最小骨架

一支合格的 LINE / Telegram 驗證 smoke test，至少要穿過這些步驟：

1. **真平台事件進 adapter**
2. **adapter 真的 dispatch 到 `handle_message(...)`**
3. **`GatewayUserStore.process_inbound_message(...)` 產生 onboarding / verification decision**
4. **`verify_token(...)` 後 verified binding 生效**
5. **live auth / handoff 路徑真的吃到 verified binding**
6. **`unbind_identity(...)` 後 verified / pairing / authz / handoff 失效**
7. **同一來源再次進來時，可重新 onboarding**

如果只測到第 3 步或第 4 步，通常還不算 smoke，只算比較胖的 unit test。

---

## 目前已存在的參考實作

檔案：
- `tests/gateway/test_line_telegram_verification_smoke.py`

關鍵測試：
- `test_line_dm_verification_smoke`
- `test_telegram_dm_verification_smoke`

這兩顆目前已覆蓋：

### LINE
- DM event 經 `LineAdapter._dispatch_event(...)` 進來
- 進 `GatewayUserStore.process_inbound_message(...)`
- `verify_token(...)` 後 `get_verified_email_for_source(...)` 生效
- `_send_email_quota_fallback(...)` 真的寄到 live 綁定 email
- `unbind_identity(...)` 後 fallback 失效
- 同來源再次進來可重新要求驗證

### Telegram
- text update 經 `TelegramAdapter._handle_text_message(...)` 進來
- text batch flush 後真的 dispatch
- `verify_token(...)` 後 verified source 生效
- `PairingStore()._approve_user(...)` 後 runner authz 生效
- `unbind_identity(...)` 後 runner authz 失效
- 同來源再次進來可重新要求驗證

---

## 兩個最容易踩的坑

### 1. Telegram text handler 不是同步直送

**症狀：**
- 你呼叫 `_handle_text_message(...)`
- 但 `decisions` / `handle_message` 沒收到任何東西
- 看起來像 Telegram 流程壞掉

**真正原因：**
- Telegram text path 先進 `_enqueue_text_event(...)`
- 真正 dispatch 發生在 `_flush_text_batch(...)`
- 如果測試沒等 flush，會誤判成流程沒進去

**建議模式：**

```python
async def _run_telegram_text(adapter, update):
    await adapter._handle_text_message(update, SimpleNamespace())
    pending = list(adapter._pending_text_batch_tasks.values())
    if pending:
        await asyncio.gather(*pending)
```

**原則：**
不要只呼叫 `_handle_text_message()` 就假設 event 已送達。

---

### 2. LINE / Telegram 第二次重送 onboarding 可能撞到 resend cooldown

**症狀：**
- 你在 unbind 後立刻重送同一個 email
- 預期拿到 `send_verification_email`
- 實際卻拿到 `handled`

**真正原因：**
- `verification_requests.last_sent_at` 還在 cooldown 視窗內
- 你撞到 resend / rate-limit 保護，不是狀態機壞掉

**建議模式：**
在本地 smoke test 裡，把 `last_sent_at` 人工往前調，讓你驗的是「重新 onboarding 能否成立」，不是節流是否生效。

```sql
UPDATE verification_requests
SET last_sent_at = datetime('now', '-11 minutes')
WHERE platform = ? AND external_user_id = ?
```

**原則：**
當測試目標是驗證狀態轉換，允許把節流窗移開；但要在註解或文件說明這是為了避開 rate-limit 噪音。

---

## 建議測試設計原則

### A. 盡量走真 adapter 入口

優先選：
- LINE：`_dispatch_event(...)`
- Telegram：`_handle_text_message(...)`

比起直接手刻 `MessageEvent` 然後呼叫 `handle_message(...)`，這樣更能抓到：
- source 解析錯誤
- DM / group / room 分流錯誤
- prefilter / batching / postback 之類的邊界問題

### B. mock 要薄，不要整條路都 fake 掉

可以 mock：
- LINE client 的 reply / push / loading
- email send function
- bot network calls

不要 mock 到：
- `GatewayUserStore.process_inbound_message(...)`
- `verify_token(...)`
- `unbind_identity(...)`
- runner `_is_user_authorized(...)`

否則你只是把 smoke test 再拆回單元測試 cosplay。

### C. 要驗「生效」與「失效」兩面

不夠只驗：
- verify 後可用

還要驗：
- unbind 後失效
- pairing revoke 後失效
- fallback / handoff 不再寄到舊綁定 email

### D. 最後一輪最好重跑 onboarding

真正容易壞的常常不是第一次 onboarding，而是：
- verify 過一次
- unbind
- 再次 onboarding

所以 smoke test 最後建議再讓同一來源進來一次，確認沒有殘留狀態把它卡死。

---

## 建議命名方式

推薦：
- `test_line_dm_verification_smoke`
- `test_telegram_dm_verification_smoke`
- `test_<platform>_<path>_smoke`

不推薦：
- `test_integration_1`
- `test_full_flow`
- `test_regression`

原則是：
**檔名 / test name 一眼要看得出是哪個平台、哪條路、是不是 smoke。**

---

## 什麼時候要擴充既有 smoke，而不是另開新檔

優先擴充 `tests/gateway/test_line_telegram_verification_smoke.py`，如果你的改動仍屬於：
- DM onboarding / verification
- verified binding / handoff
- unbind / pairing revoke
- authz 存活與失效

只有在測試目標已明顯換成另一條主線時，再另開檔，例如：
- forum / topic / group 專用 state machine
- webhook retry / duplicate event idempotency
- home-channel / control-channel 特化行為

---

## Smoke test 完成後至少再跑什麼

最少建議：

```bash
pytest -q \
  tests/gateway/test_line_telegram_verification_smoke.py \
  tests/hermes_cli/test_web_verification_admin.py \
  tests/gateway/test_line_plugin.py \
  tests/gateway/test_telegram_auth_check.py \
  tests/gateway/test_verified_email_handoff_guard_helpers.py
```

如果改到 pairing / allowlist bypass，再加：

```bash
pytest -q \
  tests/gateway/test_pairing.py \
  tests/gateway/test_pairing_allowlist_bypass.py
```

---

## 一句話總結

> Smoke test 的任務不是證明某個 helper 很乖，
> 而是證明 **LINE / Telegram 真事件進來後，adapter、store、authz、handoff、unbind 這幾層還能一起活著回家。**
