Files
arcrun-collector/tidy_test.go
T
Leo 5a140faf1f fix(collector): 機器寫進筆記庫的檔案一律帶前綴,不再跟使用者的頁面撞名(arcrun-rag#60)
leo 2026-08-12:「我的 Logseq 又被覆蓋⋯⋯**不只是加上 journal,可能所有的檔案都加一個前後綴,比如「wiki」**。」

不是資料被蓋掉,是機器產出用了跟他一樣的命名空間(status.md、日期檔那些)
⇒ 他打開資料夾分不出哪些是自己的。**心理上的覆蓋跟實際覆蓋一樣糟。**
2026-08-12 14:57:32 +08:00

251 lines
8.1 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.
// tidy_test.go — arcrun-rag#60 驗收條件③:已經寫進去的舊產物要有辦法一次認出來/改名。
package collector
import (
"os"
"path/filepath"
"sort"
"strings"
"testing"
)
func TestMarkName_Idempotent(t *testing.T) {
if got := MarkName("status.md"); got != "arcrun-status.md" {
t.Fatalf("MarkName=%q", got)
}
// 冪等:daemon 每輪自動跑、tidy 也可能重跑,不准疊成 arcrun-arcrun-。
twice := MarkName(MarkName("status.md"))
if twice != "arcrun-status.md" {
t.Fatalf("重複加標記疊起來了:%q", twice)
}
if !IsMarked("arcrun-x.md") || IsMarked("x.md") {
t.Fatal("IsMarked 判斷錯")
}
if got := UnmarkName("arcrun-x.md"); got != "x.md" {
t.Fatalf("UnmarkName=%q", got)
}
}
// 舊產物:卡片與備份檔都要被認出來並改名;使用者自己的檔一個都不准動。
func TestTidy_RenamesLegacyCardsOnly(t *testing.T) {
root := t.TempDir()
mustMkdir(t, filepath.Join(root, "logseq"))
// 使用者自己的東西(絕對不准動)
mine := map[string]string{
"journals/2026_08_10.md": "我的日記",
"pages/讀書筆記.md": "我的筆記",
}
for rel, body := range mine {
p := filepath.Join(root, filepath.FromSlash(rel))
mustMkdir(t, filepath.Dir(p))
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
// 舊版 daemon 落下的產物(兩個卡片目錄各放一份,加一個備份檔)
legacy := []string{
".arcrun-rag/wiki/cards/2026_08_10.md",
".arcrun-rag/wiki/cards/2026_08_10.md.bak-1723459200000000000",
"system-dev/wiki/cards/會議記錄.md",
}
for _, rel := range legacy {
p := filepath.Join(root, filepath.FromSlash(rel))
mustMkdir(t, filepath.Dir(p))
if err := os.WriteFile(p, []byte("機器寫的"), 0o644); err != nil {
t.Fatal(err)
}
}
// ① dry-run:什麼都不該動
rep, err := Tidy(root, false)
if err != nil {
t.Fatal(err)
}
for _, rel := range legacy {
if _, serr := os.Stat(filepath.Join(root, filepath.FromSlash(rel))); serr != nil {
t.Fatalf("dry-run 竟然動了檔案:%s 不見了", rel)
}
}
var previewed []string
for _, it := range rep.Items {
previewed = append(previewed, it.Rel+" → "+it.To+""+it.Action+"")
if it.Action != TidyActionWillRename {
t.Fatalf("dry-run 出現非預期動作:%+v", it)
}
}
sort.Strings(previewed)
t.Logf("dry-run 預覽:\n %s", strings.Join(previewed, "\n "))
if len(rep.Items) != len(legacy) {
t.Fatalf("認出來的舊產物數量不對:got %d want %d%+v", len(rep.Items), len(legacy), rep.Items)
}
// ② --apply:真的改名
if _, err := Tidy(root, true); err != nil {
t.Fatal(err)
}
want := []string{
".arcrun-rag/wiki/cards/arcrun-2026_08_10.md",
".arcrun-rag/wiki/cards/arcrun-2026_08_10.md.bak-1723459200000000000",
"system-dev/wiki/cards/arcrun-會議記錄.md",
}
for _, rel := range want {
if _, serr := os.Stat(filepath.Join(root, filepath.FromSlash(rel))); serr != nil {
t.Fatalf("改名後的檔案不存在:%s", rel)
}
}
for _, rel := range legacy {
if _, serr := os.Stat(filepath.Join(root, filepath.FromSlash(rel))); serr == nil {
t.Fatalf("舊名字還在——應該是改名不是複製:%s", rel)
}
}
// 使用者的檔案原封不動
for rel, body := range mine {
data, rerr := os.ReadFile(filepath.Join(root, filepath.FromSlash(rel)))
if rerr != nil {
t.Fatalf("使用者的檔案不見了:%s%v", rel, rerr)
}
if string(data) != body {
t.Fatalf("使用者的檔案被改了:%s → %q", rel, data)
}
}
// ③ 冪等:再跑一次沒有東西可做
rep3, err := Tidy(root, true)
if err != nil {
t.Fatal(err)
}
if len(rep3.Items) != 0 {
t.Fatalf("第二次跑不該再有動作:%+v", rep3.Items)
}
}
// vault 裡的 template 殘留(status.mdmistakes.md 那批)=搬走,不刪。
func TestTidy_VaultTemplateLeftoversAreMovedNotDeleted(t *testing.T) {
root := t.TempDir()
mustMkdir(t, filepath.Join(root, "logseq"))
leftovers := map[string]string{
"system-dev/wiki/status.md": "template 的 status",
"system-dev/wiki/mistakes.md": "template 的 mistakes",
"scripts/sdd-active-check.sh": "#!/bin/sh",
}
for rel, body := range leftovers {
p := filepath.Join(root, filepath.FromSlash(rel))
mustMkdir(t, filepath.Dir(p))
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
rep, err := Tidy(root, true)
if err != nil {
t.Fatal(err)
}
moved := 0
for _, it := range rep.Items {
if it.Kind == TidyKindTemplate {
if it.Action != TidyActionMoved {
t.Fatalf("vault 裡的 template 殘留應該被搬走:%+v", it)
}
moved++
}
}
if moved != len(leftovers) {
t.Fatalf("搬走的數量不對:got %d want %d", moved, len(leftovers))
}
for rel, body := range leftovers {
// 原位不該還在
if _, serr := os.Stat(filepath.Join(root, filepath.FromSlash(rel))); serr == nil {
t.Fatalf("原位的殘留還在:%s", rel)
}
// 但**內容必須還找得到**——本指令從不刪東西
p := filepath.Join(root, filepath.FromSlash(legacyTemplateRelDir), filepath.FromSlash(rel))
data, rerr := os.ReadFile(p)
if rerr != nil {
t.Fatalf("搬過去的檔案讀不到(東西被弄丟了?):%s(%v)", p, rerr)
}
if string(data) != body {
t.Fatalf("搬移過程中內容變了:%s", rel)
}
}
}
// 非 vault(很可能是使用者自己的開發 repo)=template 只列出來,一個都不准動。
func TestTidy_PlainFolderTemplateOnlyReported(t *testing.T) {
root := t.TempDir()
rel := "system-dev/wiki/status.md"
p := filepath.Join(root, filepath.FromSlash(rel))
mustMkdir(t, filepath.Dir(p))
if err := os.WriteFile(p, []byte("這是我 repo 裡真正的 status"), 0o644); err != nil {
t.Fatal(err)
}
rep, err := Tidy(root, true) // 即使 --apply 也不准動
if err != nil {
t.Fatal(err)
}
if len(rep.Items) != 1 || rep.Items[0].Action != TidyActionReport {
t.Fatalf("非 vault 的 template 應該只列出不動手:%+v", rep.Items)
}
data, rerr := os.ReadFile(p)
if rerr != nil || string(data) != "這是我 repo 裡真正的 status" {
t.Fatalf("非 vault 的 template 被動了:%v / %q", rerr, data)
}
}
// 目的地已經有同名檔=跳過並回報,絕不覆蓋(safewrite.go 同一條紅線)。
func TestTidy_DoesNotClobberExistingTarget(t *testing.T) {
root := t.TempDir()
cardDir := filepath.Join(root, filepath.FromSlash(cardsRelDir))
mustMkdir(t, cardDir)
if err := os.WriteFile(filepath.Join(cardDir, "x.md"), []byte("舊的"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(cardDir, "arcrun-x.md"), []byte("新的"), 0o644); err != nil {
t.Fatal(err)
}
rep, err := Tidy(root, true)
if err != nil {
t.Fatal(err)
}
if len(rep.Items) != 1 || rep.Items[0].Action != TidyActionSkipped {
t.Fatalf("目的地已存在時應跳過:%+v", rep.Items)
}
if data, _ := os.ReadFile(filepath.Join(cardDir, "arcrun-x.md")); string(data) != "新的" {
t.Fatalf("既有目標被覆蓋了:%q", data)
}
if data, _ := os.ReadFile(filepath.Join(cardDir, "x.md")); string(data) != "舊的" {
t.Fatalf("來源被動了:%q", data)
}
}
// MigrateCardNamesdaemon 每輪自動跑的那個)只碰卡片產物區。
func TestMigrateCardNames_TouchesOnlyCardDirs(t *testing.T) {
root := t.TempDir()
mustMkdir(t, filepath.Join(root, "logseq"))
// 卡片區的舊卡(該改名)
cardDir := filepath.Join(root, filepath.FromSlash(vaultCardsRelDir))
mustMkdir(t, cardDir)
if err := os.WriteFile(filepath.Join(cardDir, "舊卡.md"), []byte("c"), 0o644); err != nil {
t.Fatal(err)
}
// template 殘留(自動路不准碰——那要人確認是不是他自己的 repo)
tplDir := filepath.Join(root, "system-dev", "wiki")
mustMkdir(t, tplDir)
if err := os.WriteFile(filepath.Join(tplDir, "status.md"), []byte("s"), 0o644); err != nil {
t.Fatal(err)
}
if n := MigrateCardNames(root); n != 1 {
t.Fatalf("改名筆數=%dwant 1", n)
}
if _, err := os.Stat(filepath.Join(cardDir, "arcrun-舊卡.md")); err != nil {
t.Fatalf("舊卡沒被改名:%v", err)
}
if _, err := os.Stat(filepath.Join(tplDir, "status.md")); err != nil {
t.Fatalf("自動路不該碰 template 殘留,但它不見了:%v", err)
}
}