# T517 — test-m1-profile-watch-quota

> Status: `done`  
> Milestone: `M1`  
> Kind: `test`  
> Est. change: `~180 lines`

## Goal

完成後系統應：M1 的驗收 ID（SP-01、SP-02、RW-01～RW-04）有可重跑的整合測試把關。

## Depends on

- T513、T514

## Inputs

- Spec：`../spec.md` §9.1
- 既有整合測試模式：`apps/backend/internal/testkit/**`、`make test-integration`

## Outputs

### 程式變更（預期路徑）

| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/internal/logic/radar/m1_integration_test.go` | add | 覆蓋 SP-01～SP-02、RW-01～RW-04 |
| `apps/backend/internal/testkit/**` | edit | 需要時補 radar fixture 與 fake AI provider |

### 行為變更

- 無（測試）。

## Out of scope

- sweep／判定測試（T534）

## Acceptance

- [x] 六個 ID 各有一個具名 test case，失敗訊息可對回 spec ID
- [x] AI 呼叫走 fake，測試不打真 provider
- [x] 指令：

```bash
cd apps/backend && go test ./internal/logic/radar/... -count=1 && make test-integration
```

## Notes

### 實作落點

- `apps/backend/internal/logic/radar/m1_integration_test.go`：六個具名 case，名稱直接帶 spec ID，失敗訊息也以 ID 開頭。

| Test | 蓋到什麼 |
|------|----------|
| `TestM1_SP01_ActiveWatchWithoutServiceProfileIsRejected` | 沒服務檔案建 active → 400100＋訊息指向 `service-profile`；被擋不留半筆；停用狀態仍可先建 |
| `TestM1_SP02_ServiceProfileReadsBackEveryField` | 存檔後 GET 回全部欄位，`forbidden[]` 可讀，`updated_at` 是 ns |
| `TestM1_RW01_SecondActiveWatchOverQuotaIsRejected` | 上限 1 時第二個 active 被拒（含上限與 upgrade 提示）；既有那筆不被降級；停用不佔配額 |
| `TestM1_RW02_PausedWatchLeavesTheDailySweep` | pause 後離開 `ListActiveWatches`（每日排程來源），設定保留，resume 回到名單 |
| `TestM1_RW03_SuggestReturnsAdoptableTerms` | 建議每則有理由、不自動建訂閱、採用後可成功 create、計 `ai_copy`／`radar.suggest` |
| `TestM1_RW04_ArchivedWatchStopsProducingAndStaysVisible` | archived 退出巡的名單但列表看得到；resume／pause 都被拒 |

- `apps/backend/Makefile`：`test-integration` 加入 `./internal/module/radar/...` 與 `./internal/logic/radar/`，否則這個 gate 跑不到 M1。

### 決策

- **不新增 testkit fixture**：既有 `validProfileReq()` 與 memory repository 已足夠，另建一套 radar fixture 只會多一份要同步的真相。
- **只假造兩件事**：儲存層用 memory（不必起 mongo）、AI 用 `fakeSuggestAI`（不打 provider、不花錢）；配額、狀態機、擁有者隔離全部走真的實作。
- **RW-02／RW-04 用 `ListActiveWatches` 當斷言對象**：那是每日排程真正讀的集合，斷言它才等於斷言「不再排入每日巡」；只看 status 字串會漏掉排程來源改壞的情況。
