Skip to content

Commit 95e548d

Browse files
committed
wip:RAG pipeline:general structure #6 - test(pipeline): cover chunker dispatch and the digest stamped on every chunk of a file
1 parent 4799a5d commit 95e548d

2 files changed

Lines changed: 226 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from ingestion import indexed_file_state
2+
3+
4+
# --- compute_file_sha1: stable whole-file digest --------------------------
5+
6+
def test_compute_file_sha1_is_the_hex_sha1_of_the_utf8_bytes():
7+
# Pinned digest: it is stored in Qdrant and compared against the next
8+
# run, so changing the algorithm or the encoding would mark every
9+
# indexed file as changed and re-embed the whole corpus. The accented
10+
# character makes the utf-8 choice observable.
11+
assert (indexed_file_state.compute_file_sha1("héllo")
12+
== "35b5ea45c5e41f78b46a937cc74d41dfea920890")
13+
14+
15+
def test_compute_file_sha1_is_deterministic():
16+
assert (indexed_file_state.compute_file_sha1("const a = 1")
17+
== indexed_file_state.compute_file_sha1("const a = 1"))
18+
19+
20+
def test_compute_file_sha1_changes_with_content():
21+
assert (indexed_file_state.compute_file_sha1("const a = 1")
22+
!= indexed_file_state.compute_file_sha1("const a = 2"))
23+
24+
25+
# --- load_indexed_file_hashes: rebuild the state from stored payloads -----
26+
27+
def test_load_indexed_file_hashes_keys_on_repository_and_source_path(
28+
monkeypatch):
29+
monkeypatch.setattr(indexed_file_state.vectordb, "iter_payloads", lambda: [
30+
{"repository": "kdk", "source_path": "map/x.js", "file_sha1": "aaa"},
31+
{"repository": "kano", "source_path": "map/x.js", "file_sha1": "bbb"},
32+
])
33+
assert indexed_file_state.load_indexed_file_hashes() == {
34+
("kdk", "map/x.js"): "aaa",
35+
("kano", "map/x.js"): "bbb",
36+
}
37+
38+
39+
def test_load_indexed_file_hashes_shares_one_entry_per_file(monkeypatch):
40+
# Every chunk of a file carries the same file_sha1, so many payloads
41+
# collapse into a single entry.
42+
monkeypatch.setattr(indexed_file_state.vectordb, "iter_payloads", lambda: [
43+
{"repository": "kdk", "source_path": "map/x.js", "file_sha1": "aaa"},
44+
{"repository": "kdk", "source_path": "map/x.js", "file_sha1": "aaa"},
45+
])
46+
assert indexed_file_state.load_indexed_file_hashes() == {
47+
("kdk", "map/x.js"): "aaa"}
48+
49+
50+
def test_load_indexed_file_hashes_skips_payloads_without_a_digest(monkeypatch):
51+
monkeypatch.setattr(indexed_file_state.vectordb, "iter_payloads", lambda: [
52+
{"repository": "kdk", "source_path": "map/x.js", "file_sha1": ""},
53+
{"repository": "kdk", "source_path": "map/y.js"},
54+
])
55+
assert indexed_file_state.load_indexed_file_hashes() == {}
56+
57+
58+
def test_load_indexed_file_hashes_is_empty_on_the_first_run(monkeypatch):
59+
monkeypatch.setattr(
60+
indexed_file_state.vectordb, "iter_payloads", lambda: [])
61+
assert indexed_file_state.load_indexed_file_hashes() == {}
62+
63+
64+
# --- select_changed_chunks: drop chunks whose file is unchanged -----------
65+
66+
def test_select_changed_chunks_keeps_a_file_that_is_not_indexed_yet():
67+
chunk = {"metadata": {
68+
"repository": "kdk", "source_path": "map/x.js", "file_sha1": "aaa"}}
69+
assert indexed_file_state.select_changed_chunks([chunk], {}) == [chunk]
70+
71+
72+
def test_select_changed_chunks_drops_a_file_indexed_at_the_same_digest():
73+
chunk = {"metadata": {
74+
"repository": "kdk", "source_path": "map/x.js", "file_sha1": "aaa"}}
75+
indexed = {("kdk", "map/x.js"): "aaa"}
76+
assert indexed_file_state.select_changed_chunks([chunk], indexed) == []
77+
78+
79+
def test_select_changed_chunks_keeps_a_file_whose_content_changed():
80+
chunk = {"metadata": {
81+
"repository": "kdk", "source_path": "map/x.js", "file_sha1": "bbb"}}
82+
indexed = {("kdk", "map/x.js"): "aaa"}
83+
assert indexed_file_state.select_changed_chunks([chunk], indexed) == [chunk]
84+
85+
86+
def test_select_changed_chunks_distinguishes_repositories():
87+
# source_path is repo-relative, so the same path in another repo must not
88+
# be mistaken for an already indexed file.
89+
chunk = {"metadata": {
90+
"repository": "kano", "source_path": "map/x.js", "file_sha1": "aaa"}}
91+
indexed = {("kdk", "map/x.js"): "aaa"}
92+
assert indexed_file_state.select_changed_chunks([chunk], indexed) == [chunk]
93+
94+
95+
def test_select_changed_chunks_keeps_every_chunk_of_a_changed_file():
96+
chunks = [
97+
{"metadata": {"repository": "kdk", "source_path": "map/x.js",
98+
"file_sha1": "bbb", "chunk_index": 0}},
99+
{"metadata": {"repository": "kdk", "source_path": "map/x.js",
100+
"file_sha1": "bbb", "chunk_index": 1}},
101+
]
102+
indexed = {("kdk", "map/x.js"): "aaa"}
103+
assert indexed_file_state.select_changed_chunks(chunks, indexed) == chunks

