Files
arcrun-collector/direct_t182_test.go
T
Leo 133f5add46 t182:老 config 抹除成 Workers AI+雲端探測——leo 實測「無卡」的真因
leo 08-04 實測回報(更新 v0.15.5 後仍顯示 Gemini/丟 PDF 無卡),
查出來不是 Workers AI 不通,是**根本沒走到那條路**。三顆真 bug:

① 抹除只做一半(leo:「就要抹除改成用 Workers AI,如果保持 Gemini
   它不會改掉,那就是失敗的」)
   t181 只在 RunDirectOnce 改頂層記憶體值,但
   - makeAccountSubConfig 會把**帳號層**舊值蓋回來(t126「帳號層優先」)
   - 完全**沒寫回檔案** ⇒ 托盤是另一個行程,讀檔還是念 Gemini
   leo 的 config 正是頂層+兩個帳號各留 "gemma"、無 explicit
   ⇒ 畫面顯示 Gemini、萃取也真的跑 Gemini。
   修:遷移移到 LoadDirectConfig(每次啟動必經),逐層抹除 + 寫回檔案。
   金鑰保留(Gemini 是選配不是廢除)。

② 驗證器擋掉自己的新預設
   `extractor 只能是 claude / gemma` ⇒ config 一旦寫成 workers-ai,
   daemon 直接載入失敗起不來。v0.15.5 已帶著這顆出貨。

③ 托盤標籤照 config 舊字串念(t178 的通則版)
   t178 只把 claude 這**一個**殘留值導向 Gemini,殘留 gemma 一樣脫鉤。
   改成與 direct.go 同一條判準:沒 explicit 就一律念「雲端 AI」。

+ t182 雲端探測(leo 指定設計:「會去掃一次看雲端是否裝好,沒裝好就顯示
  workers AI 還沒通,一旦通了就顯示可用」):ProbeWorkersAI 逐帳號探
  /portal/daemon/extract(送空 text,不燒 LLM 額度),結果寫進 status.json
  由托盤「狀態:」講白話。**不做靜默退回 Gemini**——那會讓用戶永遠
  不知道自己雲端沒更新。

實測證據(非 mock,打真實例):
- youlin 實例萃真卡:3.67s,產出完整知識卡(一句話定義/要點/關鍵實體/關聯)
- 連測三次:3.51s / 3.34s / 3.59s(Gemini 實測 16.87s ⇒ 快約 5 倍)
- 兩個實例都已有 /portal/daemon/extract(皆回 400=route 存在)
- 拿 leo 真 config 複本跑遷移:三層全抹成 workers-ai、金鑰留著、重讀仍是 workers-ai

測試:新增 direct_t182_test.go(抹除全層/寫回磁碟/主動選過不動/合法值/冪等);
既有三則因預設變更而失效的斷言已更新(t108 二進位不出機的契約未放寬,仍綠)。
全套綠。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:29:56 +08:00

