This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/tests/test_integration/test_librechat_tavily.py
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

302 lines
10 KiB
Python

"""
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 \
"<!DOCTYPE" in response.text[:100]
except httpx.ConnectError:
pytest.skip("LibreChat Container nicht erreichbar")
class TestTavilyConfigValidation:
"""Tests für Tavily Konfigurationsvalidierung."""
def test_tavily_api_key_format(self):
"""Test: Tavily API Key hat korrektes Format."""
# Tavily Keys beginnen mit "tvly-"
assert TAVILY_API_KEY.startswith("tvly-"), \
f"Tavily API Key sollte mit 'tvly-' beginnen, ist aber: {TAVILY_API_KEY[:10]}..."
def test_tavily_api_key_length(self):
"""Test: Tavily API Key hat korrekte Länge."""
# Tavily Keys sind typischerweise ~40 Zeichen
assert len(TAVILY_API_KEY) > 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