Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
302 lines
10 KiB
Python
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
|