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

408 lines
13 KiB
Python

"""
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"])