Files
arcrun-collector/cmd/arcrun-app/default_library_e2e_test.go
T
Leo 361f1f1f9d 新增全新安裝的「可刪除預設庫」(leo 2026-08-08 拍板)
全新使用者第一次連上知識庫時,自動在 Documents 建一個
「Arcrun 範例庫(可刪除)」,裡面放三份 .md 示範內容,
不必自己選資料夾就能立刻走完「丟檔→知識卡→搜得到」。

四條紅線都有測試釘住(default_library_test.go/
default_library_e2e_test.go,含真的建 binary 跑 --dry-run 驗證):
1. 可刪——刪掉就真的消失,marker 檔擋住重種
2. 只有第一次——已有帳號的機器不會冒出來
3. 不污染——只在自己的資料夾裡寫檔
4. 不留幽靈資料——資料夾被刪後,config 殘留引用會被清掉(GetState 自我修復)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:22:03 +08:00

90 lines
3.4 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 (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// TestDefaultLibraryEndToEndDryRun 是 default_library_test.go 的補強:那邊驗證的是
// 「App 這一層的邏輯對不對」(單元層級),這支驗證的是**真的把同步引擎跑起來**,
// 證明預設庫裡的檔案真的會被 collector 認成「要處理的事件」——也就是
// 「丟檔 → 知識卡」那條路的前半段真的通,不是紙上談兵。
// 用 --dry-runcollector 掃到 added 事件後回報 status="planned" 就收手,
// 不會真的打雲端(沒有真的知識庫可以打),也不寫 manifest。
func TestDefaultLibraryEndToEndDryRun(t *testing.T) {
bin := filepath.Join(t.TempDir(), "arcrun-app-e2e")
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)
// ① 全新安裝、第一次連上知識庫 ⇒ 應該自動長出預設庫
srv := fakeDaemonConfigServer(t, "https://instance-e2e.example.workers.dev")
app := &App{}
if err := app.Connect(srv.URL, "evan@example.com", "pw"); err != nil {
t.Fatalf("Connect 失敗:%v", err)
}
libPath := defaultLibraryPath()
run := func(label string) string {
t.Helper()
cmd := exec.Command(bin, collectorModeFlag, "direct", "--once", "--dry-run", "--config", configPath())
cmd.Env = append(os.Environ(), "HOME="+home, "USERPROFILE="+home)
out, _ := cmd.CombinedOutput() // 非零結束碼也要看輸出,不提前判死
t.Logf("=== %s ===\n%s", label, out)
return string(out)
}
// ② 真的跑一輪:三份示範檔應該被辨識成 planned(=萃取管線願意收)
out := run("① 全新安裝,預設庫存在")
plannedCount := strings.Count(out, `"status": "planned"`)
if plannedCount < len(defaultLibraryFiles) {
t.Fatalf("預設庫的 %d 份示範檔應該全部被排進處理佇列(status=planned),"+
"實際只看到 %d 次,輸出:\n%s", len(defaultLibraryFiles), plannedCount, out)
}
if strings.Contains(out, "缺必填欄位") {
t.Fatalf("設定缺必填欄位:%s", out)
}
// ③ 使用者把預設庫整個刪掉
if err := os.RemoveAll(libPath); err != nil {
t.Fatalf("刪除失敗:%v", err)
}
if _, err := os.Stat(libPath); !os.IsNotExist(err) {
t.Fatalf("刪除沒生效:%v", err)
}
// App 下一次讀狀態時的自我修復(等同 GetState 裡的那段):清掉殘留引用
cfg, err := loadCfg()
if err != nil {
t.Fatalf("讀不回設定:%v", err)
}
if !pruneMissingDefaultLibrary(cfg) {
t.Fatal("應該偵測到預設庫已被刪除")
}
if err := saveCfg(cfg); err != nil {
t.Fatalf("存檔失敗:%v", err)
}
// ④ 再跑一輪:不該再處理那三份檔(資料夾不在了),也不該把資料夾生回來
out2 := run("② 刪除後再跑一輪")
if strings.Contains(out2, `"status": "planned"`) {
t.Fatalf("刪除後不該還有 planned 事件,輸出:\n%s", out2)
}
if _, err := os.Stat(libPath); !os.IsNotExist(err) {
t.Fatalf("預設庫刪掉後又被同步引擎生回來了:%v", err)
}
// ⑤ marker 檔還在 ⇒ 就算再跑一次種子邏輯,也不會再出現(紅線②③④一次證完)
seedDefaultLibraryIfFirstEver(cfg, 0)
if _, err := os.Stat(libPath); !os.IsNotExist(err) {
t.Fatalf("種子邏輯把已刪除的預設庫又生回來了:%v", err)
}
}