2025-12-12 08:47:12 +01:00
# tests/test_smart_chunking_integration.py
import asyncio
import unittest
import os
import sys
from pathlib import Path
from typing import List , Dict
2025-12-12 08:59:08 +01:00
# --- PFAD-KORREKTUR ---
2025-12-12 09:07:33 +01:00
# Fügt das Root-Verzeichnis zum Python-Pfad hinzu
2025-12-12 08:59:08 +01:00
ROOT_DIR = Path ( __file__ ) . resolve ( ) . parent . parent
sys . path . insert ( 0 , str ( ROOT_DIR ) )
# ----------------------
2025-12-12 08:47:12 +01:00
# Import der Kernkomponenten
from app . core import chunker
from app . core import derive_edges
2025-12-12 09:07:33 +01:00
from app . services . semantic_analyzer import SemanticAnalyzer # Import der Klasse für die Instanziierung
2025-12-12 08:47:12 +01:00
2025-12-12 08:59:08 +01:00
2025-12-12 09:07:33 +01:00
# 1. Definieren der Test-Note (Simuliert eine journal.md Datei)
2025-12-12 08:47:12 +01:00
TEST_NOTE_ID = " 20251211-journal-sem-test "
TEST_NOTE_TYPE = " journal "
TEST_MARKDOWN = """
- - -
id : 20251211 - journal - sem - test
title : Tägliches Log - Semantischer Test
type : journal
status : active
created : 2025 - 12 - 11
tags : [ test , daily - log ]
- - -
# Log-Eintrag 2025-12-11
Heute war ein guter Tag . Zuerst habe ich mit der R1 Meditation begonnen , um meinen Nordstern Fokus zu klären . Das Ritual [ [ leitbild - rituale - system ] ] hat mir geholfen , ruhig in den Tag zu starten . Ich habe gespürt , wie wichtig meine [ [ leitbild - werte #Integrität]] für meine Entscheidungen ist. Das ist das Fundament.
Am Nachmittag gab es einen Konflikt bei der Karate - Trainer - Ausbildung . Ein Schüler war uneinsichtig . Ich habe die Situation nach [ [ leitbild - prinzipien #P4 Gerechtigkeit & Fairness]] behandelt und beide Seiten gehört (Steelman). Das war anstrengend, aber ich habe meine [[leitbild-rollen#Karate-Trainer]] Mission erfüllt. Die Konsequenz war klar und ruhig.
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.
"""
class TestSemanticChunking ( unittest . TestCase ) :
2025-12-12 09:07:33 +01:00
# 2. Ressourcen-Management (Schließt den httpx.AsyncClient sauber)
_analyzer_instance = None
@classmethod
def setUpClass ( cls ) :
""" 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.
chunker . _semantic_analyzer_instance = cls . _analyzer_instance
@classmethod
def tearDownClass ( cls ) :
""" Schließt den httpx.AsyncClient nach allen Tests. """
if cls . _analyzer_instance :
asyncio . run ( cls . _analyzer_instance . close ( ) )
2025-12-12 08:47:12 +01:00
def setUp ( self ) :
2025-12-12 09:07:33 +01:00
# Lädt die Konfiguration, um die Strategie zu prüfen
2025-12-12 08:47:12 +01:00
self . config = chunker . get_chunk_config ( TEST_NOTE_TYPE )
def test_a_strategy_selection ( self ) :
""" Prüft, ob die Strategie ' semantic_llm ' für den Typ ' journal ' gewählt wird. """
self . assertEqual ( self . config [ ' strategy ' ] , ' semantic_llm ' ,
" Fehler: ' journal ' sollte die Strategie ' semantic_llm ' nutzen. " )
def test_b_llm_chunking_and_injection ( self ) :
"""
Prüft den gesamten End - to - End - Flow :
2025-12-12 09:07:33 +01:00
1. LLM - Chunking ( muss > 1 Chunk liefern )
2025-12-12 08:47:12 +01:00
2. Kanten - Injektion ( als [ [ rel : . . . ] ] )
3. Kanten - Erkennung durch derive_edges . py
"""
2025-12-12 09:07:33 +01:00
2025-12-12 08:47:12 +01:00
# --- 1. Chunking (Asynchron) ---
chunks = asyncio . run ( chunker . assemble_chunks (
note_id = TEST_NOTE_ID ,
md_text = TEST_MARKDOWN ,
note_type = TEST_NOTE_TYPE
) )
print ( f " \n --- LLM Chunker Output: { len ( chunks ) } Chunks --- " )
2025-12-12 09:07:33 +01:00
# Assertion B1: Zerlegung (Die Fallback-Logik des LLM liefert bei Fehler 1 Chunk)
2025-12-12 08:47:12 +01:00
self . assertTrue ( len ( chunks ) > 1 ,
2025-12-12 09:07:33 +01:00
" Assertion B1 Fehler: Das LLM sollte den Text in mehrere semantische Chunks zerlegen. " )
2025-12-12 08:47:12 +01:00
# --- 2. Injektion prüfen (Der Chunk-Text muss die Links enthalten) ---
chunk_1_text = chunks [ 0 ] . text
self . assertIn ( " [[rel: " , chunk_1_text ,
2025-12-12 09:07:33 +01:00
" Assertion B2 Fehler: Der Chunk-Text muss die injizierte [[rel: Kante enthalten. " )
2025-12-12 08:47:12 +01:00
# --- 3. Kanten-Derivation (Synchron) ---
edges = derive_edges . build_edges_for_note (
note_id = TEST_NOTE_ID ,
2025-12-12 09:07:33 +01:00
chunks = [ c . __dict__ for c in chunks ]
2025-12-12 08:47:12 +01:00
)
print ( f " --- Edge Derivation Output: { len ( edges ) } Kanten --- " )
# 4. Assertions: Prüfen auf Existenz spezifischer, vom LLM generierter Kanten
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 ' )
]
2025-12-12 09:07:33 +01:00
# Assertion B3: Mindestens 3 LLM-Kanten (eine pro semantischem Abschnitt)
2025-12-12 08:47:12 +01:00
self . assertTrue ( len ( llm_generated_edges ) > = 3 ,
2025-12-12 09:07:33 +01:00
" Assertion B3 Fehler: Mindestens 3 LLM-generierte Kanten (eine pro semantischem Abschnitt). " )
2025-12-12 08:47:12 +01:00
2025-12-12 09:07:33 +01:00
# Assertion B4: Check für die Matrix-Logik / Werte-Kante (Chunk 1)
# Erwartet: derived_from oder based_on zu 'leitbild-werte'
2025-12-12 08:47:12 +01:00
has_matrix_kante = any (
2025-12-12 09:07:33 +01:00
e [ ' target_id ' ] . startswith ( ' leitbild-werte ' ) and e [ ' kind ' ] in [ ' based_on ' , ' derived_from ' ]
2025-12-12 08:47:12 +01:00
for e in llm_generated_edges
)
self . assertTrue ( has_matrix_kante ,
2025-12-12 09:07:33 +01:00
" Assertion B4 Fehler: Die Matrix-Logik / Werte-Kante wurde nicht erkannt. " )
2025-12-12 08:47:12 +01:00
print ( " \n ✅ Integrationstest für Semantic Chunking erfolgreich. " )
def test_c_draft_status_prevention ( self ) :
""" Prüft, ob ' draft ' Status semantic_llm auf by_heading überschreibt. """
DRAFT_MARKDOWN = TEST_MARKDOWN . replace ( " status: active " , " status: draft " )
# 1. Chunking mit Draft Status
chunks = asyncio . run ( chunker . assemble_chunks (
note_id = TEST_NOTE_ID ,
md_text = DRAFT_MARKDOWN ,
note_type = TEST_NOTE_TYPE
) )
# 2. Prüfen der Chunker-IDs
2025-12-12 09:07:33 +01:00
# Assertion C1: LLM-Chunking muss verhindert werden (darf NICHT mit '#sem' starten)
2025-12-12 08:47:12 +01:00
self . assertFalse ( chunks [ 0 ] . id . startswith ( TEST_NOTE_ID + ' #sem ' ) ,
2025-12-12 09:07:33 +01:00
" Assertion C1 Fehler: LLM-Chunking wurde für den Status ' draft ' nicht verhindert. " )
2025-12-12 08:47:12 +01:00
2025-12-12 09:07:33 +01:00
# Assertion C2: Fallback-Strategie sollte by_heading sein (ID muss mit '#c' starten)
2025-12-12 08:59:08 +01:00
self . assertTrue ( chunks [ 0 ] . id . startswith ( TEST_NOTE_ID + ' #c ' ) ,
2025-12-12 09:07:33 +01:00
" Assertion C2 Fehler: Fallback-Strategie ' by_heading ' wurde nicht korrekt ausgeführt. " )
2025-12-12 08:47:12 +01:00
2025-12-12 08:59:08 +01:00
print ( f " \n ✅ Prevention Test: Draft-Status hat LLM-Chunking verhindert (Fallback ID: { chunks [ 0 ] . id } ). " )
2025-12-12 09:07:33 +01:00
if __name__ == ' __main__ ' :
print ( " Starte den Semantic Chunking Integrationstest. Stelle sicher, dass Ollama und die Konfiguration korrekt sind. " )
unittest . main ( )