test/unit/test_pipeline.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from ingestion import pipeline
2+
from ingestion.indexed_file_state import compute_file_sha1
3+
4+
JAVASCRIPT = "export function center () {\n return [0, 0]\n}\n"
5+
MARKDOWN = "# Title\n\nSome prose about the map.\n"
6+
7+
8+
# --- dispatch by extension ------------------------------------------------
9+
10+
def test_chunk_files_dispatches_each_extension_to_a_chunker(tmp_path):
11+
_write(tmp_path, "kdk/map/base.js", JAVASCRIPT)
12+
_write(tmp_path, "kdk/docs/guide.md", MARKDOWN)
13+
14+
chunks = pipeline.chunk_files(_files(tmp_path), tmp_path)
15+
16+
assert {chunk["metadata"]["source_path"] for chunk in chunks} == {
17+
"map/base.js", "docs/guide.md"}
18+
19+
20+
def test_chunk_files_skips_extensions_without_a_chunker(tmp_path):
21+
# The incremental path feeds this function straight from git, which knows
22+
# nothing about the scanner's filters, so unsupported files arrive here.
23+
_write(tmp_path, "kdk/scripts/deploy.py", "print('hi')")
24+
_write(tmp_path, "kdk/map/base.js", JAVASCRIPT)
25+
26+
chunks = pipeline.chunk_files(_files(tmp_path), tmp_path)
27+
28+
assert all(chunk["metadata"]["source_path"] == "map/base.js"
29+
for chunk in chunks)
30+
31+
32+
def test_chunk_files_dispatches_on_a_lowercased_extension(tmp_path):
33+
_write(tmp_path, "kdk/docs/GUIDE.MD", MARKDOWN)
34+
35+
chunks = pipeline.chunk_files(_files(tmp_path), tmp_path)
36+
37+
assert chunks
38+
39+
40+
def test_chunk_files_returns_nothing_for_no_files(tmp_path):
41+
assert pipeline.chunk_files([], tmp_path) == []
42+
43+
44+
# --- repository and source_path: the key everything else uses -------------
45+
46+
def test_chunk_files_splits_the_repository_from_the_source_path(tmp_path):
47+
# delete_file() and the indexed state both key on this pair, so a wrong
48+
# split silently orphans a file's chunks instead of replacing them.
49+
_write(tmp_path, "kdk/map/mixin.base-map.js", JAVASCRIPT)
50+
51+
chunk = pipeline.chunk_files(_files(tmp_path), tmp_path)[0]
52+
53+
assert chunk["metadata"]["repository"] == "kdk"
54+
assert chunk["metadata"]["source_path"] == "map/mixin.base-map.js"
55+
56+
57+
def test_chunk_files_keeps_the_source_path_repository_relative(tmp_path):
58+
_write(tmp_path, "kdk/packages/core/src/deep.js", JAVASCRIPT)
59+
60+
chunk = pipeline.chunk_files(_files(tmp_path), tmp_path)[0]
61+
62+
assert chunk["metadata"]["repository"] == "kdk"
63+
assert chunk["metadata"]["source_path"] == "packages/core/src/deep.js"
64+
65+
66+
def test_chunk_files_distinguishes_the_same_path_in_two_repositories(tmp_path):
67+
_write(tmp_path, "kdk/map/base.js", JAVASCRIPT)
68+
_write(tmp_path, "kano/map/base.js", JAVASCRIPT)
69+
70+
chunks = pipeline.chunk_files(_files(tmp_path), tmp_path)
71+
72+
assert {chunk["metadata"]["repository"] for chunk in chunks} == {
73+
"kdk", "kano"}
74+
75+
76+
# --- file_sha1: the gate on the next run ----------------------------------
77+
78+
def test_chunk_files_stamps_the_digest_of_the_whole_file(tmp_path):
79+
_write(tmp_path, "kdk/map/base.js", JAVASCRIPT)
80+
81+
chunk = pipeline.chunk_files(_files(tmp_path), tmp_path)[0]
82+
83+
assert chunk["metadata"]["file_sha1"] == compute_file_sha1(JAVASCRIPT)
84+
85+
86+
def test_chunk_files_stamps_every_chunk_of_a_file_with_the_same_digest(
87+
tmp_path):
88+
# select_changed_chunks() compares per file, not per chunk: one chunk
89+
# carrying a different digest would re-embed a file that never changed.
90+
_write(tmp_path, "kdk/map/base.js", JAVASCRIPT * 40)
91+
92+
chunks = pipeline.chunk_files(_files(tmp_path), tmp_path)
93+
94+
assert len(chunks) > 1
95+
assert len({chunk["metadata"]["file_sha1"] for chunk in chunks}) == 1
96+
97+
98+
def test_chunk_files_changes_the_digest_when_the_content_changes(tmp_path):
99+
path = _write(tmp_path, "kdk/map/base.js", JAVASCRIPT)
100+
before = pipeline.chunk_files([path], tmp_path)[0]
101+
102+
path.write_text(JAVASCRIPT.replace("[0, 0]", "[1, 1]"))
103+
after = pipeline.chunk_files([path], tmp_path)[0]
104+
105+
assert before["metadata"]["file_sha1"] != after["metadata"]["file_sha1"]
106+
107+
108+
# ---------------------------------------------------------------------------
109+
# UTILS
110+
# ---------------------------------------------------------------------------
111+
112+
113+
# Write a workspace-relative file, creating parent directories as needed.
114+
def _write(root, relative, text):
115+
path = root / relative
116+
path.parent.mkdir(parents=True, exist_ok=True)
117+
path.write_text(text)
118+
return path
119+
120+
121+
# Every file under the workspace, as the callers of chunk_files pass them.
122+
def _files(root):
123+
return sorted(path for path in root.rglob("*") if path.is_file())

0 commit comments

Comments
 (0)