diff --git a/tests/test_smart_chunking_integration.py b/tests/test_smart_chunking_integration.py index fcb167f..d1f67bc 100644 --- a/tests/test_smart_chunking_integration.py +++ b/tests/test_smart_chunking_integration.py @@ -1,4 +1,4 @@ -# tests/test_smart_chunking_integration.py +# tests/test_smart_chunking_integration.py (Final für Stabilität) import asyncio import unittest @@ -6,9 +6,9 @@ import os import sys from pathlib import Path from typing import List, Dict +import time # Nur für debug/sleep # --- PFAD-KORREKTUR --- -# Fügt das Root-Verzeichnis zum Python-Pfad hinzu ROOT_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT_DIR)) # ---------------------- @@ -16,8 +16,8 @@ sys.path.insert(0, str(ROOT_DIR)) # Import der Kernkomponenten from app.core import chunker from app.core import derive_edges -from app.services.semantic_analyzer import SemanticAnalyzer # Import der Klasse für die Instanziierung - +from app.services.semantic_analyzer import SemanticAnalyzer +from app.core.chunker import extract_frontmatter_from_text # Wichtig für Status Check # 1. Definieren der Test-Note (Simuliert eine journal.md Datei) TEST_NOTE_ID = "20251211-journal-sem-test" @@ -41,9 +41,13 @@ Am Nachmittag gab es einen Konflikt bei der Karate-Trainer-Ausbildung. Ein Schü Abends habe ich den wöchentlichen Load-Check mit meinem Partner gemacht. Das Paar-Ritual [[leitbild-rituale-system#R5]] hilft, das Ziel [[leitbild-ziele-portfolio#Nordstern Partner]] aktiv zu verfolgen. Es ist der operative Rhythmus für uns beide. """ +# Helper, um den Client im vorhandenen Loop zu schließen (Muss in der Klasse sein) +async def _async_close_analyzer(analyzer): + await analyzer.close() + class TestSemanticChunking(unittest.TestCase): - # 2. Ressourcen-Management (Schließt den httpx.AsyncClient sauber) + # 2. Ressourcen-Management _analyzer_instance = None @classmethod @@ -51,18 +55,24 @@ class TestSemanticChunking(unittest.TestCase): """Initialisiert den SemanticAnalyzer einmalig und asynchron.""" # Da LLMService async ist, nutzen wir die Singleton-Instanz der Klasse cls._analyzer_instance = SemanticAnalyzer() - # Stellen Sie sicher, dass der Chunker diese Instanz verwenden kann. - # Dies ist im chunker.py Code über _get_semantic_analyzer_instance() abgedeckt. + # Stellt sicher, dass der Chunker diese Instanz verwenden kann. chunker._semantic_analyzer_instance = cls._analyzer_instance @classmethod def tearDownClass(cls): - """Schließt den httpx.AsyncClient nach allen Tests.""" + """Schließt den httpx.AsyncClient nach allen Tests (Korrektur des Event Loop Fehlers).""" if cls._analyzer_instance: - asyncio.run(cls._analyzer_instance.close()) + # Wir nutzen run_coroutine_threadsafe, da der Loop geschlossen ist + loop = asyncio.get_event_loop() + if loop.is_running(): + # Wenn Loop noch läuft, planen wir den Aufruf + asyncio.run_coroutine_threadsafe(cls._analyzer_instance.close(), loop) + else: + # Andernfalls starten wir einen neuen temporären Loop nur zum Schließen + asyncio.run(cls._analyzer_instance.close()) + def setUp(self): - # Lädt die Konfiguration, um die Strategie zu prüfen self.config = chunker.get_chunk_config(TEST_NOTE_TYPE) def test_a_strategy_selection(self): @@ -72,10 +82,8 @@ class TestSemanticChunking(unittest.TestCase): def test_b_llm_chunking_and_injection(self): """ - Prüft den gesamten End-to-End-Flow: - 1. LLM-Chunking (muss > 1 Chunk liefern) - 2. Kanten-Injektion (als [[rel:...]]) - 3. Kanten-Erkennung durch derive_edges.py + Prüft den gesamten End-to-End-Flow: 1. LLM-Chunking, 2. Kanten-Injektion, 3. Kanten-Erkennung. + (Diese Tests setzen voraus, dass das LLM JSON liefert) """ # --- 1. Chunking (Asynchron) --- @@ -87,11 +95,11 @@ class TestSemanticChunking(unittest.TestCase): print(f"\n--- LLM Chunker Output: {len(chunks)} Chunks ---") - # Assertion B1: Zerlegung (Die Fallback-Logik des LLM liefert bei Fehler 1 Chunk) + # Assertion B1: Zerlegung (Das LLM muss mehr als 1 Chunk liefern) self.assertTrue(len(chunks) > 1, - "Assertion B1 Fehler: Das LLM sollte den Text in mehrere semantische Chunks zerlegen.") + "Assertion B1 Fehler: LLM hat nicht zerlegt (Fallback aktiv). Prüfe LLM-Stabilität.") - # --- 2. Injektion prüfen (Der Chunk-Text muss die Links enthalten) --- + # --- 2. Injektion prüfen --- chunk_1_text = chunks[0].text self.assertIn("[[rel:", chunk_1_text, "Assertion B2 Fehler: Der Chunk-Text muss die injizierte [[rel: Kante enthalten.") @@ -104,18 +112,16 @@ class TestSemanticChunking(unittest.TestCase): print(f"--- Edge Derivation Output: {len(edges)} Kanten ---") - # 4. Assertions: Prüfen auf Existenz spezifischer, vom LLM generierter Kanten + # Assertion B3: Mindestens 3 LLM-Kanten (inline:rel) llm_generated_edges = [ e for e in edges if e.get('rule_id') == 'inline:rel' and e.get('source_id').startswith(TEST_NOTE_ID + '#sem') ] - # Assertion B3: Mindestens 3 LLM-Kanten (eine pro semantischem Abschnitt) self.assertTrue(len(llm_generated_edges) >= 3, - "Assertion B3 Fehler: Mindestens 3 LLM-generierte Kanten (eine pro semantischem Abschnitt).") + "Assertion B3 Fehler: Es wurden weniger als 3 semantische Kanten gefunden.") - # Assertion B4: Check für die Matrix-Logik / Werte-Kante (Chunk 1) - # Erwartet: derived_from oder based_on zu 'leitbild-werte' + # Assertion B4: Check für die Matrix-Logik / Werte-Kante has_matrix_kante = any( e['target_id'].startswith('leitbild-werte') and e['kind'] in ['based_on', 'derived_from'] for e in llm_generated_edges @@ -149,5 +155,5 @@ class TestSemanticChunking(unittest.TestCase): print(f"\n✅ Prevention Test: Draft-Status hat LLM-Chunking verhindert (Fallback ID: {chunks[0].id}).") if __name__ == '__main__': - print("Starte den Semantic Chunking Integrationstest. Stelle sicher, dass Ollama und die Konfiguration korrekt sind.") + print("Starte den Semantic Chunking Integrationstest.") unittest.main() \ No newline at end of file