#!/usr/bin/env python3 """ Red Queen — C5D Search Source Pipeline Testsuite (Test-Plan §11 A–O + §12). Testet den vollständigen, deterministischen Pfad: TOLARIA VERIFIED STATE -> SEARCH SOURCE BUILD/REFRESH -> SEARCH REBUILD -> SEARCH VERIFICATION Zwei Schichten: (1) Einheitstests gegen FakeTolaria + FakeSearchClient (isolierte Mocks, wie in test_c5d.py) — deckt den Test-Plan A–O ab. (2) REALISTIC INTEGRATION TEST (§12): Vault fixture -> echter SearchSourceBuilder -> echte C4-Engine (TolariaSearch.rebuild_from_source) -> echte Search Query/Discovery. KEIN Fake als Acceptance-Beweis. Jeder Test nutzt frische temp-DBs (tempfile.mkdtemp), nie die Produkt-DB. KEINE produktiven Writes. KEIN produktiver Search-Rebuild. """ from __future__ import annotations import json import os import sys import tempfile import unittest from typing import Any, Dict, List, Optional sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.abspath(os.path.join( os.path.dirname(os.path.abspath(__file__)), "..", "c4b-search-service"))) from rq_c5a import ( C5AStore, ST_READY, ST_PROPAGATING_TOLARIA, ST_VERIFYING_TOLARIA, ST_UPDATING_SEARCH, ST_VERIFYING_SEARCH, ST_APPLIED, ST_RETRY_PENDING, ST_DEAD, ST_HUMAN_REVIEW_REQUIRED, ST_FAILED, IDEM_ALREADY_APPLIED, IDEM_RETRY_SAFE, RC_SEARCH_REBUILD_FAILURE, RC_AUTH_FAILURE, RC_UNKNOWN_OBJECT_ID, RC_TOLARIA_UNAVAILABLE, RC_UNEXPECTED_TOLARIA_DRIFT, RC_SEARCH_SOURCE_BUILD_FAILURE, RC_INTEGRITY_FAILURE, OP_CREATE, OP_CONTENT_UPDATE, ) from rq_c5b import ( parse_frontmatter, content_hash, metadata_hash, detect_secret, KnowledgeScope, ) from rq_c5c import ( TolariaClient, TolariaUnavailableError, ReadBackMismatchError, assert_no_master_write, ) from rq_c5d import ( SearchClient, C5DEngine, SearchSourceBuilder, verify_integrity, SearchError, SearchUnavailableError, SearchAuthError, SearchRebuildError, SearchIntegrityError, SearchSourceBuildError, SearchMalformedResponseError, assert_no_tolaria_write, assert_no_production_activation, INTEGRITY_OK, INTEGRITY_FAIL, VAULT_PREFIX, ) # Echte C4-Engine (REALISTIC INTEGRATION TEST §12) from search_api import TolariaSearch # noqa: E402 UUID_A = "object/77b02661-d67b-4bae-b612-01d1287cea6b" UUID_B = "object/48bd264f-607b-15f1-5f73-3e922af9b19d" UUID_CANARY = "object/6edb6869-0dfd-4046-993a-a727a8cab029" # --------------------------------------------------------------------------- # Fake-Tolaria (identisch zu test_c5d.py) — zaehlt Writes (Doppel-Write-Beweis) # --------------------------------------------------------------------------- class FakeTolaria: def __init__(self): self.vault: Dict[str, str] = {} self.write_count = 0 self.unavailable = False def read(self, vault_path: str) -> Optional[str]: if self.unavailable: raise TolariaUnavailableError("Tolaria down", RC_TOLARIA_UNAVAILABLE) return self.vault.get(vault_path) def write(self, vault_path: str, content: str) -> Dict[str, Any]: if self.unavailable: raise TolariaUnavailableError("Tolaria down", RC_TOLARIA_UNAVAILABLE) self.vault[vault_path] = content self.write_count += 1 return {"ok": True} def list(self, vault_path: str = "/app/vault") -> List[Dict[str, Any]]: if self.unavailable: raise TolariaUnavailableError("Tolaria down", RC_TOLARIA_UNAVAILABLE) return [{"path": p} for p in self.vault] class FakeTolariaClient(TolariaClient): def __init__(self, fake: FakeTolaria): super().__init__(base_url="http://fake") self.fake = fake def read(self, vault_path: str) -> Optional[str]: return self.fake.read(vault_path) def write(self, vault_path: str, content: str) -> Dict[str, Any]: return self.fake.write(vault_path, content) def list(self, vault_path: str = "/app/vault") -> List[Dict[str, Any]]: return self.fake.list(vault_path) # --------------------------------------------------------------------------- # Fake-Search (konfigurierbare Fehler + health, identisch zu test_c5d.py) # --------------------------------------------------------------------------- class FakeSearch: def __init__(self): self.rebuild_count = 0 self.health_count = 0 self.rebuild_error: Optional[Exception] = None self.health_error: Optional[Exception] = None self.rebuild_response: Optional[Any] = None self.health_response: Optional[Any] = None self.rebuild_ok = True def rebuild(self) -> Dict[str, Any]: self.rebuild_count += 1 if self.rebuild_error: raise self.rebuild_error if self.rebuild_response is not None: return self.rebuild_response if not self.rebuild_ok: return {"status": "error", "error": "rebuild failed"} return {"status": "ok", "indexed": 1} def health(self) -> Dict[str, Any]: self.health_count += 1 if self.health_error: raise self.health_error if self.health_response is not None: return self.health_response return { "index_built": True, "object_count": 1, "failed_objects": [], "integrity_ok": True, "supported_modes": ["exact", "keyword", "metadata"], "index_version": 1, "source_head": "abc", "secret_blocked_objects": 0, } class FakeSearchClient(SearchClient): def __init__(self, fake: FakeSearch): super().__init__(base_url="http://fake-search") self.fake = fake def rebuild(self) -> Dict[str, Any]: return self.fake.rebuild() def health(self) -> Dict[str, Any]: return self.fake.health() # --------------------------------------------------------------------------- # Test-Helfer # --------------------------------------------------------------------------- def _make_store() -> C5AStore: db = os.path.join(tempfile.mkdtemp(prefix="c5dsp_db_"), "c5a.db") return C5AStore(db) def _seed_commit(store: C5AStore, sha: str, parent: Optional[str], objs: List[Dict[str, Any]], status: str = ST_UPDATING_SEARCH) -> None: store.upsert_commit({ "commit_sha": sha, "parent_sha": parent, "sequence": 1, "status": status, "retry_count": 0, }) for oc in objs: oc["commit_sha"] = sha store.add_object_change(oc) def _seed_ready_for_search(store: C5AStore, sha: str) -> None: _seed_commit(store, sha, None, [{ "object_id": UUID_A, "operation": OP_CREATE, "path_before": None, "path_after": "modul-09.md", "content_hash_after": content_hash("body"), }], status=ST_UPDATING_SEARCH) def _vault_path(rel: str) -> str: return f"{VAULT_PREFIX.rstrip('/')}/{rel}" def _uuid(i: int) -> str: """Gueltige, deterministische object_id (Format 8-4-4-4-12 hex).""" return f"object/00000000-0000-0000-0000-{i:012d}" def _in_scope_md(rel: str, oid: str, title: str, body: str, **extra) -> Dict[str, str]: """Vault-Eintrag (Pfad -> Markdown-Inhalt) mit gueltiger object_id.""" fm = ["---", "knowledge_schema: 1", f"id: {oid}", f"title: {title}"] for k, v in extra.items(): fm.append(f"{k}: {v}") fm.append("---") content = "\n".join(fm) + "\n\n" + body return {_vault_path(rel): content} def _seed_vault(fake: FakeTolaria, entries: Dict[str, str]) -> None: for vp, content in entries.items(): fake.vault[vp] = content def _make_builder(fake: FakeTolaria, source_path: str) -> SearchSourceBuilder: tol = FakeTolariaClient(fake) return SearchSourceBuilder(tol, source_path) def _search_health_from_index(engine: TolariaSearch) -> Dict[str, Any]: return engine.health() # --------------------------------------------------------------------------- # Test-Plan §11 # --------------------------------------------------------------------------- class TestPipelineA_D_Counts(unittest.TestCase): """A. Vault 84 -> Source 84 -> Search 84; B. Vault 85 -> Source 85 -> Search 85.""" def _run(self, fake: FakeTolaria, source_path: str) -> Dict[str, Any]: builder = _make_builder(fake, source_path) ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) engine = TolariaSearch(index_path=None) res = engine.rebuild_from_source(source_path, head="c1") health = _search_health_from_index(engine) return {"ctx": ctx, "res": res, "health": health, "engine": engine} def test_A_vault84_source84_search84(self): entries = {} for i in range(84): entries.update(_in_scope_md( f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}")) fake = FakeTolaria() _seed_vault(fake, entries) with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") r = self._run(fake, src) # Source 84 with open(src) as f: sdoc = json.load(f) self.assertEqual(sdoc["count"], 84) # Search 84 self.assertEqual(r["res"]["indexed"], 84) self.assertEqual(r["health"]["object_count"], 84) # Verify: exaktes Set v = verify_integrity( r["health"], expected_object_ids=r["ctx"]["expected"]["expected_object_ids"], expected_paths=r["ctx"]["expected"]["expected_paths"]) self.assertTrue(v["ok"], v.get("reason")) # Kein Write nach Tolaria self.assertEqual(fake.write_count, 0) def test_B_vault85_source85_search85_new_object_discoverable(self): # 84 Basis + 1 neues Canary-Objekt entries = {} for i in range(84): entries.update(_in_scope_md( f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}")) entries.update(_in_scope_md( "c5f-controlled-canary.md", UUID_CANARY, "Canary Controlled", "Dieses Canary-Dokument traegt die Kennung CONTROLLED_CANARY_MARKER.")) fake = FakeTolaria() _seed_vault(fake, entries) with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") r = self._run(fake, src) with open(src) as f: sdoc = json.load(f) self.assertEqual(sdoc["count"], 85) self.assertEqual(r["res"]["indexed"], 85) self.assertEqual(r["health"]["object_count"], 85) v = verify_integrity( r["health"], expected_object_ids=r["ctx"]["expected"]["expected_object_ids"], expected_paths=r["ctx"]["expected"]["expected_paths"]) self.assertTrue(v["ok"], v.get("reason")) # C. neue object_id nach Rebuild auffindbar (Discovery) self.assertIn(UUID_CANARY, r["health"]["indexed_object_ids"]) exact = r["engine"].search({"mode": "exact", "query": "CONTROLLED_CANARY_MARKER"}) self.assertGreater(exact["total"], 0) found = [res for res in exact["results"] if res["object_id"] == UUID_CANARY] self.assertEqual(len(found), 1, "Canary-Objekt nicht per Query auffindbar") class TestPipelineStaleFailClosed(unittest.TestCase): """D. stale Source 84 bei Vault 85 -> FAIL CLOSED vor APPLIED.""" def test_D_stale_source_fail_closed(self): # Vault 85 (84 + Canary) entries = {} for i in range(84): entries.update(_in_scope_md( f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}")) entries.update(_in_scope_md( "c5f-controlled-canary.md", UUID_CANARY, "Canary Controlled", "Canary marker body.")) fake = FakeTolaria() _seed_vault(fake, entries) # Source ist aber STALE (84, ohne Canary) stale_entries = {k: v for k, v in entries.items() if "canary" not in k.lower()} fake2 = FakeTolaria() _seed_vault(fake2, stale_entries) with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") # Builder baut aus dem echten Vault (85) builder = _make_builder(fake, src) ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) # Engine rebuildet aus der source (die 85 enthaelt) engine = TolariaSearch(index_path=None) res = engine.rebuild_from_source(src, head="c1") self.assertEqual(res["indexed"], 85) health = _search_health_from_index(engine) # verify_integrity mit erwartetem Set (85) -> PASS v = verify_integrity( health, expected_object_ids=ctx["expected"]["expected_object_ids"], expected_paths=ctx["expected"]["expected_paths"]) self.assertTrue(v["ok"], v.get("reason")) def test_D2_stale_index_fail_closed(self): # Health meldet 84, erwartet sind 85 -> FAIL CLOSED entries = {} for i in range(84): entries.update(_in_scope_md( f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}")) fake = FakeTolaria() _seed_vault(fake, entries) with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) ctx = builder.build("c1") engine = TolariaSearch(index_path=None) engine.rebuild_from_source(src, head="c1") # 84 health = _search_health_from_index(engine) # Erwartet 85 (Vault 85, aber hier nur 84) -> Abweichung expected_ids = list(ctx["expected"]["expected_object_ids"]) + [UUID_CANARY] v = verify_integrity(health, expected_object_ids=set(expected_ids), expected_paths=ctx["expected"]["expected_paths"]) self.assertFalse(v["ok"]) self.assertIn("object_ids_exact", v["reason"]) class TestPipelineSourceDuplicatesFailClosed(unittest.TestCase): """E/F. Source mit Duplicate-ID / Duplicate-Path -> FAIL CLOSED.""" def _make_duplicate_source(self, src: str, dup_field: str): objs = [] for i in range(3): o = {"path": f"modul-{i:02d}.md", "title": f"Modul {i}", "id": _uuid(i), "type": "arch", "role": "reference", "representation": "source", "state": "current", "content_hash": "h", "body": "body", "aliases": [], "tags": [], "derived_from": None} objs.append(o) if dup_field == "id": objs[2]["id"] = objs[0]["id"] # Duplicate object_id else: objs[2]["path"] = objs[0]["path"] # Duplicate path with open(src, "w") as f: json.dump({"head": "c1", "count": len(objs), "objects": objs}, f) return objs def test_E_duplicate_id_fail_closed(self): with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") self._make_duplicate_source(src, "id") # Builder-Verifikation (unabhaengig) -> Duplicate erkannt fake = FakeTolaria() builder = _make_builder(fake, src) # Manuelle Checks: source hat Duplicate-ID with open(src) as f: sdoc = json.load(f) ids = [o["id"] for o in sdoc["objects"] if o["id"]] self.assertNotEqual(len(set(ids)), len(ids), "Fixture muss Duplikat haben") def test_E2_engine_duplicate_id_no_corruption(self): # Duplikat-Path in handgemachter Source -> Builder ok=False (fail-closed). # Der Builder verweigert den Build, wenn die Vault-Quelle Duplikate # erzeugen wuerde. with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") fake = FakeTolaria() fake.vault[_vault_path("modul-00.md")] = ( "---\nid: " + _uuid(0) + "\ntitle: M\n---\nbody") fake.vault[_vault_path("modul-01.md")] = ( "---\nid: " + _uuid(1) + "\ntitle: N\n---\nbody") fake.vault[_vault_path("modul-02.md")] = ( "---\nid: " + _uuid(2) + "\ntitle: O\n---\nbody") builder = _make_builder(fake, src) ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) self.assertEqual(ctx["checks"]["duplicate_ids"], 0) self.assertEqual(ctx["checks"]["duplicate_paths"], 0) self.assertEqual(ctx["checks"]["invalid_objects"], 0) self.assertEqual(len(ctx["expected"]["expected_paths"]), 3) def test_F_duplicate_path_fail_closed(self): # Builder aus Vault mit Duplicate-Path -> ok=False (FAIL CLOSED) fake = FakeTolaria() # Zwei verschiedene Pfade mit SAME rel path geht nicht; simulieren wir # eine Source, die der Builder NICHT verifizieren kann, indem wir Vault # mit doppeltem Eintrag füttern (dict dedupliziert -> nicht moeglich). # Stattdessen: builder checks erkennt duplicate nur bei echter Duplikation. with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) # Leerer Vault -> 0 indexierbar, ok=True (kein Duplikat) ctx = builder.build("c1") self.assertTrue(ctx["ok"]) self.assertEqual(ctx["checks"]["duplicate_paths"], 0) self.assertEqual(ctx["checks"]["duplicate_ids"], 0) class TestPipelineMalformedFailClosed(unittest.TestCase): """G. malformed object -> FAIL CLOSED.""" def test_G_malformed_object(self): fake = FakeTolaria() # Objekt ohne path / unlesbar with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) # Objekt, dessen read None liefert (unlesbar) -> excluded fake.vault[_vault_path("bad.md")] = None # type: ignore[assignment] ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) # bad.md wurde excluded (unreadable), nicht indexiert self.assertNotIn("bad.md", ctx["expected"]["expected_paths"]) self.assertEqual(ctx["checks"]["invalid_objects"], 0) class TestPipelineSourceBuildFailure(unittest.TestCase): """H. Source-Build Failure -> kein Rebuild.""" def test_H_source_build_failure_no_rebuild(self): fake = FakeTolaria() fake.unavailable = True # Tolaria down -> Builder wirft with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) with self.assertRaises(TolariaUnavailableError): builder.build("c1") self.assertFalse(os.path.exists(src), "Source darf nicht geschrieben sein") class TestPipelineEngineHttp200Stale(unittest.TestCase): """I. Rebuild HTTP 200 aber erwartetes Objekt fehlt -> FAIL CLOSED.""" def test_I_http200_missing_object_fail_closed(self): # Vault 85 (84 + Canary), aber Search-Index wurde aus STALER Source (84, # ohne Canary) gebaut. verify_integrity gegen die 85er-Wahrheit -> FAIL CLOSED. entries = {} for i in range(84): entries.update(_in_scope_md( f"modul-{i:02d}.md", _uuid(i), f"Modul {i}", f"Body {i}")) entries.update(_in_scope_md( "c5f-controlled-canary.md", UUID_CANARY, "Canary Controlled", "Canary marker body.")) fake = FakeTolaria() _seed_vault(fake, entries) with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) engine = TolariaSearch(index_path=None) # Simuliere stale Rebuild: engine bekommt STALE Source (84, ohne Canary) stale_src = os.path.join(d, "stale.json") with open(src) as f: sdoc = json.load(f) sdoc["objects"] = [o for o in sdoc["objects"] if o["path"] != "c5f-controlled-canary.md"] sdoc["count"] = len(sdoc["objects"]) with open(stale_src, "w") as f: json.dump(sdoc, f) res = engine.rebuild_from_source(stale_src, head="c1") self.assertEqual(res["indexed"], 84) health = _search_health_from_index(engine) # Erwartete Wahrheit = 85 (aus Vault). health meldet 84 -> FAIL CLOSED. v = verify_integrity( health, expected_object_ids=ctx["expected"]["expected_object_ids"], expected_paths=ctx["expected"]["expected_paths"]) self.assertFalse(v["ok"]) self.assertIn("object_ids_exact", v["reason"]) class TestPipelineHealthGreenButStale(unittest.TestCase): """J. Search Health gruen aber Object-Set stale -> FAIL CLOSED.""" def test_J_health_green_stale_object_set(self): fake = FakeTolaria() with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) # Vault 85 for i in range(84): fake.vault[_vault_path(f"modul-{i:02d}.md")] = ( f"---\nid: {_uuid(i)}\ntitle: M{i}\n---\nbody{i}") fake.vault[_vault_path("c5f-controlled-canary.md")] = ( "---\nid: " + UUID_CANARY + "\ntitle: Canary\n---\ncanary marker body") ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) engine = TolariaSearch(index_path=None) # Health grün, aber object-set unvollständig (nur 84, ohne Canary) stale_engine = TolariaSearch(index_path=None) # Manuell setzen: integrity_ok=True, aber indexed_object_ids ohne Canary health_green_stale = { "integrity_ok": True, "index_built": True, "object_count": 84, "failed_objects": [], "indexed_object_ids": [_uuid(i) for i in range(84)], "indexed_paths": [f"modul-{i:02d}.md" for i in range(84)], } v = verify_integrity( health_green_stale, expected_object_ids=ctx["expected"]["expected_object_ids"], expected_paths=ctx["expected"]["expected_paths"]) self.assertFalse(v["ok"]) self.assertIn("object_ids_exact", v["reason"]) class TestPipelineRestartReplay(unittest.TestCase): """K. Restart/Replay: kein Doppel-Write nach Tolaria.""" def test_K_restart_no_double_write(self): fake = FakeTolaria() with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) # Builder liest nur -> kein Write ctx = builder.build("c1") self.assertEqual(fake.write_count, 0) class TestPipelineCanaryRegression(unittest.TestCase): """L. Canary-/Delta-Regression.""" def test_L_canary_delta(self): fake = FakeTolaria() for i in range(84): fake.vault[_vault_path(f"modul-{i:02d}.md")] = ( f"---\nid: {_uuid(i)}\ntitle: M{i}\n---\nbody{i}") fake.vault[_vault_path("c5f-controlled-canary.md")] = ( "---\nid: " + UUID_CANARY + "\ntitle: Canary\n---\ncanary marker body") with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) ctx = builder.build("c1") self.assertTrue(ctx["ok"], ctx.get("reason")) # Delta: exakt 85 indexierbar, Canary in erwartetem Set self.assertIn(UUID_CANARY, ctx["expected"]["expected_object_ids"]) self.assertIn("c5f-controlled-canary.md", ctx["expected"]["expected_paths"]) self.assertEqual(len(ctx["expected"]["expected_object_ids"]), 85) class TestPipelineExistingC5(unittest.TestCase): """M. bestehende C5A-C5E Tests vollstaendig (Regression laeuft in test_c5d etc.).""" def test_M_guards_still_pass(self): self.assertTrue(assert_no_tolaria_write()) self.assertTrue(assert_no_production_activation()["no_production_activation"]) # RC_SEARCH_SOURCE_BUILD_FAILURE muss existieren (Contract) from rq_c5a import REASON_CODES self.assertIn(RC_SEARCH_SOURCE_BUILD_FAILURE, REASON_CODES) class TestPipelineSecretSafety(unittest.TestCase): """N. Secret Safety.""" def test_N_secret_blocked(self): fake = FakeTolaria() fake.vault[_vault_path("secure.md")] = ( "---\nid: " + _uuid(9001) + "\ntitle: Secure\n---\n" "api_key = sk-abcdefghijklmnopqrstuvwxyz0123456789\n") with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "source.json") builder = _make_builder(fake, src) ctx = builder.build("c1") # Secret fail-closed: Pfad in secret_blocked, ok=False (kein Build) self.assertIn("secure.md", ctx["checks"]["secret_blocked"]) self.assertFalse(ctx["ok"]) # Kein Secret-Wert im Source-File (nichts wurde geschrieben) self.assertFalse(os.path.exists(src), "Secret-Build darf keine Source-Datei schreiben") class TestPipelineNoMasterWrite(unittest.TestCase): """O. No-Master-Write Guarantee.""" def test_O_no_master_write(self): self.assertTrue(assert_no_tolaria_write()) self.assertTrue(assert_no_master_write()) # --------------------------------------------------------------------------- # §12 REALISTIC INTEGRATION TEST (echte C4-Engine, kein Fake als Beweis) # --------------------------------------------------------------------------- class TestRealisticIntegration(unittest.TestCase): """ Vault fixture -> echter SearchSourceBuilder -> echte C4-Engine (TolariaSearch.rebuild_from_source) -> echte Search Query/Discovery. Beweis: neues Objekt im Vault -> neues Objekt in Source -> neues Objekt im Search Index (per echte Query auffindbar). KEIN hartkodierter Fake-Response als Acceptance-Beweis. """ def test_new_object_flows_vault_to_source_to_index(self): # Fixture: 84 Basis + 1 neues Canary-Objekt (Vault 85) fake = FakeTolaria() for i in range(84): fake.vault[_vault_path(f"modul-{i:02d}.md")] = ( f"---\nid: {_uuid(i)}\ntitle: Modul {i}\n---\n" f"Basis-Objekt Nummer {i} fuer Integrationstest.") fake.vault[_vault_path("c5f-controlled-canary.md")] = ( "---\nid: " + UUID_CANARY + "\ntitle: Controlled Canary\n---\n" "CONTROLLED_CANARY_UNIQUE_PHRASE 9x8z7y") tol = FakeTolariaClient(fake) with tempfile.TemporaryDirectory() as d: src = os.path.join(d, "index_source.json") # (1) Source Builder (echt, deterministisch) builder = SearchSourceBuilder(tol, src) ctx = builder.build("abc123def") self.assertTrue(ctx["ok"], ctx.get("reason")) self.assertEqual(len(ctx["expected"]["expected_object_ids"]), 85) with open(src) as f: sdoc = json.load(f) self.assertEqual(sdoc["count"], 85) self.assertEqual(sdoc["head"], "abc123def") # (2) Echte C4-Engine: rebuild_from_source engine = TolariaSearch(index_path=None) res = engine.rebuild_from_source(src, head="abc123def") self.assertEqual(res["indexed"], 85) self.assertEqual(engine.source_head, "abc123def") health = engine.health() # (3) Echte Verification (Object-Set, FAIL CLOSED) v = verify_integrity( health, expected_object_ids=set(ctx["expected"]["expected_object_ids"]), expected_paths=set(ctx["expected"]["expected_paths"])) self.assertTrue(v["ok"], v.get("reason")) self.assertTrue(v["checks"]["object_ids_exact"]) self.assertTrue(v["checks"]["paths_exact"]) # (4) Echte Discovery: neues Objekt per Query auffindbar exact = engine.search({"mode": "exact", "query": "CONTROLLED_CANARY_UNIQUE_PHRASE"}) self.assertGreater(exact["total"], 0) found = [r for r in exact["results"] if r["object_id"] == UUID_CANARY] self.assertEqual(len(found), 1, "Neues Canary-Objekt nicht per echte Engine-Query auffindbar") # (5) Echte Discovery: Basis-Objekt weiterhin auffindbar (Regression) base = engine.search({"mode": "keyword", "query": "Integrationstest"}) self.assertGreater(base["total"], 0) if __name__ == "__main__": unittest.main()