sync: collector/ 同步自 inkstone/arcrun-rag@7c21a3a(桌面小幫手 0.18.42)

This commit is contained in:
2026-08-27 21:30:21 +08:00
parent 6e80296362
commit 3d1cb83110
18 changed files with 737 additions and 61 deletions
+140
View File
@@ -0,0 +1,140 @@
package collector
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
const legacyCard = `---
tags: [測試]
gloss: 測試卡
created: 2026-08-01
updated: 2026-08-01
---
# 小果被AFTEE詐貸
← [[00-INDEX]]
## 摘要
一段摘要。
## 重點
- 一條重點
## 實體
- **小果**(人物)— 當事人
## 關聯
### 內文知識關係
- 小果 >` + `> 被詐貸 >` + `> AFTEE
### 卡片關係
- [[小果被AFTEE詐貸]] >` + `> 整理出 >` + `> [[貸款爭議]]
### 出處
- ` + "`../小果被AFTEE詐貸.pdf`" + ` >` + `> 提及 >` + `> 小果被AFTEE詐貸
`
// TestRepairCardSourceBlocks 驗既有資料的處置:舊形出處就地改寫+原樣重推。
// 這是票 `inkstone/Arcrun#167` 驗收條件「不能只修新的」的機械閘。
func TestRepairCardSourceBlocks(t *testing.T) {
root := t.TempDir()
cardRel := "子資料夾/.wiki/小果被AFTEE詐貸.md"
if err := os.MkdirAll(filepath.Join(root, "子資料夾", ".wiki"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, filepath.FromSlash(cardRel)), []byte(legacyCard), 0o644); err != nil {
t.Fatal(err)
}
wm := &wikiManifest{Version: 1, Docs: []wikiDoc{{
Node: "子資料夾", Path: "小果被AFTEE詐貸.pdf", Status: "extracted",
Card: "小果被AFTEE詐貸", Cards: []string{cardRel},
}}}
if err := saveWikiManifest(root, wm); err != nil {
t.Fatal(err)
}
var got map[string]any
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(b, &got)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true}`))
}))
defer srv.Close()
cfg := &DirectConfig{
CypherURL: srv.URL, APIKey: "k", Namespace: "ns",
CardIngestWF: "rag_ingest_card",
MachineLabel: "教育部 Leo 的 Mac",
}
m := &Manifest{Root: root, Entries: map[string]*ManifestEntry{}}
res := repairCardSourceBlocks(cfg, root, m, false, false, time.Now())
if res == nil || res.Repushed != 1 {
t.Fatalf("沒有重推:%+v", res)
}
// ① 本機卡不再有內部相對路徑,且定位得到子資料夾。
fixed, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(cardRel)))
if err != nil {
t.Fatal(err)
}
card := string(fixed)
if strings.Contains(card, "../") {
t.Fatalf("本機卡仍帶 `../`\n%s", card)
}
if !strings.Contains(card, "子資料夾/小果被AFTEE詐貸.pdf") {
t.Fatalf("本機卡的出處定位不到子資料夾:\n%s", card)
}
if !strings.Contains(card, "教育部 Leo 的 Mac") {
t.Fatalf("本機卡的出處沒有機器標記:\n%s", card)
}
// ② 出處以外一字不動(重萃會讓內容變樣,就地修不會)。
if !strings.Contains(card, "## 摘要\n一段摘要。") || !strings.Contains(card, "- **小果**(人物)— 當事人") {
t.Fatalf("出處以外的內容被動到了:\n%s", card)
}
// ③ 送上雲的 payload 與 direct.go 送新卡時同一組欄位,且路徑是**庫內完整路徑**。
if got["path"] != "子資料夾/小果被AFTEE詐貸.pdf" {
t.Fatalf("重推的 path 不對:%v", got["path"])
}
if got["page_name"] != "小果被AFTEE詐貸" {
t.Fatalf("重推的 page_name 不對:%v", got["page_name"])
}
if s, _ := got["card_content"].(string); strings.Contains(s, "../") {
t.Fatalf("送上雲的卡片內容仍帶 `../`\n%s", s)
}
// ④ 修完蓋章 ⇒ 不會每輪重掃、更不會每輪重推。
if m.SourceOriginRepairedAt == 0 {
t.Fatal("修完沒蓋章,會變成每輪都跑的背景工作")
}
got = nil
if again := repairCardSourceBlocks(cfg, root, m, false, false, time.Now()); again != nil {
t.Fatalf("蓋章後又跑了一次:%+v", again)
}
if got != nil {
t.Fatal("蓋章後又重推了一次")
}
}
// TestRewriteSourceBlockLeavesNewShapeAlone:已經是新形的卡不該被再動一次
//(避免「每輪都改一點」讓使用者的檔案 mtime 無故跳動)。
func TestRewriteSourceBlockLeavesNewShapeAlone(t *testing.T) {
o := SourceOrigin{MachineLabel: "m", Library: "kb", LibraryPath: "a/b.md"}
var b strings.Builder
renderSourceLine(&b, o, "卡")
card := "## 關聯\n### 卡片關係\n" + b.String()
if hasLegacySourceLine(card) {
t.Fatalf("新形被誤判成舊形:\n%s", card)
}
if rewriteSourceBlock(card, o, "卡") != card {
t.Fatal("新形卡被重寫了")
}
}