diff --git a/cmd/arcrun-app/freshinstall_e2e_test.go b/cmd/arcrun-app/freshinstall_e2e_test.go new file mode 100644 index 0000000..b60c5ea --- /dev/null +++ b/cmd/arcrun-app/freshinstall_e2e_test.go @@ -0,0 +1,63 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// TestFreshInstallCollectorStarts 端到端釘住 leo 2026-08-06 Windows 事故的完整鏈條: +// +// 全新安裝 → App 存 config → supervisor 用「同一支執行檔 + --collector」拉起同步引擎。 +// +// 事故當時這條鏈在最後一步斷掉:config 少了 `manifest` ⇒ `exit status 2` ⇒ 無限重拉。 +// 上一支測試只驗到「LoadDirectConfig 讀得下去」;這支**真的把執行檔跑起來**, +// 因為「函式回傳 nil」與「行程活得下去」是兩件事——事故就是死在後者。 +// +// 用 --once --dry-run:只走到解析設定就收工,不 POST、不寫 manifest,不動到真資料。 +func TestFreshInstallCollectorStarts(t *testing.T) { + // 🔴 先編再改 HOME:把 HOME 指到暫存目錄後才 `go build`,Go 會把**唯讀的**模組快取 + // 寫進那個目錄,測試結束清不掉(實撞:TempDir RemoveAll permission denied)。 + 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) + + cfg := &directConfig{ + Accounts: []accountCfg{{ + CypherURL: "https://example.workers.dev", + Namespace: "abc123", + APIKey: "abc123", + }}, + Extractor: "workers-ai", + } + if err := saveCfg(cfg); err != nil { + t.Fatalf("存檔失敗:%v", err) + } + + // 用剛才編好的執行檔自己當 collector 跑(=supervisor 在真機上做的事) + 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() + } else if err != nil { + t.Fatalf("跑不起來:%v", err) + } + + if code == 2 { + t.Fatalf("同步引擎以 exit status 2 結束=設定被拒,就是 leo 撞到的無限重試。\n輸出:%s", out) + } + if strings.Contains(string(out), "缺必填欄位") { + t.Fatalf("設定仍缺必填欄位:%s", out) + } + t.Logf("✅ 全新安裝的設定,同步引擎吃得下去(exit=%d)", code) +}