"""The load stage, exercised against the in-memory store with no server. Two classes of failure are silent in a vector database and are what most of these tests aim at. Loading the same corpus twice can leave two copies of a dose, and every query still succeeds — so idempotency is asserted by point count, not by inspecting the upsert calls. Mixing two corpus generations or two models into one collection also raises nothing at query time; every search returns *something*, just from the wrong material. That is what the manifest gate exists to make loud, and there is a test per way it can be violated. The last test runs over the real `chunks.jsonl` when it is present, because a provenance rule that only holds for hand-written records is not evidence. """ import json from pathlib import Path import pytest from ingestion.load import ( ChunkLoader, CollectionSpec, CorpusManifest, CorpusMismatch, InMemoryVectorStore, PointCountMismatch, build_point, corpus_sha256, count_chunks, iter_chunk_records, manifest_collection, point_id_for, read_manifest, validate_chunk_record, ) DIMENSIONS = 4 COLLECTION = "duoc_thu_chunks" CORPUS_SHA = "a" * 64 OTHER_SHA = "b" * 64 MODEL = "amazon.titan-embed-text-v2:0" REAL_CHUNKS = ( Path(__file__).resolve().parents[1] / "data" / "processed" / "chunks.jsonl" ) def chunk_record(chunk_id="abacavir__lieu_luong__0", **overrides): record = { "schema_version": 4, "chunk_id": chunk_id, "drug_id": "abacavir", "drug_name": "ABACAVIR", "section_key": "lieu_luong_va_cach_dung", "section_display_name": "Liều lượng và cách dùng", "text": "Người lớn: 300 mg, hai lần mỗi ngày.", "source_text": "Người lớn: 300 mg, hai lần mỗi ngày.", "heading_physical_page": 100, "source_page_range": [100, 102], "printed_page_range": [101, 103], "atc_codes": ["J05AF06"], "part_index": 0, "part_count": 1, "est_tokens": 14, "oversized": False, "chunk_kind": "prose", "attachments": [], "has_quarantined_content": False, } record.update(overrides) return record def vector(seed=0.1): return [seed] * DIMENSIONS def manifest(**overrides): values = { "corpus_sha256": CORPUS_SHA, "chunk_count": 3, "model_id": MODEL, "dimensions": DIMENSIONS, "input_kind": "document", "provider": "titan-v2", } values.update(overrides) return CorpusManifest(**values) def spec(**overrides): values = {"name": COLLECTION, "vector_size": DIMENSIONS} values.update(overrides) return CollectionSpec(**values) def loader(store, **overrides): return ChunkLoader( store, overrides.pop("spec", spec()), overrides.pop("manifest", manifest()), **overrides, ) def pairs(count=3): return [ (chunk_record(chunk_id=f"drug__section__{i}"), vector(0.1 * (i + 1))) for i in range(count) ] # --- A5: derived ids and idempotency ------------------------------------- def test_point_id_is_derived_from_chunk_id_and_is_stable(): first = point_id_for("abacavir__lieu_luong__0") second = point_id_for("abacavir__lieu_luong__0") assert first == second assert first != point_id_for("abacavir__lieu_luong__1") def test_point_id_refuses_an_empty_chunk_id(): with pytest.raises(ValueError, match="chunk_id is required"): point_id_for(" ") def test_loading_the_same_corpus_twice_leaves_the_point_count_unchanged(): store = InMemoryVectorStore() data = pairs(3) first = loader(store).load(data) second = loader(store).load(data) assert first.collection_created is True assert second.collection_created is False assert first.collection_count == 3 assert second.collection_count == 3, "a re-run duplicated points" assert second.points_upserted == 3 def test_a_reloaded_chunk_overwrites_its_own_point_rather_than_adding_one(): store = InMemoryVectorStore() record = chunk_record() loader(store).load([(record, vector(0.1))]) edited = chunk_record(text="Người lớn: 600 mg, một lần mỗi ngày.") loader(store).load([(edited, vector(0.9))]) assert store.count(COLLECTION) == 1 stored = store.retrieve(COLLECTION, point_id_for(record["chunk_id"])) assert stored.payload["text"] == "Người lớn: 600 mg, một lần mỗi ngày." assert stored.vector == vector(0.9) def test_records_are_upserted_in_batches_of_the_configured_size(): store = InMemoryVectorStore() report = loader(store, batch_size=2).load(pairs(5)) assert report.batches == 3 assert report.points_upserted == 5 assert report.collection_count == 5 def test_batch_size_must_be_positive(): with pytest.raises(ValueError, match="batch_size must be positive"): ChunkLoader(InMemoryVectorStore(), spec(), manifest(), batch_size=0) # --- A4: collection shape and payload ------------------------------------ def test_collection_is_created_with_the_declared_size_and_payload_indexes(): store = InMemoryVectorStore() loader(store).load(pairs(1)) assert store.spec(COLLECTION).vector_size == DIMENSIONS assert store.spec(COLLECTION).distance == "Cosine" indexed = dict(store.indexed_fields(COLLECTION)) assert indexed["drug_id"] == "keyword" assert indexed["section_key"] == "keyword" assert indexed["atc_codes"] == "keyword" assert indexed["chunk_kind"] == "keyword" assert indexed["has_quarantined_content"] == "bool" def test_payload_carries_every_provenance_field_of_the_chunk_record(): store = InMemoryVectorStore() record = chunk_record() loader(store).load([(record, vector())]) payload = store.retrieve(COLLECTION, point_id_for(record["chunk_id"])).payload assert payload == record def test_a_field_added_by_a_future_chunker_flows_through_untouched(): store = InMemoryVectorStore() record = chunk_record( population_tags=["Người lớn", "Suy thận"], printed_page_range=[101, 103] ) loader(store).load([(record, vector())]) payload = store.retrieve(COLLECTION, point_id_for(record["chunk_id"])).payload assert payload["population_tags"] == ["Người lớn", "Suy thận"] assert payload["printed_page_range"] == [101, 103] @pytest.mark.parametrize( "field", [ "chunk_id", "drug_id", "section_key", "source_page_range", "printed_page_range", "text", ], ) def test_a_chunk_missing_a_required_provenance_field_is_refused(field): with pytest.raises(ValueError, match="missing required provenance fields"): validate_chunk_record(chunk_record(**{field: None})) # --- failing closed on incomplete provenance ------------------------------ # # Every case below passed an earlier version of this validator. The cost of # that is not an exception at load time — it is paying for an embedding run and # then discovering every answer abstains because the chunks cannot be cited. @pytest.mark.parametrize("field", ["source_page_range", "printed_page_range"]) def test_an_empty_page_range_is_missing_not_present(field): """`[] in (None, "")` is False, which is exactly how this slipped through.""" with pytest.raises(ValueError, match="missing required provenance fields"): validate_chunk_record(chunk_record(**{field: []})) def test_an_unknown_old_or_future_schema_is_refused_fail_closed(): for version in (3, 5): with pytest.raises(ValueError, match="supports exactly v4"): validate_chunk_record(chunk_record(schema_version=version)) def test_a_chunk_with_no_schema_version_at_all_is_refused(): record = chunk_record() del record["schema_version"] with pytest.raises(ValueError, match="declares schema_version None"): validate_chunk_record(record) @pytest.mark.parametrize( "value", [[101], [101, 102, 103], "101-103", 101, {"start": 101}] ) def test_a_page_range_that_is_not_a_pair_is_refused(value): with pytest.raises(ValueError, match=r"expected a \[start, end\] pair"): validate_chunk_record(chunk_record(printed_page_range=value)) def test_a_page_range_running_backwards_is_refused(): with pytest.raises(ValueError, match="running backwards"): validate_chunk_record(chunk_record(printed_page_range=[103, 101])) def test_a_non_integer_page_is_refused(): with pytest.raises(ValueError, match="non-integer page"): validate_chunk_record(chunk_record(printed_page_range=[101.5, 103])) def test_boolean_pages_are_not_accepted_as_python_integers(): with pytest.raises(ValueError, match="non-integer page"): validate_chunk_record(chunk_record(printed_page_range=[False, True])) @pytest.mark.parametrize( ("field", "value"), [("source_page_range", [-1, 0]), ("printed_page_range", [0, 1])], ) def test_page_ranges_reject_impossible_lower_bounds(field, value): with pytest.raises(ValueError, match="pages must start"): validate_chunk_record(chunk_record(**{field: value})) def test_page_zero_and_false_are_values_not_absences(): """Physical pages are 0-indexed; a falsiness test would reject real records.""" validate_chunk_record( chunk_record( heading_physical_page=0, source_page_range=[0, 0], printed_page_range=[1, 1], has_quarantined_content=False, oversized=False, part_index=0, ) ) def test_a_wrong_sized_vector_is_refused_before_anything_is_upserted(): store = InMemoryVectorStore() with pytest.raises(ValueError, match="expects 4"): loader(store).load([(chunk_record(), [0.1, 0.2])]) assert store.count(COLLECTION) == 0 def test_manifest_dimensions_must_agree_with_the_collection_spec(): with pytest.raises(ValueError, match="manifest declares 8 dimensions"): ChunkLoader(InMemoryVectorStore(), spec(), manifest(dimensions=8)) def test_collection_spec_refuses_a_nonpositive_vector_size(): with pytest.raises(ValueError, match="vector_size must be positive"): CollectionSpec(name=COLLECTION, vector_size=0) # --- A6: the corpus binding gate ----------------------------------------- def test_the_manifest_lives_beside_the_data_so_the_point_count_stays_exact(): store = InMemoryVectorStore() loader(store).load(pairs(3)) assert store.count(COLLECTION) == 3, "the manifest must not inflate the count" assert store.count(manifest_collection(COLLECTION)) == 1 stored = read_manifest(store, COLLECTION) assert stored.corpus_sha256 == CORPUS_SHA assert stored.model_id == MODEL assert stored.provider == "titan-v2" def test_a_second_corpus_generation_is_refused_and_nothing_is_written(): store = InMemoryVectorStore() loader(store).load(pairs(3)) with pytest.raises(CorpusMismatch, match="does not match"): loader(store, manifest=manifest(corpus_sha256=OTHER_SHA)).load(pairs(2)) assert store.count(COLLECTION) == 3 assert read_manifest(store, COLLECTION).corpus_sha256 == CORPUS_SHA def test_a_second_model_is_refused_even_when_the_corpus_matches(): store = InMemoryVectorStore() loader(store).load(pairs(1)) with pytest.raises(CorpusMismatch, match="cohere.embed-v4:0"): loader(store, manifest=manifest(model_id="cohere.embed-v4:0")).load(pairs(1)) def test_a_query_subspace_vector_is_refused_for_a_document_collection(): store = InMemoryVectorStore() loader(store).load(pairs(1)) with pytest.raises(CorpusMismatch, match="input kind query"): loader(store, manifest=manifest(input_kind="query")).load(pairs(1)) def test_a_dimension_change_is_refused(): store = InMemoryVectorStore() loader(store).load(pairs(1)) eight = CollectionSpec(name=COLLECTION, vector_size=8) with pytest.raises(CorpusMismatch, match="8 dimensions"): ChunkLoader(store, eight, manifest(dimensions=8)).load( [(chunk_record(), [0.1] * 8)] ) def test_an_existing_collection_with_no_manifest_is_refused(): store = InMemoryVectorStore() store.create_collection(spec()) store.upsert(COLLECTION, [build_point(chunk_record(), vector())]) with pytest.raises(CorpusMismatch, match="has no manifest"): loader(store).load(pairs(1)) def test_the_same_corpus_and_model_is_allowed_through(): store = InMemoryVectorStore() loader(store).load(pairs(3)) report = loader(store).load(pairs(3)) assert report.collection_count == 3 assert report.count_matches is True # --- the v1 point-count gate --------------------------------------------- def test_assert_point_count_passes_when_every_chunk_has_exactly_one_point(): store = InMemoryVectorStore() active = loader(store) active.load(pairs(3)) assert active.assert_point_count(3) == 3 def test_assert_point_count_raises_when_the_collection_is_short(): store = InMemoryVectorStore() active = loader(store) active.load(pairs(3)) with pytest.raises(PointCountMismatch, match="holds 3 points but the corpus has 4"): active.assert_point_count(4) # --- mode A: exhaustive filter retrieval --------------------------------- def section_pairs(drug_id, section_key, parts): return [ ( chunk_record( chunk_id=f"{drug_id}__{section_key}__{i}", drug_id=drug_id, section_key=section_key, part_index=i, part_count=parts, ), vector(0.1), ) for i in range(parts) ] def test_a_filter_returns_every_part_of_a_section_not_a_top_k(): """The rule mode A exists for: two of five contraindications is worse than none.""" store = InMemoryVectorStore() loader(store).load( section_pairs("metformin", "chong_chi_dinh", 5) + section_pairs("metformin", "lieu_luong_va_cach_dung", 3) + section_pairs("pantoprazol", "chong_chi_dinh", 2) ) found = store.find_by_payload( COLLECTION, {"drug_id": "metformin", "section_key": "chong_chi_dinh"} ) assert len(found) == 5 assert sorted(p.payload["part_index"] for p in found) == [0, 1, 2, 3, 4] assert {p.payload["drug_id"] for p in found} == {"metformin"} def test_a_filter_never_leaks_a_neighbouring_drugs_section(): store = InMemoryVectorStore() loader(store).load( section_pairs("pantoprazol", "chong_chi_dinh", 2) + section_pairs("omeprazol", "chong_chi_dinh", 2) ) found = store.find_by_payload( COLLECTION, {"drug_id": "pantoprazol", "section_key": "chong_chi_dinh"} ) assert len(found) == 2 assert {p.payload["drug_id"] for p in found} == {"pantoprazol"} def test_a_list_valued_field_matches_on_any_element(): store = InMemoryVectorStore() record = chunk_record(atc_codes=["A10BA02", "A10BD20"]) loader(store).load([(record, vector())]) assert len(store.find_by_payload(COLLECTION, {"atc_codes": "A10BD20"})) == 1 assert len(store.find_by_payload(COLLECTION, {"atc_codes": "J05AF06"})) == 0 def test_a_filter_matching_nothing_returns_empty_rather_than_raising(): store = InMemoryVectorStore() loader(store).load(pairs(2)) assert store.find_by_payload(COLLECTION, {"drug_id": "khong_ton_tai"}) == [] def test_a_filter_with_no_condition_is_refused(): store = InMemoryVectorStore() loader(store).load(pairs(1)) with pytest.raises(ValueError, match="at least one condition"): store.find_by_payload(COLLECTION, {}) def test_parts_reassemble_in_order_into_the_whole_section(): store = InMemoryVectorStore() bodies = ["Phần một.", "Phần hai.", "Phần ba."] records = [ ( chunk_record( chunk_id=f"metformin__chong_chi_dinh__{i}", drug_id="metformin", section_key="chong_chi_dinh", text=body, part_index=i, part_count=len(bodies), ), vector(), ) for i, body in enumerate(bodies) ] loader(store).load(records) found = store.find_by_payload( COLLECTION, {"drug_id": "metformin", "section_key": "chong_chi_dinh"} ) ordered = sorted(found, key=lambda p: p.payload["part_index"]) assert [p.payload["text"] for p in ordered] == bodies assert {p.payload["part_count"] for p in found} == {3} # --- corpus digest and reading ------------------------------------------- def test_corpus_sha256_changes_when_a_single_byte_changes(tmp_path): path = tmp_path / "chunks.jsonl" path.write_text(json.dumps(chunk_record()) + "\n", encoding="utf-8") before = corpus_sha256(path) path.write_text( json.dumps(chunk_record(text="Người lớn: 301 mg.")) + "\n", encoding="utf-8" ) assert corpus_sha256(path) != before assert len(before) == 64 def test_the_same_data_hashes_the_same_under_crlf_and_lf(tmp_path): """A gate that cries wolf gets switched off. A raw-byte digest made a Windows CRLF checkout and a Linux LF checkout of identical data disagree, so A6 would refuse a CI load against the very corpus it was built from. """ body = json.dumps(chunk_record()) + "\n" + json.dumps(chunk_record("b")) + "\n" lf = tmp_path / "lf.jsonl" crlf = tmp_path / "crlf.jsonl" lf.write_bytes(body.encode("utf-8")) crlf.write_bytes(body.replace("\n", "\r\n").encode("utf-8")) assert corpus_sha256(lf) == corpus_sha256(crlf) assert crlf.stat().st_size > lf.stat().st_size, "the files really do differ" def test_blank_lines_are_skipped_and_bad_json_names_its_line(tmp_path): path = tmp_path / "chunks.jsonl" path.write_text( json.dumps(chunk_record()) + "\n\n" + json.dumps(chunk_record("b")) + "\n", encoding="utf-8", ) assert count_chunks(path) == 2 broken = tmp_path / "broken.jsonl" broken.write_text(json.dumps(chunk_record()) + "\n{oops\n", encoding="utf-8") with pytest.raises(ValueError, match="line 2 is not valid JSON"): list(iter_chunk_records(broken)) # --- the real artifact ---------------------------------------------------- @pytest.mark.skipif( not REAL_CHUNKS.exists(), reason="chunks.jsonl has not been generated" ) def test_every_real_chunk_satisfies_the_loader_provenance_contract(): """Whole-artifact scope: all records in `data/processed/chunks.jsonl`.""" seen_ids = set() seen_points = set() total = 0 for record in iter_chunk_records(REAL_CHUNKS): validate_chunk_record(record) point = point_id_for(record["chunk_id"]) assert point not in seen_points, f"point id collision on {record['chunk_id']}" seen_points.add(point) seen_ids.add(record["chunk_id"]) total += 1 assert total == len(seen_ids), "duplicate chunk_id in the artifact" assert total == len(seen_points) # A floor, not the exact count: the corpus is regenerated as `segment/` # changes, but a truncated or half-written artifact must not pass as # whole-artifact evidence. Measured 15,066 records on 2026-08-04. assert total > 10_000, f"chunks.jsonl looks truncated: only {total} records"