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:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,407 @@
"""
Tests für den Consent Client
"""
import pytest
import jwt
from datetime import datetime, timedelta
from unittest.mock import AsyncMock, patch, MagicMock
import sys
import os
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from consent_client import (
generate_jwt_token,
generate_demo_token,
DocumentType,
ConsentStatus,
DocumentVersion,
ConsentClient,
JWT_SECRET,
)
class TestJWTTokenGeneration:
"""Tests für JWT Token Generierung"""
def test_generate_jwt_token_default(self):
"""Test JWT generation with default values"""
token = generate_jwt_token()
assert token is not None
assert isinstance(token, str)
assert len(token) > 0
# Decode and verify
decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
assert "user_id" in decoded
assert decoded["email"] == "demo@breakpilot.app"
assert decoded["role"] == "user"
assert "exp" in decoded
assert "iat" in decoded
def test_generate_jwt_token_custom_values(self):
"""Test JWT generation with custom values"""
user_id = "test-user-123"
email = "test@example.com"
role = "admin"
token = generate_jwt_token(
user_id=user_id,
email=email,
role=role,
expires_hours=48
)
decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
assert decoded["user_id"] == user_id
assert decoded["email"] == email
assert decoded["role"] == role
def test_generate_jwt_token_expiration(self):
"""Test that token expiration is set correctly"""
token = generate_jwt_token(expires_hours=1)
decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
exp = datetime.utcfromtimestamp(decoded["exp"])
now = datetime.utcnow()
# Should expire in approximately 1 hour
time_diff = exp - now
assert time_diff.total_seconds() > 3500 # At least 58 minutes
assert time_diff.total_seconds() < 3700 # At most 62 minutes
def test_generate_demo_token(self):
"""Test demo token generation"""
token = generate_demo_token()
decoded = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
assert decoded["user_id"].startswith("demo-user-")
assert decoded["email"] == "demo@breakpilot.app"
assert decoded["role"] == "user"
def test_tokens_are_unique(self):
"""Test that generated tokens are unique"""
tokens = [generate_demo_token() for _ in range(10)]
assert len(set(tokens)) == 10 # All tokens should be unique
def test_jwt_token_signature(self):
"""Test that token signature is valid"""
token = generate_jwt_token()
# Should not raise exception
jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
# Should raise exception with wrong secret
with pytest.raises(jwt.InvalidSignatureError):
jwt.decode(token, "wrong-secret", algorithms=["HS256"])
class TestDocumentType:
"""Tests für DocumentType Enum"""
def test_document_types(self):
"""Test all document types exist"""
assert DocumentType.TERMS.value == "terms"
assert DocumentType.PRIVACY.value == "privacy"
assert DocumentType.COOKIES.value == "cookies"
assert DocumentType.COMMUNITY.value == "community"
def test_document_type_is_string(self):
"""Test that document types can be used as strings"""
assert str(DocumentType.TERMS) == "DocumentType.TERMS"
assert DocumentType.TERMS.value == "terms"
class TestConsentStatus:
"""Tests für ConsentStatus Dataclass"""
def test_consent_status_basic(self):
"""Test basic ConsentStatus creation"""
status = ConsentStatus(has_consent=True)
assert status.has_consent is True
assert status.current_version_id is None
assert status.consented_version is None
assert status.needs_update is False
assert status.consented_at is None
def test_consent_status_full(self):
"""Test ConsentStatus with all fields"""
status = ConsentStatus(
has_consent=True,
current_version_id="version-123",
consented_version="1.0.0",
needs_update=False,
consented_at="2024-01-01T00:00:00Z"
)
assert status.has_consent is True
assert status.current_version_id == "version-123"
assert status.consented_version == "1.0.0"
assert status.needs_update is False
assert status.consented_at == "2024-01-01T00:00:00Z"
class TestDocumentVersion:
"""Tests für DocumentVersion Dataclass"""
def test_document_version_creation(self):
"""Test DocumentVersion creation"""
version = DocumentVersion(
id="doc-version-123",
document_id="doc-123",
version="1.0.0",
language="de",
title="Test Document",
content="<p>Test content</p>",
summary="Test summary"
)
assert version.id == "doc-version-123"
assert version.document_id == "doc-123"
assert version.version == "1.0.0"
assert version.language == "de"
assert version.title == "Test Document"
assert version.content == "<p>Test content</p>"
assert version.summary == "Test summary"
class TestConsentClient:
"""Tests für ConsentClient"""
def test_client_initialization(self):
"""Test client initialization"""
client = ConsentClient()
# In Docker: consent-service:8081, locally: localhost:8081
assert client.base_url in ("http://localhost:8081", "http://consent-service:8081")
assert "/api/v1" in client.api_url
def test_client_custom_url(self):
"""Test client with custom URL"""
client = ConsentClient(base_url="https://custom.example.com/")
assert client.base_url == "https://custom.example.com"
assert client.api_url == "https://custom.example.com/api/v1"
def test_get_headers(self):
"""Test header generation"""
client = ConsentClient()
token = "test-token-123"
headers = client._get_headers(token)
assert headers["Authorization"] == "Bearer test-token-123"
assert headers["Content-Type"] == "application/json"
class TestConsentClientAsync:
"""Async tests für ConsentClient"""
@pytest.mark.asyncio
async def test_check_consent_success(self):
"""Test successful consent check"""
client = ConsentClient()
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"has_consent": True,
"current_version_id": "version-123",
"consented_version": "1.0.0",
"needs_update": False,
"consented_at": "2024-01-01T00:00:00Z"
}
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.get.return_value = mock_response
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
status = await client.check_consent(
jwt_token="test-token",
document_type=DocumentType.TERMS
)
assert status.has_consent is True
assert status.current_version_id == "version-123"
@pytest.mark.asyncio
async def test_check_consent_not_found(self):
"""Test consent check when user has no consent"""
client = ConsentClient()
mock_response = MagicMock()
mock_response.status_code = 404
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.get.return_value = mock_response
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
status = await client.check_consent(
jwt_token="test-token",
document_type=DocumentType.TERMS
)
assert status.has_consent is False
assert status.needs_update is True
@pytest.mark.asyncio
async def test_check_consent_connection_error(self):
"""Test consent check when service is unavailable"""
import httpx
client = ConsentClient()
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.get.side_effect = httpx.RequestError("Connection error")
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
status = await client.check_consent(
jwt_token="test-token",
document_type=DocumentType.TERMS
)
# Should not block user when service is unavailable
assert status.has_consent is True
assert status.needs_update is False
@pytest.mark.asyncio
async def test_health_check_success(self):
"""Test successful health check"""
client = ConsentClient()
mock_response = MagicMock()
mock_response.status_code = 200
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.get.return_value = mock_response
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
is_healthy = await client.health_check()
assert is_healthy is True
@pytest.mark.asyncio
async def test_health_check_failure(self):
"""Test failed health check"""
import httpx
client = ConsentClient()
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.get.side_effect = httpx.RequestError("Connection refused")
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
is_healthy = await client.health_check()
assert is_healthy is False
@pytest.mark.asyncio
async def test_give_consent_success(self):
"""Test successful consent submission"""
client = ConsentClient()
mock_response = MagicMock()
mock_response.status_code = 201
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = mock_response
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
success = await client.give_consent(
jwt_token="test-token",
document_type="terms",
version_id="version-123",
consented=True
)
assert success is True
@pytest.mark.asyncio
async def test_give_consent_failure(self):
"""Test failed consent submission"""
client = ConsentClient()
mock_response = MagicMock()
mock_response.status_code = 400
with patch("httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = mock_response
mock_instance.__aenter__.return_value = mock_instance
mock_instance.__aexit__.return_value = None
mock_client.return_value = mock_instance
success = await client.give_consent(
jwt_token="test-token",
document_type="terms",
version_id="version-123",
consented=True
)
assert success is False
class TestValidation:
"""Tests für Validierungslogik"""
def test_valid_document_types(self):
"""Test that only valid document types are accepted"""
valid_types = ["terms", "privacy", "cookies", "community"]
for doc_type in DocumentType:
assert doc_type.value in valid_types
def test_jwt_expiration_validation(self):
"""Test that expired tokens are rejected"""
# Create token that expired 1 hour ago
expired_payload = {
"user_id": "test-user",
"email": "test@example.com",
"role": "user",
"exp": datetime.utcnow() - timedelta(hours=1),
"iat": datetime.utcnow() - timedelta(hours=2),
}
expired_token = jwt.encode(expired_payload, JWT_SECRET, algorithm="HS256")
with pytest.raises(jwt.ExpiredSignatureError):
jwt.decode(expired_token, JWT_SECRET, algorithms=["HS256"])
# Performance Tests
class TestPerformance:
"""Performance tests"""
def test_token_generation_performance(self):
"""Test that token generation is fast"""
import time
start = time.time()
for _ in range(100):
generate_jwt_token()
elapsed = time.time() - start
# Should generate 100 tokens in less than 1 second
assert elapsed < 1.0, f"Token generation too slow: {elapsed}s for 100 tokens"
if __name__ == "__main__":
pytest.main([__file__, "-v"])