# tests/test_final_wp15_validation.py import asyncio import unittest from typing import List, Dict, Any import re from pathlib import Path import sys # --- PFAD-KORREKTUR --- ROOT_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT_DIR)) from app.core import chunker from app.services.semantic_analyzer import SemanticAnalyzer def get_config_for_test(strategy: str, enable_smart_edge: bool) -> Dict[str, Any]: cfg = chunker.get_chunk_config("concept") cfg['strategy'] = strategy cfg['enable_smart_edge_allocation'] = enable_smart_edge # WICHTIG: Setze sehr kleine Werte, um Split bei kurzem Text zu erzwingen cfg['target'] = 50 cfg['max'] = 100 return cfg TEST_NOTE_ID_SMART = "20251212-test-smart" TEST_NOTE_ID_LEGACY = "20251212-test-legacy" TEST_MARKDOWN_SMART = """ --- id: 20251212-test-smart title: Integrationstest - Smart Edges type: concept status: active --- # Teil 1: Wichtige Definition Die Mission ist: präsent sein. Dies entspricht unseren Werten [[leitbild-werte#Integrität]]. ## Teil 2: Konflikt Der Konflikt zwischen [[leitbild-rollen#Vater]] und [[leitbild-rollen#Beruf]]. Lösung: [[rel:depends_on leitbild-review#Weekly Review]]. """ # Verlängerter Text, um Split > 1 zu erzwingen (bei Target 50) TEST_MARKDOWN_SLIDING = """ --- id: 20251212-test-legacy title: Fließtext Protokoll type: journal status: active --- Dies ist der erste Absatz. Er muss lang genug sein, damit der Chunker ihn schneidet. Wir schreiben hier über Rituale wie [[leitbild-rituale-system]] und viele andere Dinge. Das Wetter ist schön und die Programmierung läuft gut. Dies sind Füllsätze für Länge. Dies ist der zweite Absatz. Er ist durch eine Leerzeile getrennt und sollte einen neuen Kontext bilden. Auch hier schreiben wir viel Text, damit die Token-Anzahl die Grenze von 50 Tokens überschreitet. Das System muss hier splitten. """ class TestFinalWP15Integration(unittest.TestCase): _analyzer_instance = None @classmethod def setUpClass(cls): cls._analyzer_instance = SemanticAnalyzer() chunker._semantic_analyzer_instance = cls._analyzer_instance @classmethod def tearDownClass(cls): # FIX: Kein explizites Loop-Closing hier, um RuntimeError zu vermeiden pass def test_a_smart_edge_allocation(self): """A: Prüft Smart Edge Allocation (LLM-Filter).""" config = get_config_for_test('by_heading', enable_smart_edge=True) chunks = asyncio.run(chunker.assemble_chunks( note_id=TEST_NOTE_ID_SMART, md_text=TEST_MARKDOWN_SMART, note_type='concept', config=config )) self.assertTrue(len(chunks) >= 2, f"A1 Fehler: Erwartete >= 2 Chunks, bekam {len(chunks)}") print(f" -> Chunks generiert (Smart): {len(chunks)}") def test_b_backward_compatibility(self): """B: Prüft Sliding Window (Legacy).""" config = get_config_for_test('sliding_window', enable_smart_edge=False) chunks = asyncio.run(chunker.assemble_chunks( note_id=TEST_NOTE_ID_LEGACY, md_text=TEST_MARKDOWN_SLIDING, note_type='journal', config=config )) # Sliding Window muss bei diesem langen Text > 1 Chunk liefern self.assertTrue(len(chunks) >= 2, f"B1 Fehler: Sliding Window lieferte nur {len(chunks)} Chunk(s). Split defekt.") # Check: Keine LLM Kanten (da deaktiviert) injected = re.search(r'\[\[rel:', chunks[0].text) self.assertIsNone(injected, "B2 Fehler: LLM-Kanten trotz Deaktivierung gefunden!") print(f" -> Chunks generiert (Legacy): {len(chunks)}") if __name__ == '__main__': unittest.main()