From 29407d7a753fb22b00b18b08f693b98a97c69918 Mon Sep 17 00:00:00 2001
From: ax_rd <ax_rd@aisim.cn>
Date: Thu, 03 Sep 2026 14:03:02 +0800
Subject: [PATCH] test: 补 KeywordSearch FTS 通道/双通道去重 + GetUnresolvedLinks 覆盖(index 包 42.8%→50.9%)

---
 internal/index/fts_test.go        |   78 +++++++++++++++++++++++++++++++++++++++
 internal/index/unresolved_test.go |   32 ++++++++++++++++
 2 files changed, 110 insertions(+), 0 deletions(-)

diff --git a/internal/index/fts_test.go b/internal/index/fts_test.go
index 1a42430..050f3e0 100644
--- a/internal/index/fts_test.go
+++ b/internal/index/fts_test.go
@@ -52,3 +52,81 @@
 		t.Errorf("result count = %d, want >= 1", len(results))
 	}
 }
+
+// TestKeywordSearchASCII 覆盖 KeywordSearch 的 FTS 通道(ASCII 词 MATCH + rank 排序)
+func TestKeywordSearchASCII(t *testing.T) {
+	store, err := Open(filepath.Join(t.TempDir(), "test.db"))
+	if err != nil {
+		t.Fatalf("Open failed: %v", err)
+	}
+	defer store.Close()
+
+	// 两个节点都含 "pump",但 node1 标题也含(title 权重高 → rank 更靠前)
+	store.InsertNode(&graph.Node{
+		ID:      1,
+		Path:    "知识/001-pump.md",
+		Title:   "pump failure",
+		Section: "知识",
+		Content: "pump failure troubleshooting guide",
+	})
+	store.InsertNode(&graph.Node{
+		ID:      2,
+		Path:    "知识/002-valve.md",
+		Title:   "valve config",
+		Section: "知识",
+		Content: "the pump is configured here",
+	})
+	if err := store.CreateFTS(); err != nil {
+		t.Fatalf("CreateFTS failed: %v", err)
+	}
+	if err := store.PopulateFTS(); err != nil {
+		t.Fatalf("PopulateFTS failed: %v", err)
+	}
+
+	results, err := store.KeywordSearch([]string{"pump"}, 10)
+	if err != nil {
+		t.Fatalf("KeywordSearch failed: %v", err)
+	}
+	if len(results) != 2 {
+		t.Fatalf("result count = %d, want 2 (FTS 通道应命中两节点)", len(results))
+	}
+	// FTS 通道 rank 排序:标题含 pump 的 node1 应排第一
+	if results[0].ID != 1 {
+		t.Errorf("FTS rank 排序: 首位 = %d, want 1", results[0].ID)
+	}
+}
+
+// TestKeywordSearchMixed 覆盖双通道混合(ASCII 走 FTS + CJK 走 LIKE,seen 去重)
+func TestKeywordSearchMixed(t *testing.T) {
+	store, err := Open(filepath.Join(t.TempDir(), "test.db"))
+	if err != nil {
+		t.Fatalf("Open failed: %v", err)
+	}
+	defer store.Close()
+
+	store.InsertNode(&graph.Node{
+		ID:      1,
+		Path:    "FAQ/001-pump.md",
+		Title:   "pump 补气",
+		Section: "FAQ",
+		Content: "pump 补气失败排查",
+	})
+	if err := store.CreateFTS(); err != nil {
+		t.Fatalf("CreateFTS failed: %v", err)
+	}
+	if err := store.PopulateFTS(); err != nil {
+		t.Fatalf("PopulateFTS failed: %v", err)
+	}
+
+	// 同一节点被两个通道命中 → 去重后只出现一次
+	results, err := store.KeywordSearch([]string{"pump", "补气"}, 10)
+	if err != nil {
+		t.Fatalf("KeywordSearch failed: %v", err)
+	}
+	if len(results) != 1 {
+		t.Fatalf("双通道去重: count = %d, want 1", len(results))
+	}
+	if results[0].ID != 1 {
+		t.Errorf("命中节点 = %d, want 1", results[0].ID)
+	}
+}
diff --git a/internal/index/unresolved_test.go b/internal/index/unresolved_test.go
index c598256..4d15230 100644
--- a/internal/index/unresolved_test.go
+++ b/internal/index/unresolved_test.go
@@ -96,3 +96,35 @@
 		t.Errorf("entity 边 provenance: %q", prov)
 	}
 }
+
+// TestGetUnresolvedLinks 覆盖 GetUnresolvedLinks 查询(explore 命令悬空链接提示用)
+func TestGetUnresolvedLinks(t *testing.T) {
+	dir := t.TempDir()
+	os.MkdirAll(filepath.Join(dir, "FAQ"), 0755)
+	// 文档 A 有两条悬空链接;文档 B 无
+	os.WriteFile(filepath.Join(dir, "FAQ", "001-文档A.md"),
+		[]byte("---\ntitle: 文档A\n---\n提到 [[悬空甲]] 和 [[悬空乙]]\n"), 0644)
+	os.WriteFile(filepath.Join(dir, "FAQ", "002-文档B.md"),
+		[]byte("---\ntitle: 文档B\n---\n无链接\n"), 0644)
+
+	store, _ := Open(filepath.Join(t.TempDir(), "kb.db"))
+	defer store.Close()
+	Reconcile(store, dir)
+
+	links, err := store.GetUnresolvedLinks("FAQ/001-文档A.md")
+	if err != nil {
+		t.Fatalf("GetUnresolvedLinks failed: %v", err)
+	}
+	if len(links) != 2 {
+		t.Fatalf("文档A 悬空链接 = %d, want 2: %v", len(links), links)
+	}
+
+	// 无悬空链接的文档返回空(非 nil 错误)
+	links, err = store.GetUnresolvedLinks("FAQ/002-文档B.md")
+	if err != nil {
+		t.Fatalf("GetUnresolvedLinks(文档B) failed: %v", err)
+	}
+	if len(links) != 0 {
+		t.Errorf("文档B 悬空链接 = %d, want 0: %v", len(links), links)
+	}
+}

--
Gitblit v1.10.0