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_comparison.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

378 lines
13 KiB
Python

"""
Tests fuer LLM Comparison Route.
"""
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from datetime import datetime
class TestComparisonModels:
"""Tests fuer die Pydantic Models."""
def test_comparison_request_defaults(self):
"""Test ComparisonRequest mit Default-Werten."""
from llm_gateway.routes.comparison import ComparisonRequest
req = ComparisonRequest(prompt="Test prompt")
assert req.prompt == "Test prompt"
assert req.system_prompt is None
assert req.enable_openai is True
assert req.enable_claude is True
assert req.enable_selfhosted_tavily is True
assert req.enable_selfhosted_edusearch is True
assert req.selfhosted_model == "llama3.2:3b"
assert req.temperature == 0.7
assert req.top_p == 0.9
assert req.max_tokens == 2048
assert req.search_results_count == 5
def test_comparison_request_custom_values(self):
"""Test ComparisonRequest mit benutzerdefinierten Werten."""
from llm_gateway.routes.comparison import ComparisonRequest
req = ComparisonRequest(
prompt="Custom prompt",
system_prompt="Du bist ein Experte",
enable_openai=False,
enable_claude=True,
enable_selfhosted_tavily=False,
enable_selfhosted_edusearch=True,
selfhosted_model="llama3.1:8b",
temperature=0.5,
top_p=0.8,
max_tokens=1024,
search_results_count=10,
edu_search_filters={"language": ["de"], "doc_type": ["Lehrplan"]},
)
assert req.prompt == "Custom prompt"
assert req.system_prompt == "Du bist ein Experte"
assert req.enable_openai is False
assert req.selfhosted_model == "llama3.1:8b"
assert req.temperature == 0.5
assert req.edu_search_filters == {"language": ["de"], "doc_type": ["Lehrplan"]}
def test_llm_response_model(self):
"""Test LLMResponse Model."""
from llm_gateway.routes.comparison import LLMResponse
response = LLMResponse(
provider="openai",
model="gpt-4o-mini",
response="Test response",
latency_ms=500,
tokens_used=100,
)
assert response.provider == "openai"
assert response.model == "gpt-4o-mini"
assert response.response == "Test response"
assert response.latency_ms == 500
assert response.tokens_used == 100
assert response.error is None
assert response.search_results is None
def test_llm_response_with_error(self):
"""Test LLMResponse mit Fehler."""
from llm_gateway.routes.comparison import LLMResponse
response = LLMResponse(
provider="claude",
model="claude-3-5-sonnet",
response="",
latency_ms=100,
error="API Key nicht konfiguriert",
)
assert response.error == "API Key nicht konfiguriert"
assert response.response == ""
def test_llm_response_with_search_results(self):
"""Test LLMResponse mit Suchergebnissen."""
from llm_gateway.routes.comparison import LLMResponse
search_results = [
{"title": "Lehrplan Mathe", "url": "https://example.com", "content": "..."},
{"title": "Bildungsstandards", "url": "https://kmk.org", "content": "..."},
]
response = LLMResponse(
provider="selfhosted_edusearch",
model="llama3.2:3b",
response="Antwort mit Quellen",
latency_ms=2000,
search_results=search_results,
)
assert len(response.search_results) == 2
assert response.search_results[0]["title"] == "Lehrplan Mathe"
class TestComparisonResponse:
"""Tests fuer ComparisonResponse."""
def test_comparison_response_structure(self):
"""Test ComparisonResponse Struktur."""
from llm_gateway.routes.comparison import ComparisonResponse, LLMResponse
responses = [
LLMResponse(
provider="openai",
model="gpt-4o-mini",
response="OpenAI Antwort",
latency_ms=400,
),
LLMResponse(
provider="claude",
model="claude-3-5-sonnet",
response="Claude Antwort",
latency_ms=600,
),
]
result = ComparisonResponse(
comparison_id="cmp-test123",
prompt="Was ist 1+1?",
system_prompt="Du bist ein Mathe-Lehrer",
responses=responses,
)
assert result.comparison_id == "cmp-test123"
assert result.prompt == "Was ist 1+1?"
assert result.system_prompt == "Du bist ein Mathe-Lehrer"
assert len(result.responses) == 2
assert result.responses[0].provider == "openai"
assert result.responses[1].provider == "claude"
class TestSystemPromptStore:
"""Tests fuer System Prompt Management."""
def test_default_system_prompts_exist(self):
"""Test dass Standard-Prompts existieren."""
from llm_gateway.routes.comparison import _system_prompts_store
assert "default" in _system_prompts_store
assert "curriculum" in _system_prompts_store
assert "worksheet" in _system_prompts_store
def test_default_prompt_structure(self):
"""Test Struktur der Standard-Prompts."""
from llm_gateway.routes.comparison import _system_prompts_store
default = _system_prompts_store["default"]
assert "id" in default
assert "name" in default
assert "prompt" in default
assert "created_at" in default
assert default["id"] == "default"
assert "Lehrer" in default["prompt"] or "Assistent" in default["prompt"]
class TestSearchFunctions:
"""Tests fuer Such-Funktionen."""
@pytest.mark.asyncio
async def test_search_tavily_no_api_key(self):
"""Test Tavily Suche ohne API Key."""
from llm_gateway.routes.comparison import _search_tavily
with patch.dict("os.environ", {}, clear=True):
results = await _search_tavily("test query", 5)
assert results == []
@pytest.mark.asyncio
async def test_search_edusearch_connection_error(self):
"""Test EduSearch bei Verbindungsfehler."""
from llm_gateway.routes.comparison import _search_edusearch
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
mock_instance.__aexit__ = AsyncMock(return_value=None)
mock_instance.post = AsyncMock(side_effect=Exception("Connection refused"))
mock_client.return_value = mock_instance
results = await _search_edusearch("test query", 5)
assert results == []
class TestLLMCalls:
"""Tests fuer LLM-Aufrufe."""
@pytest.mark.asyncio
async def test_call_openai_no_api_key(self):
"""Test OpenAI Aufruf ohne API Key."""
from llm_gateway.routes.comparison import _call_openai
with patch.dict("os.environ", {}, clear=True):
result = await _call_openai("Test prompt", None)
assert result.provider == "openai"
assert result.error is not None
assert "OPENAI_API_KEY" in result.error
@pytest.mark.asyncio
async def test_call_claude_no_api_key(self):
"""Test Claude Aufruf ohne API Key."""
from llm_gateway.routes.comparison import _call_claude
with patch.dict("os.environ", {}, clear=True):
result = await _call_claude("Test prompt", None)
assert result.provider == "claude"
assert result.error is not None
assert "ANTHROPIC_API_KEY" in result.error
class TestComparisonEndpoints:
"""Integration Tests fuer die API Endpoints."""
@pytest.fixture
def mock_verify_api_key(self):
"""Mock fuer API Key Verifizierung."""
with patch("llm_gateway.routes.comparison.verify_api_key") as mock:
mock.return_value = "test-user"
yield mock
@pytest.mark.asyncio
async def test_list_system_prompts(self, mock_verify_api_key):
"""Test GET /comparison/prompts."""
from llm_gateway.routes.comparison import list_system_prompts
result = await list_system_prompts(_="test-user")
assert "prompts" in result
assert len(result["prompts"]) >= 3 # default, curriculum, worksheet
@pytest.mark.asyncio
async def test_get_system_prompt(self, mock_verify_api_key):
"""Test GET /comparison/prompts/{prompt_id}."""
from llm_gateway.routes.comparison import get_system_prompt
result = await get_system_prompt("default", _="test-user")
assert result["id"] == "default"
assert "name" in result
assert "prompt" in result
@pytest.mark.asyncio
async def test_get_system_prompt_not_found(self, mock_verify_api_key):
"""Test GET /comparison/prompts/{prompt_id} mit unbekannter ID."""
from fastapi import HTTPException
from llm_gateway.routes.comparison import get_system_prompt
with pytest.raises(HTTPException) as exc_info:
await get_system_prompt("nonexistent-id", _="test-user")
assert exc_info.value.status_code == 404
@pytest.mark.asyncio
async def test_get_comparison_history(self, mock_verify_api_key):
"""Test GET /comparison/history."""
from llm_gateway.routes.comparison import get_comparison_history
result = await get_comparison_history(limit=10, _="test-user")
assert "comparisons" in result
assert isinstance(result["comparisons"], list)
class TestProviderMapping:
"""Tests fuer Provider-Label und -Color Mapping."""
def test_provider_labels(self):
"""Test dass alle Provider Labels haben."""
provider_labels = {
"openai": "OpenAI GPT-4o-mini",
"claude": "Claude 3.5 Sonnet",
"selfhosted_tavily": "Self-hosted + Tavily",
"selfhosted_edusearch": "Self-hosted + EduSearch",
}
for key, expected in provider_labels.items():
assert key in ["openai", "claude", "selfhosted_tavily", "selfhosted_edusearch"]
class TestParameterValidation:
"""Tests fuer Parameter-Validierung."""
def test_temperature_range(self):
"""Test Temperature Bereich 0-2."""
from llm_gateway.routes.comparison import ComparisonRequest
from pydantic import ValidationError
# Gueltige Werte
req = ComparisonRequest(prompt="test", temperature=0.0)
assert req.temperature == 0.0
req = ComparisonRequest(prompt="test", temperature=2.0)
assert req.temperature == 2.0
# Ungueltige Werte
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", temperature=-0.1)
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", temperature=2.1)
def test_top_p_range(self):
"""Test Top-P Bereich 0-1."""
from llm_gateway.routes.comparison import ComparisonRequest
from pydantic import ValidationError
# Gueltige Werte
req = ComparisonRequest(prompt="test", top_p=0.0)
assert req.top_p == 0.0
req = ComparisonRequest(prompt="test", top_p=1.0)
assert req.top_p == 1.0
# Ungueltige Werte
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", top_p=-0.1)
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", top_p=1.1)
def test_max_tokens_range(self):
"""Test Max Tokens Bereich 1-8192."""
from llm_gateway.routes.comparison import ComparisonRequest
from pydantic import ValidationError
# Gueltige Werte
req = ComparisonRequest(prompt="test", max_tokens=1)
assert req.max_tokens == 1
req = ComparisonRequest(prompt="test", max_tokens=8192)
assert req.max_tokens == 8192
# Ungueltige Werte
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", max_tokens=0)
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", max_tokens=8193)
def test_search_results_count_range(self):
"""Test Search Results Count Bereich 1-20."""
from llm_gateway.routes.comparison import ComparisonRequest
from pydantic import ValidationError
# Gueltige Werte
req = ComparisonRequest(prompt="test", search_results_count=1)
assert req.search_results_count == 1
req = ComparisonRequest(prompt="test", search_results_count=20)
assert req.search_results_count == 20
# Ungueltige Werte
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", search_results_count=0)
with pytest.raises(ValidationError):
ComparisonRequest(prompt="test", search_results_count=21)