test: 补 KeywordSearch FTS 通道/双通道去重 + GetUnresolvedLinks 覆盖(index 包 42.8%→50.9%)
| | |
| | | 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) |
| | | } |
| | | } |
| | |
| | | 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) |
| | | } |
| | | } |