From 5a2be87f658d01bc5b3b7ecdba92ac69bf09dcf7 Mon Sep 17 00:00:00 2001
From: ai_xiaopei <xiaopei@aisim.cn>
Date: Thu, 03 Sep 2026 15:15:37 +0800
Subject: [PATCH] fix(search): 搜索准确性四连修——FTS5 MATCH 引号包裹(连字符不再被解析成 MINUS);KeywordSearch 截断按命中关键词数排序(高频 bigram 不再挤出强相关文档);原词奖励档(完整短语压过泛化 bigram);tags 字段真实参与评分(此前误用 Section)
---
internal/index/reconcile_test.go | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 175 insertions(+), 0 deletions(-)
diff --git a/internal/index/reconcile_test.go b/internal/index/reconcile_test.go
new file mode 100644
index 0000000..677015f
--- /dev/null
+++ b/internal/index/reconcile_test.go
@@ -0,0 +1,175 @@
+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")
+ }
+}
--
Gitblit v1.10.0