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>
This commit is contained in:
377
backend/tests/test_comparison.py
Normal file
377
backend/tests/test_comparison.py
Normal file
@@ -0,0 +1,377 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user