""" 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="
Test content
", 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 == "Test content
" 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"])