package index import ( "os" "path/filepath" "testing" "time" ) func TestReconcileAdd(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "FAQ"), 0755); err != nil { t.Fatal(err) } content := "---\ntitle: 测试文档\ntags: [t1]\nstatus: 已解决\n---\n正文\n" if err := os.WriteFile(filepath.Join(dir, "FAQ", "001-测试文档.md"), []byte(content), 0644); err != nil { t.Fatal(err) } store, err := Open(filepath.Join(t.TempDir(), "kb.db")) if err != nil { t.Fatal(err) } defer store.Close() res, err := Reconcile(store, dir) if err != nil { t.Fatal(err) } if res.Added != 1 || res.Unchanged != 0 { t.Errorf("新增场景: %+v", res) } var title, status string if err := store.db.QueryRow(`SELECT title, status FROM nodes WHERE path='FAQ/001-测试文档.md'`).Scan(&title, &status); err != nil { t.Fatal(err) } if title != "测试文档" || status != "已解决" { t.Errorf("节点字段错误: %s / %s", title, status) } } func TestReconcileModify(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "FAQ"), 0755); err != nil { t.Fatal(err) } p := filepath.Join(dir, "FAQ", "001-测试文档.md") if err := os.WriteFile(p, []byte("---\ntitle: 旧标题\n---\n旧内容\n"), 0644); err != nil { t.Fatal(err) } store, err := Open(filepath.Join(t.TempDir(), "kb.db")) if err != nil { t.Fatal(err) } defer store.Close() if _, err := Reconcile(store, dir); err != nil { t.Fatal(err) } // 修改内容(title 变新标题,内容加"补气") time.Sleep(1100 * time.Millisecond) // mtime 秒级精度,确保 mtime 变化 if err := os.WriteFile(p, []byte("---\ntitle: 新标题\n---\n新内容补气\n"), 0644); err != nil { t.Fatal(err) } res, err := Reconcile(store, dir) if err != nil { t.Fatal(err) } if res.Modified != 1 || res.Added != 0 { t.Errorf("修改场景: %+v", res) } var title string if err := store.db.QueryRow(`SELECT title FROM nodes WHERE path='FAQ/001-测试文档.md'`).Scan(&title); err != nil { t.Fatal(err) } if title != "新标题" { t.Errorf("标题未更新: %s", title) } } func TestReconcileDelete(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "FAQ"), 0755); err != nil { t.Fatal(err) } p := filepath.Join(dir, "FAQ", "001-测试文档.md") if err := os.WriteFile(p, []byte("---\ntitle: 测试文档\n---\n内容\n"), 0644); err != nil { t.Fatal(err) } store, err := Open(filepath.Join(t.TempDir(), "kb.db")) if err != nil { t.Fatal(err) } defer store.Close() if _, err := Reconcile(store, dir); err != nil { t.Fatal(err) } if err := os.Remove(p); err != nil { t.Fatal(err) } res, err := Reconcile(store, dir) if err != nil { t.Fatal(err) } if res.Deleted != 1 { t.Errorf("删除场景: %+v", res) } var n int if err := store.db.QueryRow(`SELECT COUNT(*) FROM nodes`).Scan(&n); err != nil { t.Fatal(err) } if n != 0 { t.Errorf("节点未删净: %d", n) } } func TestReconcileUnchanged(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "FAQ"), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "FAQ", "001-测试文档.md"), []byte("---\ntitle: 测试文档\n---\n内容\n"), 0644); err != nil { t.Fatal(err) } store, err := Open(filepath.Join(t.TempDir(), "kb.db")) if err != nil { t.Fatal(err) } defer store.Close() if _, err := Reconcile(store, dir); err != nil { t.Fatal(err) } res, err := Reconcile(store, dir) if err != nil { t.Fatal(err) } if res.Unchanged != 1 || res.Added != 0 || res.Modified != 0 { t.Errorf("未变场景: %+v", res) } } func TestQuickCheck(t *testing.T) { dir := t.TempDir() if err := os.MkdirAll(filepath.Join(dir, "FAQ"), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "FAQ", "001-测试文档.md"), []byte("---\ntitle: 测试文档\n---\n内容\n"), 0644); err != nil { t.Fatal(err) } store, err := Open(filepath.Join(t.TempDir(), "kb.db")) if err != nil { t.Fatal(err) } defer store.Close() if _, err := Reconcile(store, dir); err != nil { t.Fatal(err) } dirty, err := QuickCheck(store, dir) if err != nil { t.Fatal(err) } if dirty { t.Error("无差异时 QuickCheck 应 false") } }