Files
arcrun-collector/cmd/arcrun-app/upgrade_repair_test.go
T
Leo 8dc9a2ee85 v0.18.13:舊設定檔自我修復——我第一次修錯了,只修好全新安裝
leo 裝了 v0.18.12 **仍然是同一句錯**(實拍:重試 30 次、
「collector direct: config 缺必填欄位:manifest」),並提供了他的 config.json。

## 我錯在哪
v0.18.11 只在 `saveCfg`(存檔)補必填欄位。但使用者打開程式時
**只是「讀 config → 啟動引擎」,saveCfg 根本不會被呼叫**
⇒ 已經存在的壞設定永遠修不好。全新安裝好了,**現有使用者一個都沒被救到**。

leo 的 config 鐵證:頂層只有 `accounts`/`extractor`/`extractor_explicit`/
`gemini_api_key` —— **正好是 saveCfg 寫的那四個鍵**,沒有 manifest。

## 為什麼我的測試沒抓到
`TestFreshInstallCollectorStarts` 是**從 saveCfg 出發**建 config ——
它測的是「我修的那條路」,不是「leo 正在走的那條路」。
**測試照著修正寫,就只會證明修正存在,不會證明問題解決。**

## 這版
- `fillRequired()` 抽出來,**讀取時也補**(升級路徑)+存檔時也補(新建路徑)
- 讀取時補完**寫回磁碟**——collector 是另一個行程,讀的是磁碟那份不是記憶體
- 補完寫進 app.log 留痕

## 驗
新測試 `TestExistingBrokenConfigSelfHeals` **從磁碟上已存在的壞 config 出發**
(內容逐字取自 leo 那份,只把值改假),驗三件:讀取後磁碟上有 manifest、
JSON 仍合法、**真的把執行檔當 collector 跑起來不再 exit 2**。
反向驗證:拿掉自我修復即紅(訊息就是 leo 撞到的那句)。全套測試綠。

⚠️ 更正 changelog:v0.18.11 原本寫「已經裝過舊版的人不用做任何事,更新後會自動補好」
   —— **那句是假的**,已改成指向本版。
2026-08-06 18:58:31 +08:00

74 lines
2.6 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.
package main
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// leo 2026-08-06 在 Windows 上真實壞掉的那份 config(逐字,只把值改成假的)。
// 它剛好只有 saveCfg 會寫的四個鍵 —— 那就是「App 自己存出來、但少了必填欄位」的鐵證。
const leoBrokenConfig = `{
"accounts": [{"cypher_url":"https://x.workers.dev","namespace":"abc123","api_key":"abc123"}],
"extractor": "workers-ai",
"extractor_explicit": true,
"gemini_api_key": ""
}`
// TestExistingBrokenConfigSelfHeals 釘住我**第一次修錯**的那條路:
//
// 只在 saveCfg 補必填欄位 ⇒ 全新安裝好了,但**已經存在的壞設定永遠修不好**,
// 因為 App 開起來只是「讀 config → 啟動引擎」,saveCfg 根本沒被呼叫。
// leo 裝了 v0.18.12 仍看到同一句「缺必填欄位:manifest」、重試 30 次。
//
// 所以這支測試從**磁碟上已存在的壞 config** 出發,而不是從 saveCfg 出發。
func TestExistingBrokenConfigSelfHeals(t *testing.T) {
bin := filepath.Join(t.TempDir(), "arcrun-app-test")
if out, err := exec.Command("go", "build", "-o", bin, ".").CombinedOutput(); err != nil {
t.Fatalf("編不出執行檔:%v\n%s", err, out)
}
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
if err := os.MkdirAll(appDir(), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(configPath(), []byte(leoBrokenConfig), 0o600); err != nil {
t.Fatal(err)
}
// App 讀一次 config —— 這就是使用者「打開程式」會發生的事
if _, err := loadCfg(); err != nil {
t.Fatalf("讀不了 config%v", err)
}
// 修復必須**寫回磁碟**:collector 是另一個行程,讀的是磁碟那份,不是記憶體
raw, err := os.ReadFile(configPath())
if err != nil {
t.Fatal(err)
}
var m map[string]any
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatalf("修完不是合法 JSON%v", err)
}
if s, _ := m["manifest"].(string); strings.TrimSpace(s) == "" {
t.Fatal("讀取後沒把 manifest 補進磁碟 ⇒ collector 仍會 exit 2(就是 leo v0.18.12 撞到的)")
}
// 終極驗收:真的把執行檔當 collector 跑,不能再是 exit 2
cmd := exec.Command(bin, collectorModeFlag, "direct", "--once", "--dry-run", "--config", configPath())
cmd.Env = append(os.Environ(), "HOME="+home, "USERPROFILE="+home)
out, err := cmd.CombinedOutput()
code := 0
if ee, ok := err.(*exec.ExitError); ok {
code = ee.ExitCode()
}
if code == 2 || strings.Contains(string(out), "缺必填欄位") {
t.Fatalf("舊 config 修復後同步引擎仍被拒(exit=%d):%s", code, out)
}
}