""" Tests für die DSR (Data Subject Request) API Testet die Proxy-Endpoints für Betroffenenanfragen. """ import pytest from unittest.mock import AsyncMock, patch, MagicMock from fastapi.testclient import TestClient import httpx class TestDSRUserAPI: """Tests für User-Endpoints der DSR API.""" def test_create_dsr_request_body(self): """Test: CreateDSRRequest Model Validierung.""" from dsr_api import CreateDSRRequest # Valide Anfrage req = CreateDSRRequest( request_type="access", requester_email="test@example.com", requester_name="Max Mustermann" ) assert req.request_type == "access" assert req.requester_email == "test@example.com" # Minimal-Anfrage req_minimal = CreateDSRRequest( request_type="erasure" ) assert req_minimal.request_type == "erasure" assert req_minimal.requester_email is None def test_valid_request_types(self): """Test: Alle DSGVO-Anfragetypen.""" valid_types = ["access", "rectification", "erasure", "restriction", "portability"] for req_type in valid_types: from dsr_api import CreateDSRRequest req = CreateDSRRequest(request_type=req_type) assert req.request_type == req_type @pytest.mark.asyncio async def test_proxy_request_success(self): """Test: Erfolgreiche Proxy-Anfrage.""" from dsr_api import proxy_request mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = b'{"success": true}' mock_response.json.return_value = {"success": True} with patch("httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.get = AsyncMock(return_value=mock_response) mock_client.return_value.__aenter__.return_value = mock_instance result = await proxy_request("GET", "/dsr", "test-token") assert result == {"success": True} @pytest.mark.asyncio async def test_proxy_request_error(self): """Test: Fehler bei Proxy-Anfrage.""" from dsr_api import proxy_request from fastapi import HTTPException mock_response = MagicMock() mock_response.status_code = 404 mock_response.content = b'{"error": "Not found"}' mock_response.json.return_value = {"error": "Not found"} with patch("httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.get = AsyncMock(return_value=mock_response) mock_client.return_value.__aenter__.return_value = mock_instance with pytest.raises(HTTPException) as exc_info: await proxy_request("GET", "/dsr/invalid-id", "test-token") assert exc_info.value.status_code == 404 @pytest.mark.asyncio async def test_proxy_request_service_unavailable(self): """Test: Consent Service nicht erreichbar.""" from dsr_api import proxy_request from fastapi import HTTPException with patch("httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.get = AsyncMock(side_effect=httpx.RequestError("Connection failed")) mock_client.return_value.__aenter__.return_value = mock_instance with pytest.raises(HTTPException) as exc_info: await proxy_request("GET", "/dsr", "test-token") assert exc_info.value.status_code == 503 def test_get_token_valid(self): """Test: Token-Extraktion aus Header.""" from dsr_api import get_token token = get_token("Bearer valid-jwt-token") assert token == "valid-jwt-token" def test_get_token_invalid(self): """Test: Ungültiger Authorization Header.""" from dsr_api import get_token from fastapi import HTTPException with pytest.raises(HTTPException) as exc_info: get_token(None) assert exc_info.value.status_code == 401 with pytest.raises(HTTPException) as exc_info: get_token("InvalidHeader") assert exc_info.value.status_code == 401 class TestDSRAdminAPI: """Tests für Admin-Endpoints der DSR API.""" def test_create_dsr_admin_request_body(self): """Test: Admin CreateDSRRequest Model.""" from dsr_admin_api import CreateDSRRequest req = CreateDSRRequest( request_type="erasure", requester_email="user@example.com", requester_name="Test User", priority="high", source="admin_panel" ) assert req.request_type == "erasure" assert req.requester_email == "user@example.com" assert req.priority == "high" assert req.source == "admin_panel" def test_update_dsr_request_body(self): """Test: UpdateDSRRequest Model.""" from dsr_admin_api import UpdateDSRRequest req = UpdateDSRRequest( status="processing", priority="expedited", processing_notes="Daten werden zusammengestellt" ) assert req.status == "processing" assert req.priority == "expedited" assert req.processing_notes == "Daten werden zusammengestellt" def test_verify_identity_request_body(self): """Test: VerifyIdentityRequest Model.""" from dsr_admin_api import VerifyIdentityRequest req = VerifyIdentityRequest(method="id_card") assert req.method == "id_card" req2 = VerifyIdentityRequest(method="video_call") assert req2.method == "video_call" def test_extend_deadline_request_body(self): """Test: ExtendDeadlineRequest Model.""" from dsr_admin_api import ExtendDeadlineRequest req = ExtendDeadlineRequest( reason="Komplexität der Anfrage", days=60 ) assert req.reason == "Komplexität der Anfrage" assert req.days == 60 def test_complete_dsr_request_body(self): """Test: CompleteDSRRequest Model.""" from dsr_admin_api import CompleteDSRRequest req = CompleteDSRRequest( summary="Alle Daten wurden bereitgestellt.", result_data={"files": ["export.json"]} ) assert req.summary == "Alle Daten wurden bereitgestellt." assert "files" in req.result_data def test_reject_dsr_request_body(self): """Test: RejectDSRRequest Model.""" from dsr_admin_api import RejectDSRRequest req = RejectDSRRequest( reason="Daten werden für Rechtsstreitigkeiten benötigt", legal_basis="Art. 17(3)e" ) assert req.reason == "Daten werden für Rechtsstreitigkeiten benötigt" assert req.legal_basis == "Art. 17(3)e" def test_send_communication_request_body(self): """Test: SendCommunicationRequest Model.""" from dsr_admin_api import SendCommunicationRequest req = SendCommunicationRequest( communication_type="dsr_processing_started", template_version_id="uuid-123", variables={"custom_field": "value"} ) assert req.communication_type == "dsr_processing_started" assert req.template_version_id == "uuid-123" def test_update_exception_check_request_body(self): """Test: UpdateExceptionCheckRequest Model.""" from dsr_admin_api import UpdateExceptionCheckRequest req = UpdateExceptionCheckRequest( applies=True, notes="Laufende Rechtsstreitigkeiten" ) assert req.applies is True assert req.notes == "Laufende Rechtsstreitigkeiten" def test_create_template_version_request_body(self): """Test: CreateTemplateVersionRequest Model.""" from dsr_admin_api import CreateTemplateVersionRequest req = CreateTemplateVersionRequest( version="1.1.0", language="de", subject="Eingangsbestätigung", body_html="
Inhalt
", body_text="Inhalt" ) assert req.version == "1.1.0" assert req.language == "de" assert req.subject == "Eingangsbestätigung" def test_get_admin_token_from_header(self): """Test: Admin-Token aus Header extrahieren.""" from dsr_admin_api import get_admin_token # Mit Bearer Token token = get_admin_token("Bearer admin-jwt-token") assert token == "admin-jwt-token" def test_get_admin_token_fallback(self): """Test: Admin-Token Fallback für Entwicklung.""" from dsr_admin_api import get_admin_token # Ohne Header - generiert Dev-Token token = get_admin_token(None) assert token is not None assert len(token) > 0 class TestDSRRequestTypes: """Tests für DSR-Anfragetypen und DSGVO-Artikel.""" def test_access_request_art_15(self): """Test: Auskunftsrecht (Art. 15 DSGVO).""" # 30 Tage Frist expected_deadline_days = 30 assert expected_deadline_days == 30 def test_rectification_request_art_16(self): """Test: Berichtigungsrecht (Art. 16 DSGVO).""" # 14 Tage empfohlen expected_deadline_days = 14 assert expected_deadline_days == 14 def test_erasure_request_art_17(self): """Test: Löschungsrecht (Art. 17 DSGVO).""" # 14 Tage empfohlen expected_deadline_days = 14 assert expected_deadline_days == 14 def test_restriction_request_art_18(self): """Test: Einschränkungsrecht (Art. 18 DSGVO).""" # 14 Tage empfohlen expected_deadline_days = 14 assert expected_deadline_days == 14 def test_portability_request_art_20(self): """Test: Datenübertragbarkeit (Art. 20 DSGVO).""" # 30 Tage Frist expected_deadline_days = 30 assert expected_deadline_days == 30 class TestDSRStatusWorkflow: """Tests für den DSR Status-Workflow.""" def test_valid_status_values(self): """Test: Alle gültigen Status-Werte.""" valid_statuses = [ "intake", "identity_verification", "processing", "completed", "rejected", "cancelled" ] for status in valid_statuses: assert status in valid_statuses def test_status_transition_intake(self): """Test: Erlaubte Übergänge von 'intake'.""" allowed_from_intake = [ "identity_verification", "processing", "rejected", "cancelled" ] # Completed ist NICHT direkt von intake erlaubt assert "completed" not in allowed_from_intake def test_status_transition_processing(self): """Test: Erlaubte Übergänge von 'processing'.""" allowed_from_processing = [ "completed", "rejected", "cancelled" ] # Zurück zu intake ist NICHT erlaubt assert "intake" not in allowed_from_processing def test_terminal_states(self): """Test: Endstatus ohne weitere Übergänge.""" terminal_states = ["completed", "rejected", "cancelled"] for state in terminal_states: # Von Endstatus keine Übergänge möglich assert state in terminal_states class TestDSRExceptionChecks: """Tests für Art. 17(3) Ausnahmeprüfungen.""" def test_exception_types_art_17_3(self): """Test: Alle Ausnahmen nach Art. 17(3) DSGVO.""" exceptions = { "art_17_3_a": "Meinungs- und Informationsfreiheit", "art_17_3_b": "Rechtliche Verpflichtung", "art_17_3_c": "Öffentliches Interesse im Gesundheitsbereich", "art_17_3_d": "Archivzwecke, wissenschaftliche/historische Forschung", "art_17_3_e": "Geltendmachung von Rechtsansprüchen" } assert len(exceptions) == 5 for code, description in exceptions.items(): assert code.startswith("art_17_3_") assert len(description) > 0 def test_rejection_legal_bases(self): """Test: Rechtsgrundlagen für Ablehnung.""" legal_bases = [ "Art. 17(3)a", "Art. 17(3)b", "Art. 17(3)c", "Art. 17(3)d", "Art. 17(3)e", "Art. 12(5)" # Offensichtlich unbegründet/exzessiv ] assert len(legal_bases) == 6 assert "Art. 12(5)" in legal_bases class TestDSRTemplates: """Tests für DSR-Vorlagen.""" def test_template_types(self): """Test: Alle erwarteten Vorlagen-Typen.""" expected_templates = [ "dsr_receipt_access", "dsr_receipt_rectification", "dsr_receipt_erasure", "dsr_receipt_restriction", "dsr_receipt_portability", "dsr_identity_request", "dsr_processing_started", "dsr_processing_update", "dsr_clarification_request", "dsr_completed_access", "dsr_completed_rectification", "dsr_completed_erasure", "dsr_completed_restriction", "dsr_completed_portability", "dsr_restriction_lifted", "dsr_rejected_identity", "dsr_rejected_exception", "dsr_rejected_unfounded", "dsr_deadline_warning" ] assert len(expected_templates) == 19 def test_template_variables(self): """Test: Standard Template-Variablen.""" variables = [ "{{requester_name}}", "{{requester_email}}", "{{request_number}}", "{{request_type_de}}", "{{request_date}}", "{{deadline_date}}", "{{company_name}}", "{{dpo_name}}", "{{dpo_email}}", "{{portal_url}}" ] for var in variables: assert var.startswith("{{") assert var.endswith("}}")