148 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// direct_t182_test.go — t182:老 config 必須被**抹除**成 Workers AIleo 2026-08-04)。
//
// 「如果是我的 config 保持舊的,那新版裝上就要檢查,因為已經是 default worker AI
// **就要抹除改成用 Workers AI**,如果保持 Gemini 它不會改掉,**那就是失敗的**」
//
// 這組測試守的是 leo 08-04 實撞的三個真 bug
// 1. 更新到 v0.15.5 後托盤**兩個帳號都還顯示 Gemini**(帳號層舊值沒清)
// 2. 丟 PDF 進去**產不出卡**(因為根本沒走到 Workers AI,還在跑 Gemini
// 3. `workers-ai` 不在合法值清單裡 ⇒ 一旦寫進 config,daemon 直接起不來
package main
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
// t182①:老 configextractor=gemma、帳號層也是 gemma、無 explicit
// → 每一層都要被抹成 workers-ai,而且**要寫回檔案**。
func TestT182ErasesLegacyExtractorAllLayers(t *testing.T) {
dir := t.TempDir()
p := writeDirectConfig(t, dir, map[string]any{
"manifest": filepath.Join(dir, "m.json"),
"extractor": "gemma", // 舊值:頂層
"gemini_api_key": "old-key",
// 注意:**沒有** extractor_explicit ⇒ 使用者從沒主動選過
"accounts": []map[string]any{
{
"cypher_url": "https://a.example", "namespace": "nsA",
"watch_folders": []string{"/tmp/a"},
"extractor": "gemma", // 舊值:帳號層(leo 的 config 正是這樣)
},
{
"cypher_url": "https://b.example", "namespace": "nsB",
"watch_folders": []string{"/tmp/b"},
"extractor": "claude", // 更舊的殘留值
},
},
})
cfg, err := LoadDirectConfig(p)
if err != nil {
t.Fatalf("LoadDirectConfig: %v", err)
}
if cfg.Extractor != "workers-ai" {
t.Errorf("① 頂層沒被抹除:got %q want workers-ai", cfg.Extractor)
}
for i, a := range cfg.Accounts {
if a.Extractor != "workers-ai" {
t.Errorf("① 帳號[%d] 沒被抹除:got %q want workers-aileo 實撞:兩個帳號都還顯示 Gemini)", i, a.Extractor)
}
}
// 金鑰要留著——Gemini 只是變選配,不是廢除(leo:「客戶說他要用 Gemini」)
if cfg.GeminiAPIKey != "old-key" {
t.Errorf("① Gemini 金鑰不該被清掉:got %q", cfg.GeminiAPIKey)
}
// 真的寫回檔案了嗎?(只改記憶體 ⇒ 托盤是另一個行程,還是會念 Gemini)
raw, err := os.ReadFile(p)
if err != nil {
t.Fatal(err)
}
var onDisk struct {
Extractor string `json:"extractor"`
Accounts []struct {
Extractor string `json:"extractor"`
} `json:"accounts"`
}
if err := json.Unmarshal(raw, &onDisk); err != nil {
t.Fatal(err)
}
if onDisk.Extractor != "workers-ai" {
t.Errorf("① **沒寫回檔案**:磁碟上頂層仍為 %q ⇒ 托盤下次讀還是念 Gemini", onDisk.Extractor)
}
for i, a := range onDisk.Accounts {
if a.Extractor != "workers-ai" {
t.Errorf("① **沒寫回檔案**:磁碟上帳號[%d] 仍為 %q", i, a.Extractor)
}
}
}
// t182②:主動選過 Gemini 的人不被動到(Gemini 是選配、不是廢除)。
func TestT182KeepsExplicitGeminiChoice(t *testing.T) {
dir := t.TempDir()
p := writeDirectConfig(t, dir, map[string]any{
"manifest": filepath.Join(dir, "m.json"),
"extractor": "gemma",
"extractor_explicit": true, // ← 使用者在「AI 設定…」主動選過
"gemini_api_key": "my-key",
"accounts": []map[string]any{
{"cypher_url": "https://a.example", "namespace": "nsA", "watch_folders": []string{"/tmp/a"}},
},
})
cfg, err := LoadDirectConfig(p)
if err != nil {
t.Fatalf("LoadDirectConfig: %v", err)
}
if cfg.Extractor != "gemma" {
t.Errorf("② 主動選過 Gemini 不該被抹掉:got %q", cfg.Extractor)
}
}
// t182③:`workers-ai` 必須是合法值——否則寫進 config 後 daemon 直接起不來。
// (v0.15.5 就踩到這顆:預設改了、驗證器沒跟上。)
func TestT182WorkersAIIsValidExtractor(t *testing.T) {
dir := t.TempDir()
p := writeDirectConfig(t, dir, map[string]any{
"manifest": filepath.Join(dir, "m.json"),
"extractor": "workers-ai",
"extractor_explicit": true,
"accounts": []map[string]any{
{"cypher_url": "https://a.example", "namespace": "nsA", "watch_folders": []string{"/tmp/a"}},
},
})
if _, err := LoadDirectConfig(p); err != nil {
t.Fatalf("③ workers-ai 應為合法值,卻載入失敗:%v", err)
}
}
// t182④:冪等——已是 workers-ai 再載一次不應改動任何東西。
func TestT182MigrationIdempotent(t *testing.T) {
dir := t.TempDir()
p := writeDirectConfig(t, dir, map[string]any{
"manifest": filepath.Join(dir, "m.json"),
"extractor": "workers-ai",
"accounts": []map[string]any{
{"cypher_url": "https://a.example", "namespace": "nsA",
"watch_folders": []string{"/tmp/a"}, "extractor": "workers-ai"},
},
})
before, err := os.ReadFile(p)
if err != nil {
t.Fatal(err)
}
if _, err := LoadDirectConfig(p); err != nil {
t.Fatal(err)
}
after, err := os.ReadFile(p)
if err != nil {
t.Fatal(err)
}
if string(before) != string(after) {
t.Error("④ 已是 workers-ai 不該再改寫檔案(非冪等=每次啟動都寫一次磁碟)")
}
}