Files
code-utils/sync_docs_test.go
2026-08-14 07:52:01 +08:00

142 lines
4.9 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 (
"path/filepath"
"strings"
"testing"
)
// 项目/规则文档的生成与套用(不依赖 MySQL 的本地部分)。
// v2 语义:身份文档只按 name 匹配更新,不再从文档落库项目;
// 项目落地依赖 paths:<machineID> 行applyMachinePaths或用户手动绑定。
func TestProjectsDocRoundTrip(t *testing.T) {
dir := t.TempDir()
s, e := OpenStore(filepath.Join(dir, "docs.db"))
if e != nil {
t.Fatal(e)
}
defer s.db.Close()
a := NewApp()
a.store = s
doc, e := a.projectsDoc()
if e != nil || doc != "[]" {
t.Fatalf("empty doc want [], got %q err=%v", doc, e)
}
// 远端身份文档v1 旧格式,元素带 path本机没有这些项目 → 全部进待绑定,不落库。
p1 := filepath.Join(dir, "alpha")
p2 := filepath.Join(dir, "beta")
remote := `[{"name":"Alpha","path":` + jsonStr(p1) + `,"description":"d1","group":"Work","favorite":true},` +
`{"name":"Beta","path":` + jsonStr(p2) + `}]`
if e := a.applyProjectsDoc(remote); e != nil {
t.Fatal(e)
}
var n int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM projects`).Scan(&n)
if n != 0 {
t.Fatalf("identity doc must not insert projects, got %d", n)
}
if pending, _ := a.ListCloudPendingProjects(); len(pending) != 2 {
t.Fatalf("expect 2 pending cloud projects, got %d", len(pending))
}
// 本机 paths 行到达(同机换库场景)→ 项目落库,随后身份文档补齐分组/收藏。
if e := a.applyMachinePaths(`{"Alpha":` + jsonStr(p1) + `,"Beta":` + jsonStr(p2) + `}`); e != nil {
t.Fatal(e)
}
if e := a.applyProjectsDoc(remote); e != nil {
t.Fatal(e)
}
var gid int64
if e := s.db.QueryRow(`SELECT group_id FROM projects WHERE path=?`, p1).Scan(&gid); e != nil || gid == 1 {
t.Fatalf("alpha should be in new group, gid=%d err=%v", gid, e)
}
var gname string
_ = s.db.QueryRow(`SELECT name FROM project_groups WHERE id=?`, gid).Scan(&gname)
if gname != "Work" {
t.Fatalf("group name want Work, got %q", gname)
}
var fav int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM favorites f JOIN projects p ON p.id=f.project_id WHERE p.path=?`, p1).Scan(&fav)
if fav != 1 {
t.Fatal("alpha should be favorite")
}
if pending, _ := a.ListCloudPendingProjects(); len(pending) != 0 {
t.Fatalf("pending should be empty after paths applied, got %d", len(pending))
}
// 再生成文档应包含两个项目且不带 path重复套用幂等。
doc, e = a.projectsDoc()
if e != nil || !strings.Contains(doc, "Alpha") || !strings.Contains(doc, "Beta") {
t.Fatalf("doc missing entries: %q err=%v", doc, e)
}
if strings.Contains(doc, `"path"`) {
t.Fatalf("v2 doc must not contain path: %q", doc)
}
if e := a.applyProjectsDoc(remote); e != nil {
t.Fatal(e)
}
_ = s.db.QueryRow(`SELECT COUNT(*) FROM projects`).Scan(&n)
if n != 2 {
t.Fatalf("apply should be idempotent, projects=%d", n)
}
// 更新改名v1 文档按 path 匹配到本地项目)+ 取消收藏;本地多出的项目不受影响(只增改不删)。
remote2 := `[{"name":"Alpha2","path":` + jsonStr(p1) + `,"description":"d2","group":"Work","favorite":false}]`
if e := a.applyProjectsDoc(remote2); e != nil {
t.Fatal(e)
}
var name string
_ = s.db.QueryRow(`SELECT name FROM projects WHERE path=?`, p1).Scan(&name)
if name != "Alpha2" {
t.Fatalf("want renamed Alpha2, got %q", name)
}
_ = s.db.QueryRow(`SELECT COUNT(*) FROM favorites f JOIN projects p ON p.id=f.project_id WHERE p.path=?`, p1).Scan(&fav)
if fav != 0 {
t.Fatal("favorite should be removed")
}
_ = s.db.QueryRow(`SELECT COUNT(*) FROM projects`).Scan(&n)
if n != 2 {
t.Fatalf("pull must not delete local projects, got %d", n)
}
}
func TestRulesDocRoundTrip(t *testing.T) {
s, e := OpenStore(filepath.Join(t.TempDir(), "rules.db"))
if e != nil {
t.Fatal(e)
}
defer s.db.Close()
a := NewApp()
a.store = s
if _, e := s.AddRule("*.bak", "custom"); e != nil {
t.Fatal(e)
}
doc, e := a.rulesDoc()
if e != nil || !strings.Contains(doc, "*.bak") {
t.Fatalf("doc should contain custom rule: %q err=%v", doc, e)
}
var builtinBefore int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM exclusion_rules WHERE builtin=1`).Scan(&builtinBefore)
// 远端替换自定义部分:*.bak 消失,*.tmp 出现,内置规则数不变。
if e := a.applyRulesDoc(`[{"pattern":"*.tmp","category":"custom"}]`); e != nil {
t.Fatal(e)
}
var nBak, nTmp, builtinAfter int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM exclusion_rules WHERE pattern='*.bak'`).Scan(&nBak)
_ = s.db.QueryRow(`SELECT COUNT(*) FROM exclusion_rules WHERE pattern='*.tmp' AND builtin=0`).Scan(&nTmp)
_ = s.db.QueryRow(`SELECT COUNT(*) FROM exclusion_rules WHERE builtin=1`).Scan(&builtinAfter)
if nBak != 0 || nTmp != 1 || builtinAfter != builtinBefore {
t.Fatalf("replace failed: bak=%d tmp=%d builtin %d->%d", nBak, nTmp, builtinBefore, builtinAfter)
}
}
// jsonStr 把路径安全编码为 JSON 字符串Windows 反斜杠)。
func jsonStr(s string) string {
b := strings.ReplaceAll(s, `\`, `\\`)
return `"` + b + `"`
}