"""Tests: Index-Bau (Chunking, FTS, Metadaten, Schema-Gates).""" import sqlite3 import pytest from agent.ingest import SCHEMA, build_index from agent.kb import KbValidationError def test_build_index_chunks_and_fts(mini_index): con = sqlite3.connect(mini_index.db_path) try: n_chunks = con.execute("SELECT COUNT(*) FROM chunks").fetchone()[0] n_fts = con.execute("SELECT COUNT(*) FROM chunks_fts").fetchone()[0] n_entries = con.execute( "SELECT COUNT(DISTINCT entry_id) FROM chunks" ).fetchone()[0] assert n_entries == 3 assert n_chunks == n_fts and n_chunks >= 7 row = con.execute( "SELECT entry_id, section, norm FROM chunks WHERE entry_id='lb-min-01' " "AND section LIKE 'Kernwerte%'" ).fetchone() assert row is not None # Umlaut-Folding im FTS-Text: "Lohnausgleich" normalisiert auffindbar assert "lohnausgleich" in row[2] meta = dict(con.execute("SELECT key, value FROM meta").fetchall()) assert meta["n_entries"] == "3" assert meta["embed_model"] == "" # embed_off=True finally: con.close() def test_norm_contains_tags_and_legal_bases(mini_index): con = sqlite3.connect(mini_index.db_path) try: norm = con.execute( "SELECT norm FROM chunks WHERE entry_id='lb-min-01' " "AND section='Zusammenfassung'" ).fetchone()[0] assert "alvg" in norm # legal_bases im FTS-Text assert "azg" in norm and "19e" in norm finally: con.close() def test_rebuild_is_idempotent(mini_index): stats = build_index(mini_index) assert stats.n_entries == 3 con = sqlite3.connect(mini_index.db_path) try: assert con.execute("SELECT COUNT(*) FROM chunks").fetchone()[0] == stats.n_chunks finally: con.close() def test_build_index_aborts_on_gate_error(mini_cfg): """Gate-Fehler (Registry kaputt) bricht den Ingest ab — kein halber Index.""" from pathlib import Path (Path(mini_cfg.kb_dir) / "kb.json").write_text( '{"n_entries": 0, "entries": []}', encoding="utf-8" ) with pytest.raises(KbValidationError): build_index(mini_cfg) def test_vectors_table_cached_across_rebuilds(mini_index): """Die Vektoren-Tabelle bleibt beim Rebuild erhalten (Cache-Garantie).""" con = sqlite3.connect(mini_index.db_path) try: con.execute( "INSERT INTO vectors(content_hash, model, dim, vec) " "VALUES ('deadbeef', 'bge-m3', 2, x'000000003f800000')" ) # 0.0, 1.0 con.commit() finally: con.close() build_index(mini_index) con = sqlite3.connect(mini_index.db_path) try: assert con.execute( "SELECT COUNT(*) FROM vectors WHERE content_hash='deadbeef'" ).fetchone()[0] == 1 finally: con.close() def test_schema_creates_fts5(tmp_path): con = sqlite3.connect(tmp_path / "s.db") try: con.executescript(SCHEMA) con.execute("INSERT INTO chunks_fts(rowid, norm) VALUES (1, 'testtext')") hits = con.execute( "SELECT rowid FROM chunks_fts WHERE chunks_fts MATCH '\"testtext\"'" ).fetchall() assert hits == [(1,)] finally: con.close()