Files
arcrun-collector/cmd/arcrun-app/manifest_field_test.go
T
Leo fcf00bb5df v0.18.11:修好 Windows「裝了完全不會動」的真兇——全新 config 缺 manifest 欄位
leo 08-06 提供決定性線索:**exit status 2,已自動重試 21 次失敗**。

## 真兇
`app.go` 的 `saveCfg` 只寫四個鍵(accounts/extractor/extractor_explicit/
gemini_api_key),**從來不寫 `manifest`**。
而 `manifest` 是 collector 的必填欄位(`direct.go`:
`if c.Manifest == "" { missing = append(missing, "manifest") }`)
⇒ 讀 config 失敗 → `runDirect` 回 2 → supervisor 無限重拉
⇒ ①「看守中/沒有在跑」一直閃、重開無效(死因沒變)
⇒ ②加資料夾沒反應(collector 從沒跑完一輪)

## 為什麼一路活到封測
**開發機的 config 是舊版留下的、早就有這一欄。**
只有「從零長出 config」的機器才會撞到——也就是**每一台封測者的機器**。
「我這台好好的」正是這個 bug 能活下來的原因。
⇒ 新測試 `TestFreshConfigIsUsableByCollector` **從零建 config**,
   並且不只驗欄位在不在,而是**真的餵給 `collector.LoadDirectConfig`**。
   已反向驗證:把修正拿掉,測試會紅(不是假測試)。

## 順帶修好「為什麼只看得到 exit status 2」
supervisor 在行程結束時用 `cmd.Wait()` 的錯誤當訊息,
**蓋掉**了 stderr 剛寫進 `LastError` 的那句真話
(`collector direct: config 缺必填欄位:manifest`)。
⇒ 改成以 stderr 為主、退出碼放括號補充。
(v0.18.10 已讓死因上畫面+進 app.log,這版讓那句話是**對的**那一句。)

## 三種 exit 2 的實測輸出(重現測試打出來的,非推測)
- config 不是合法 JSON → `config JSON 解析失敗:invalid character …`
- 缺必填欄位 → `config 缺必填欄位:manifest, accounts(或 cypher_url 連線設定)`
- Windows 反斜線路徑 → 正常解析(**排除「反斜線害的」這個猜測**)

## 驗
collector 全測試過、app 測試過、mac+windows 皆編得過;
產物 `Arcrun-win-v0.18.11.exe`(22M)已放 leo 桌面。
⚠️ **尚未真機驗**——要 leo 在 Windows 全新裝一次才算通。
2026-08-06 18:41:31 +08:00

61 lines
2.0 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"
"path/filepath"
"strings"
"testing"
collector "arcrun-rag/collector"
)
// TestFreshConfigIsUsableByCollector 釘住 leo 2026-08-06 Windows 封測的真兇:
//
// App 存出來的**全新** config 少了 `manifest` 這個必填欄位
// ⇒ collector 一啟動就 exit status 2 ⇒ supervisor 無限重拉
// ⇒ 畫面在「看守中/沒有在跑」之間閃、加資料夾也沒反應。
//
// 為什麼一路活到封測:開發機的 config 是舊版留下的、早就有這一欄
// ——「我這台好好的」正是它能活下來的原因。所以這支測試**從零建 config**。
func TestFreshConfigIsUsableByCollector(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home) // Windows 的家目錄變數
// 模擬全新安裝:完全沒有 config,使用者連上一個知識庫後存檔
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)
}
raw, err := os.ReadFile(configPath())
if err != nil {
t.Fatalf("讀不回剛存的 config%v", 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("全新 config 沒有 manifest ⇒ collector 會 exit 2 無限重試(就是 leo 撞到的那個)")
}
// 真正的驗收不是「欄位在不在」,是**collector 吃不吃得下去**。
if _, err := collector.LoadDirectConfig(configPath()); err != nil {
t.Fatalf("collector 讀不了 App 存的 config%v", err)
}
// manifest 要落在 app 目錄下,不能指到別人家
if got, _ := m["manifest"].(string); filepath.Dir(got) != appDir() {
t.Errorf("manifest 應該放在 %s,實得 %s", appDir(), got)
}
}