# tests/test_final_wp15_validation.py import asyncio import unittest import os import sys from pathlib import Path from typing import List, Dict, Any # --- PFAD-KORREKTUR --- ROOT_DIR = Path(__file__).resolve().parent.parent 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 # 1. Hilfsfunktion zur Manipulation der Konfiguration im Test def get_config_for_test(strategy: str, enable_smart_edge: bool) -> Dict[str, Any]: """Erzeugt eine ad-hoc Konfiguration, um eine Strategie zu erzwingen.""" cfg = chunker.get_chunk_config("concept") # Nutze eine Basis cfg['strategy'] = strategy cfg['enable_smart_edge_allocation'] = enable_smart_edge return cfg # 2. Test-Daten (Muss die Entitäten aus den Vault-Dateien verwenden) TEST_NOTE_ID = "20251212-test-integration" TEST_NOTE_TYPE = "concept" # Kann eine beliebige Basis sein # Text, der die Matrix-Logik und Header triggert TEST_MARKDOWN_SMART = """ --- id: 20251212-test-integration title: Integrationstest - Smart Edges type: concept status: active --- # Teil 1: Intro Dies ist die Einleitung. Wir definieren unsere Mission: Präsent sein und vorleben. Dies entspricht unseren Werten [[leitbild-werte#Integrität]] und [[leitbild-werte#Respekt]]. ## Teil 2: Rollenkonflikt Der Konflikt zwischen [[leitbild-rollen#Vater]] und [[leitbild-rollen#Berufsrolle (Umbrella)]] muss gelöst werden. Die Lösung muss [[rel:depends_on leitbild-review#Weekly Review]]. """ # Text, der nur für Sliding Window geeignet ist TEST_MARKDOWN_SLIDING = """ --- id: 20251212-test-sliding title: Fließtext Protokoll type: journal status: active --- Dies ist ein langer Fließtextabschnitt, der ohne Header auskommt. Er spricht über die neue [[leitbild-prinzipien#P1 Integrität]] Regel und den Ablauf des Tages. Das sollte in zwei Chunks zerlegt werden. """ # 3. Testklasse class TestFinalWP15Integration(unittest.TestCase): # Initiale Ressourcen-Verwaltung (um den AsyncClient zu schließen) _analyzer_instance = None @classmethod def setUpClass(cls): cls._analyzer_instance = SemanticAnalyzer() chunker._semantic_analyzer_instance = cls._analyzer_instance @classmethod def tearDownClass(cls): if cls._analyzer_instance: # Nutzt die temporäre Loop-Lösung loop = asyncio.get_event_loop() loop.run_until_complete(cls._analyzer_instance.close()) # --- A. Smart Edge Allocation Test --- def test_a_smart_edge_allocation(self): """Prüft die neue LLM-Orchestrierung (5 Schritte) und die Kanten-Bindung.""" config = get_config_for_test('by_heading', enable_smart_edge=True) # 1. Chunking (Asynchroner Aufruf der neuen Orchestrierung) chunks = asyncio.run(chunker.assemble_chunks( note_id=TEST_NOTE_ID, md_text=TEST_MARKDOWN_SMART, note_type=TEST_NOTE_TYPE, config=config # Übergibt die ad-hoc Konfiguration (Annahme: assemble_chunks akzeptiert kwargs) )) # NOTE: Da assemble_chunks die config intern lädt, müssten wir hier idealerweise # die types.yaml zur Laufzeit manipulieren oder die config in kwargs übergeben (letzteres ist hier angenommen). # 2. Grundlegende Checks self.assertTrue(len(chunks) >= 2, "A1 Fehler: Primärzerlegung (by_heading) muss mindestens 2 Chunks liefern.") # 3. Kanten-Checks (durch derive_edges.py im Chunker ausgelöst) # Wir suchen nach der LLM-generierten, spezifischen Kante # Erwartet: Chunk 1/2 enthält die Kante 'derived_from' oder 'based_on' zu 'leitbild-werte'. all_edges = [] for c in chunks: # Um die Kanten zu erhalten, muss derive_edges manuell aufgerufen werden, # da der Chunker nur den Text injiziert. # Im echten Importer würde build_edges_for_note auf den injizierten Text angewendet. # Hier simulieren wir den Endeffekt, indem wir die injizierten Kanten prüfen: if "suggested_edges" in c.__dict__: all_edges.extend(c.suggested_edges) has_matrix_kante = any("based_on:leitbild-werte" in e or "derived_from:leitbild-werte" in e for e in all_edges) self.assertTrue(has_matrix_kante, "A2 Fehler: LLM-Kantenfilter hat die Matrix-Logik (value -> based_on/derived_from) nicht angewendet oder erkannt.") print("\n✅ Test A: Smart Edge Allocation erfolgreich.") # --- B. Abwärtskompatibilität (Legacy Tests) --- def test_b_backward_compatibility(self): """Prüft, ob die alte, reine Sliding Window Strategie (ohne LLM-Filter) noch funktioniert.""" # Erzwinge das alte, reine Sliding Window Profil config = get_config_for_test('sliding_window', enable_smart_edge=False) # 1. Chunking (Sollte *mehrere* Chunks liefern, ohne LLM-Aufruf) # Die Orchestrierung sollte nur den reinen Sliding Window Call nutzen. chunks = asyncio.run(chunker.assemble_chunks( note_id=TEST_NOTE_ID, md_text=TEST_MARKDOWN_SLIDING, note_type='journal', config=config )) self.assertTrue(len(chunks) >= 2, "B1 Fehler: Reine Sliding Window Strategie ist fehlerhaft oder zerlegt nicht.") # 2. Prüfen auf Kanten-Injection (Dürfen NUR aus Wikilinks und Defaults kommen) # Die manuelle Wikilink [[leitbild-prinzipien#P1 Integrität]] sollte in JEDEM Chunk sein # wenn Defaults für journal aktiv sind, was falsch ist. # Im reinen Sliding Window Modus (ohne LLM) werden Kanten nur durch derive_edges.py erkannt. # Wir prüfen nur, dass die Chunks existieren. self.assertNotIn('suggested_edges', chunks[0].__dict__, "B2 Fehler: LLM-Kantenfilter wurde fälschlicherweise für enable_smart_edge=False ausgeführt.") print("\n✅ Test B: Abwärtskompatibilität (reines Sliding Window) erfolgreich.") if __name__ == '__main__': print("Startet den finalen WP-15 Validierungstest.") unittest.main()