feat(daemon-beta t14/t15/t17): supervisor log 檔+removed 清本地卡+托盤建新資料夾
- t14 supervisor:子行程 stdout(tee)/stderr 全落 ~/.arcrun-rag/collector.log, >5MB 改名 .old 重開(兩代輪替);落 log 失敗一律吞掉不擋看守。新增 2 測試 (輸出進檔+輪替觸發)。 - t15 direct removed(extractor 模式):雲端 takedown 2xx 後同步刪本地 system-dev/wiki/cards/<頁名>.md(存在才刪;刪失敗只記 warning 不擋)。 新增 2 測試(刪原檔→本地卡被清/卡已不在照常下架)。 - t17 arcrun-tray:tray 選單+設定視窗加「建立新資料夾並看守…」—— widget.Entry 輸入名稱→ ~/ 底下 mkdir(已存在沿用;擋路徑分隔與 ..)→ addWatchFolder+saveConfig+重啟看守+rebuildTray。 collector: go vet ✓、go test -count=1 ./... 43/43 綠;arcrun-tray: go build ✓(產物已刪) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,116 @@ func TestDirectExtractorModeE2E(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// t15:extractor 模式刪原檔 → 雲端 takedown 成功後,本地萃出的卡也要被清掉。
|
||||
func TestDirectExtractorRemovedClearsLocalCard(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(root, "報銷規則.md"), []byte("# 原稿"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 假 cypher:收 rag_ingest_card 與 rag_takedown_direct
|
||||
var takedowns []map[string]any
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/webhooks/named/demo/rag_takedown_direct/trigger") {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
var m map[string]any
|
||||
_ = json.Unmarshal(body, &m)
|
||||
takedowns = append(takedowns, m)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
// stub claude:萃卡落地
|
||||
stubDir := t.TempDir()
|
||||
stub := filepath.Join(stubDir, "claude")
|
||||
script := "#!/bin/sh\nmkdir -p system-dev/wiki/cards\nprintf '# 報銷規則\\n## 一句話定義\\n測試卡\\n' > 'system-dev/wiki/cards/報銷規則.md'\n"
|
||||
if err := os.WriteFile(stub, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg := &DirectConfig{
|
||||
WatchFolders: []string{root},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: srv.URL, Namespace: "demo", APIKey: "demo",
|
||||
Library: "kb", Extractor: "claude", ClaudeBin: stub,
|
||||
CardIngestWF: "rag_ingest_card", RemovedWF: "rag_takedown_direct",
|
||||
// 單檔刪除=removed ratio 100%,預設 0.4 防呆會壓下事件;本測試聚焦下架路,放寬到 1.0
|
||||
//(1 > 1.0×1 為 false → 事件放行)。
|
||||
MaxRemoved: 1.0,
|
||||
}
|
||||
|
||||
// 第一輪:萃卡+上雲,本地卡存在
|
||||
if _, exit, _ := RunDirectOnce(cfg, false); exit != 0 {
|
||||
t.Fatalf("第一輪 ingest 失敗 exit=%d", exit)
|
||||
}
|
||||
cardPath := filepath.Join(root, "system-dev", "wiki", "cards", "報銷規則.md")
|
||||
if _, err := os.Stat(cardPath); err != nil {
|
||||
t.Fatalf("前置失敗:卡片未落地 %v", err)
|
||||
}
|
||||
|
||||
// 刪原檔 → 第二輪:takedown 打出去、本地卡也被清
|
||||
if err := os.Remove(filepath.Join(root, "報銷規則.md")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
results, exit, _ := RunDirectOnce(cfg, false)
|
||||
if exit != 0 {
|
||||
t.Fatalf("第二輪 exit=%d results=%+v", exit, results)
|
||||
}
|
||||
if len(results) != 1 || results[0].Status != "removed" {
|
||||
t.Fatalf("results=%+v", results)
|
||||
}
|
||||
if len(takedowns) != 1 {
|
||||
t.Fatalf("應恰好一次 takedown,got %d", len(takedowns))
|
||||
}
|
||||
if pn, _ := takedowns[0]["page_name"].(string); pn != "報銷規則" {
|
||||
t.Fatalf("takedown page_name=%q", pn)
|
||||
}
|
||||
if _, err := os.Stat(cardPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("本地卡應已被清(err=%v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
// t15:本地卡不存在時(存在才刪)下架照常成功,不多出 warning。
|
||||
func TestDirectExtractorRemovedNoLocalCardOK(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(root, "a.md"), []byte("x"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
|
||||
}))
|
||||
defer srv.Close()
|
||||
stubDir := t.TempDir()
|
||||
stub := filepath.Join(stubDir, "claude")
|
||||
script := "#!/bin/sh\nmkdir -p system-dev/wiki/cards\nprintf '# a\\n## 一句話定義\\n卡\\n' > system-dev/wiki/cards/a.md\n"
|
||||
if err := os.WriteFile(stub, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := &DirectConfig{
|
||||
WatchFolders: []string{root},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: srv.URL, Namespace: "demo", APIKey: "demo",
|
||||
Extractor: "claude", ClaudeBin: stub,
|
||||
CardIngestWF: "rag_ingest_card", RemovedWF: "rag_takedown_direct",
|
||||
MaxRemoved: 1.0,
|
||||
}
|
||||
if _, exit, _ := RunDirectOnce(cfg, false); exit != 0 {
|
||||
t.Fatal("第一輪失敗")
|
||||
}
|
||||
// 模擬用戶已手動清走本地卡 → removed 分支「存在才刪」不應報錯或多出 warning
|
||||
if err := os.Remove(filepath.Join(root, "system-dev", "wiki", "cards", "a.md")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Remove(filepath.Join(root, "a.md")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
results, exit, _ := RunDirectOnce(cfg, false)
|
||||
if exit != 0 || len(results) != 1 || results[0].Status != "removed" {
|
||||
t.Fatalf("exit=%d results=%+v", exit, results)
|
||||
}
|
||||
}
|
||||
|
||||
// 萃取失敗=該檔標 failed、exit=1、manifest 不標(下輪重試),其他檔不受影響。
|
||||
func TestDirectExtractorFailKeepsRetry(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
Reference in New Issue
Block a user