""" Integration Tests für LibreChat + Tavily Web Search. Diese Tests prüfen: 1. Tavily API Konnektivität 2. LibreChat Container Health 3. End-to-End Web Search Flow """ import os import pytest import httpx from unittest.mock import patch, AsyncMock # Test-Konfiguration TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "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") LIBRECHAT_URL = os.getenv("LIBRECHAT_URL", "http://localhost:3080") TAVILY_API_URL = "https://api.tavily.com" class TestTavilyAPIConnectivity: """Tests für direkte Tavily API Verbindung.""" @pytest.mark.asyncio async def test_tavily_api_health(self): """Test: Tavily API ist erreichbar und antwortet.""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": TAVILY_API_KEY, "query": "test query", "max_results": 1 } ) assert response.status_code == 200 data = response.json() assert "results" in data assert "query" in data @pytest.mark.asyncio async def test_tavily_search_returns_results(self): """Test: Tavily gibt Suchergebnisse zurück.""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": TAVILY_API_KEY, "query": "LibreChat AI chat platform", "max_results": 3 } ) assert response.status_code == 200 data = response.json() # Prüfe Struktur assert "results" in data assert len(data["results"]) > 0 # Prüfe erstes Ergebnis first_result = data["results"][0] assert "url" in first_result assert "title" in first_result assert "content" in first_result assert "score" in first_result @pytest.mark.asyncio async def test_tavily_invalid_api_key(self): """Test: Tavily gibt Fehler bei ungültigem API Key.""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": "invalid-key-12345", "query": "test", "max_results": 1 } ) # Sollte 401 oder 403 zurückgeben assert response.status_code in [401, 403, 400] @pytest.mark.asyncio async def test_tavily_search_depth_basic(self): """Test: Tavily basic search depth funktioniert.""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": TAVILY_API_KEY, "query": "Python programming", "search_depth": "basic", "max_results": 2 } ) assert response.status_code == 200 data = response.json() assert "response_time" in data @pytest.mark.asyncio async def test_tavily_german_query(self): """Test: Tavily kann deutsche Suchanfragen verarbeiten.""" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": TAVILY_API_KEY, "query": "Datenschutz Schulen Deutschland", "max_results": 3 } ) assert response.status_code == 200 data = response.json() assert len(data["results"]) > 0 class TestLibreChatHealth: """Tests für LibreChat Container Health.""" @pytest.mark.asyncio async def test_librechat_api_health(self): """Test: LibreChat API ist erreichbar.""" async with httpx.AsyncClient(timeout=10.0) as client: try: response = await client.get(f"{LIBRECHAT_URL}/api/health") # LibreChat hat keinen /api/health, aber / sollte funktionieren if response.status_code == 404: response = await client.get(f"{LIBRECHAT_URL}/") assert response.status_code in [200, 301, 302] except httpx.ConnectError: pytest.skip("LibreChat Container nicht erreichbar") @pytest.mark.asyncio async def test_librechat_frontend_loads(self): """Test: LibreChat Frontend lädt.""" async with httpx.AsyncClient(timeout=10.0) as client: try: response = await client.get(f"{LIBRECHAT_URL}/") assert response.status_code in [200, 301, 302] # Prüfe ob HTML zurückkommt if response.status_code == 200: assert "html" in response.headers.get("content-type", "").lower() or \ " 30, \ f"Tavily API Key zu kurz: {len(TAVILY_API_KEY)} Zeichen" def test_tavily_api_key_not_placeholder(self): """Test: Tavily API Key ist kein Platzhalter.""" placeholders = [ "your-tavily-api-key", "TAVILY_API_KEY", "tvly-xxx", "tvly-placeholder", ] assert TAVILY_API_KEY not in placeholders, \ "Tavily API Key ist noch ein Platzhalter" class TestBreakPilotTavilyIntegration: """Tests für BreakPilot Backend Tavily Integration.""" @pytest.mark.asyncio async def test_breakpilot_tool_gateway_available(self): """Test: BreakPilot Tool Gateway ist verfügbar.""" from llm_gateway.services.tool_gateway import ToolGateway, ToolGatewayConfig config = ToolGatewayConfig(tavily_api_key=TAVILY_API_KEY) gateway = ToolGateway(config) assert gateway.tavily_available is True @pytest.mark.asyncio async def test_breakpilot_pii_redaction_before_tavily(self): """Test: PII wird vor Tavily-Anfragen redaktiert.""" from llm_gateway.services.pii_detector import PIIDetector detector = PIIDetector() # Text mit PII query_with_pii = "Suche Informationen über max.mustermann@schule.de in Klasse 5a" result = detector.redact(query_with_pii) # PII sollte redaktiert sein assert "max.mustermann@schule.de" not in result.redacted_text assert result.pii_found is True assert len(result.matches) > 0 # E-Mail sollte als [EMAIL_REDACTED] redaktiert sein assert "[EMAIL_REDACTED]" in result.redacted_text @pytest.mark.asyncio async def test_breakpilot_tavily_search_with_pii_protection(self): """Test: Tavily Search mit PII-Schutz funktioniert.""" from llm_gateway.services.tool_gateway import ToolGateway, ToolGatewayConfig config = ToolGatewayConfig(tavily_api_key=TAVILY_API_KEY) gateway = ToolGateway(config) # Suche mit PII (wird automatisch redaktiert) result = await gateway.search( query="Datenschutz Email hans.mueller@example.com", max_results=2 ) # Wichtig: PII-Schutz hat funktioniert assert result is not None assert result.pii_detected is True assert "email" in result.pii_types assert result.redacted_query is not None assert "hans.mueller@example.com" not in result.redacted_query assert "[EMAIL_REDACTED]" in result.redacted_query # Ergebnisse sind optional - die redaktierte Query kann leer sein assert result.results is not None # Liste existiert (kann leer sein) class TestEndToEndFlow: """End-to-End Tests für den kompletten Flow.""" @pytest.mark.asyncio async def test_complete_search_flow(self): """Test: Kompletter Such-Flow von Anfrage bis Ergebnis.""" # 1. Tavily API direkt async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": TAVILY_API_KEY, "query": "Schulrecht Deutschland aktuelle Änderungen", "max_results": 3, "search_depth": "basic" } ) assert response.status_code == 200 data = response.json() # Validiere Ergebnisse assert len(data["results"]) > 0 for result in data["results"]: assert "url" in result assert "title" in result assert result["url"].startswith("http") @pytest.mark.asyncio async def test_search_response_time(self): """Test: Tavily antwortet in akzeptabler Zeit.""" import time async with httpx.AsyncClient(timeout=30.0) as client: start = time.time() response = await client.post( f"{TAVILY_API_URL}/search", json={ "api_key": TAVILY_API_KEY, "query": "test query", "max_results": 3 } ) elapsed = time.time() - start assert response.status_code == 200 # Sollte unter 10 Sekunden antworten assert elapsed < 10.0, f"Tavily Antwortzeit zu lang: {elapsed:.2f}s" # Fixtures für gemeinsame Test-Ressourcen @pytest.fixture def tavily_api_key(): """Fixture für Tavily API Key.""" return TAVILY_API_KEY @pytest.fixture def librechat_url(): """Fixture für LibreChat URL.""" return LIBRECHAT_URL