Skip to content

Commit e820041

Browse files
committed
feat: introduce configurable hybrid search alpha and reranker top-k multiplier with telemetry and quality evaluation tools
1 parent 4bb18e2 commit e820041

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""Unit tests for SQLite-backed knowledge store."""
2+
3+
import json
4+
import sqlite3
5+
import threading
6+
from pathlib import Path
7+
8+
import pytest
9+
10+
from knowcode.data_models import Entity, EntityKind, Location, Relationship, RelationshipKind
11+
from knowcode.storage.knowledge_store import KnowledgeStore
12+
from knowcode.storage.sqlite_knowledge_store import SqliteKnowledgeStore
13+
14+
15+
def _make_entity(entity_id: str, kind: EntityKind, name: str) -> Entity:
16+
return Entity(
17+
id=entity_id,
18+
kind=kind,
19+
name=name,
20+
qualified_name=name,
21+
location=Location("file.py", 1, 1),
22+
metadata={"content_hash": "dummy_hash"}
23+
)
24+
25+
26+
def test_entity_round_trip(tmp_path: Path) -> None:
27+
"""Test inserting and retrieving entities."""
28+
db_path = tmp_path / "knowledge.db"
29+
store = SqliteKnowledgeStore(db_path)
30+
31+
foo = _make_entity("file.py::foo", EntityKind.FUNCTION, "foo")
32+
store.add_entity(foo)
33+
34+
# Retrieve by ID
35+
retrieved = store.get_entity("file.py::foo")
36+
assert retrieved is not None
37+
assert retrieved.id == foo.id
38+
assert retrieved.kind == foo.kind
39+
assert retrieved.name == foo.name
40+
41+
# Search by pattern
42+
results = store.search("fo")
43+
assert len(results) == 1
44+
assert results[0].id == foo.id
45+
46+
47+
def test_relationship_queries(tmp_path: Path) -> None:
48+
"""Test querying relationships."""
49+
db_path = tmp_path / "knowledge.db"
50+
store = SqliteKnowledgeStore(db_path)
51+
52+
foo = _make_entity("file.py::foo", EntityKind.FUNCTION, "foo")
53+
bar = _make_entity("file.py::bar", EntityKind.FUNCTION, "bar")
54+
store.add_entity(foo)
55+
store.add_entity(bar)
56+
57+
rel = Relationship(source_id=foo.id, target_id=bar.id, kind=RelationshipKind.CALLS)
58+
store.add_relationship(rel)
59+
60+
# test get_callers / get_callees
61+
assert store.get_callers(bar.id) == [foo]
62+
assert store.get_callees(foo.id) == [bar]
63+
64+
65+
def test_recursive_trace_calls(tmp_path: Path) -> None:
66+
"""Test multi-hop traversal using CTE."""
67+
db_path = tmp_path / "knowledge.db"
68+
store = SqliteKnowledgeStore(db_path)
69+
70+
a = _make_entity("file.py::a", EntityKind.FUNCTION, "a")
71+
b = _make_entity("file.py::b", EntityKind.FUNCTION, "b")
72+
c = _make_entity("file.py::c", EntityKind.FUNCTION, "c")
73+
store.add_entity(a)
74+
store.add_entity(b)
75+
store.add_entity(c)
76+
77+
store.add_relationship(Relationship(source_id=a.id, target_id=b.id, kind=RelationshipKind.CALLS))
78+
store.add_relationship(Relationship(source_id=b.id, target_id=c.id, kind=RelationshipKind.CALLS))
79+
80+
# Trace callees depth 1
81+
callees_1 = store.trace_calls(a.id, direction="callees", depth=1)
82+
assert len(callees_1) == 1
83+
assert callees_1[0]["entity_id"] == b.id
84+
assert callees_1[0]["call_depth"] == 1
85+
86+
# Trace callees depth 2
87+
callees_2 = store.trace_calls(a.id, direction="callees", depth=2)
88+
assert len(callees_2) == 2
89+
ids = {c["entity_id"] for c in callees_2}
90+
assert ids == {b.id, c.id}
91+
92+
93+
def test_get_impact(tmp_path: Path) -> None:
94+
"""Test get_impact calculation."""
95+
db_path = tmp_path / "knowledge.db"
96+
store = SqliteKnowledgeStore(db_path)
97+
98+
core = _make_entity("file.py::core", EntityKind.CLASS, "core")
99+
dep1 = _make_entity("file.py::dep1", EntityKind.FUNCTION, "dep1")
100+
dep2 = _make_entity("file.py::dep2", EntityKind.FUNCTION, "dep2")
101+
102+
store.add_entity(core)
103+
store.add_entity(dep1)
104+
store.add_entity(dep2)
105+
106+
store.add_relationship(Relationship(source_id=dep1.id, target_id=core.id, kind=RelationshipKind.CALLS))
107+
store.add_relationship(Relationship(source_id=dep2.id, target_id=dep1.id, kind=RelationshipKind.CALLS))
108+
109+
impact = store.get_impact(core.id, max_depth=2)
110+
assert impact["entity_id"] == core.id
111+
assert len(impact["direct_dependents"]) == 1
112+
assert impact["direct_dependents"][0]["entity_id"] == dep1.id
113+
assert len(impact["transitive_dependents"]) == 1
114+
assert impact["transitive_dependents"][0]["entity_id"] == dep2.id
115+
116+
117+
def test_json_migration(tmp_path: Path) -> None:
118+
"""Test importing from JSON knowledge store."""
119+
json_path = tmp_path / "knowledge.json"
120+
legacy_store = KnowledgeStore()
121+
foo = _make_entity("file.py::foo", EntityKind.FUNCTION, "foo")
122+
legacy_store.entities = {foo.id: foo}
123+
legacy_store.save(json_path)
124+
125+
db_path = tmp_path / "knowledge.db"
126+
store = SqliteKnowledgeStore.from_json(json_path, db_path)
127+
128+
retrieved = store.get_entity(foo.id)
129+
assert retrieved is not None
130+
assert retrieved.name == foo.name
131+
132+
133+
def test_concurrent_reads(tmp_path: Path) -> None:
134+
"""Test WAL mode concurrent reads."""
135+
db_path = tmp_path / "knowledge.db"
136+
store = SqliteKnowledgeStore(db_path)
137+
138+
foo = _make_entity("file.py::foo", EntityKind.FUNCTION, "foo")
139+
store.add_entity(foo)
140+
141+
def read_task() -> None:
142+
reader_store = SqliteKnowledgeStore(db_path)
143+
assert reader_store.get_entity(foo.id) is not None
144+
145+
threads = [threading.Thread(target=read_task) for _ in range(5)]
146+
for t in threads:
147+
t.start()
148+
for t in threads:
149+
t.join()

0 commit comments

Comments
 (0)