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_gdpr_api.py
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

288 lines
10 KiB
Python

"""
Tests für die GDPR API Endpoints
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, AsyncMock, MagicMock
import sys
import os
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
class TestDataCategories:
"""Tests für Datenkategorien-Endpoint"""
def test_data_categories_structure(self):
"""Test that data categories have correct structure"""
# Import the data categories from the GDPR API
try:
from gdpr_api import DATA_CATEGORIES
except ImportError:
pytest.skip("gdpr_api module not available")
assert "essential" in DATA_CATEGORIES
assert "optional" in DATA_CATEGORIES
# Check essential categories
for category in DATA_CATEGORIES["essential"]:
assert "name" in category
assert "description" in category
assert "retention_days" in category
assert "legal_basis" in category
# Check optional categories
for category in DATA_CATEGORIES["optional"]:
assert "name" in category
assert "description" in category
assert "retention_days" in category
assert "cookie_category" in category
def test_retention_days_values(self):
"""Test that retention days are reasonable"""
try:
from gdpr_api import DATA_CATEGORIES
except ImportError:
pytest.skip("gdpr_api module not available")
all_categories = DATA_CATEGORIES["essential"] + DATA_CATEGORIES["optional"]
for category in all_categories:
retention = category.get("retention_days")
if retention is not None and isinstance(retention, int):
assert retention > 0, f"Retention for {category['name']} should be positive"
assert retention <= 3650, f"Retention for {category['name']} shouldn't exceed 10 years"
class TestGDPRCompliance:
"""Tests für GDPR Compliance"""
def test_gdpr_rights_covered(self):
"""Test that all GDPR rights are addressable"""
gdpr_rights = {
"art_15": "Right of access", # Auskunftsrecht
"art_16": "Right to rectification", # Berichtigungsrecht
"art_17": "Right to erasure", # Löschungsrecht
"art_18": "Right to restriction", # Einschränkungsrecht
"art_20": "Right to portability", # Datenübertragbarkeit
"art_21": "Right to object", # Widerspruchsrecht
}
# These should all be implementable via the consent service
for article, right in gdpr_rights.items():
assert right is not None, f"GDPR {article} ({right}) should be covered"
def test_mandatory_documents(self):
"""Test that mandatory legal documents are defined"""
mandatory_docs = ["terms", "privacy"]
for doc in mandatory_docs:
assert doc in mandatory_docs, f"Document {doc} should be mandatory"
def test_cookie_categories_defined(self):
"""Test that cookie categories follow GDPR requirements"""
expected_categories = ["necessary", "functional", "analytics", "marketing"]
# Necessary cookies must be allowed without consent
assert "necessary" in expected_categories
# Optional categories require consent
optional = [c for c in expected_categories if c != "necessary"]
assert len(optional) > 0
class TestRetentionPolicies:
"""Tests für Löschfristen"""
def test_session_data_retention(self):
"""Test that session data has short retention"""
session_retention_days = 1 # Expected: 24 hours max
assert session_retention_days <= 7, "Session data should be retained for max 7 days"
def test_audit_log_retention(self):
"""Test audit log retention complies with legal requirements"""
# Audit logs must be kept for compliance but not indefinitely
audit_retention_days = 1095 # 3 years
assert audit_retention_days >= 365, "Audit logs should be kept for at least 1 year"
assert audit_retention_days <= 3650, "Audit logs shouldn't be kept more than 10 years"
def test_consent_record_retention(self):
"""Test that consent records are kept long enough for proof"""
# § 7a UWG requires proof of consent for 3 years
consent_retention_days = 1095
assert consent_retention_days >= 1095, "Consent records must be kept for at least 3 years"
def test_ip_address_retention(self):
"""Test IP address retention is minimized"""
ip_retention_days = 28 # 4 weeks
assert ip_retention_days <= 90, "IP addresses should not be stored longer than 90 days"
class TestDataMinimization:
"""Tests für Datensparsamkeit"""
def test_password_not_stored_plain(self):
"""Test that passwords are never stored in plain text"""
# This is a design requirement test
assert True, "Passwords must be hashed with bcrypt"
def test_unnecessary_data_not_collected(self):
"""Test that only necessary data is collected"""
# User model should only contain necessary fields
required_fields = ["id", "email", "password_hash", "created_at"]
optional_fields = ["name", "role"]
# No excessive personal data
forbidden_fields = ["ssn", "credit_card", "date_of_birth", "address"]
for field in forbidden_fields:
assert field not in required_fields, f"Field {field} should not be required"
class TestAnonymization:
"""Tests für Anonymisierung"""
def test_ip_anonymization(self):
"""Test IP address anonymization logic"""
def anonymize_ip(ip: str) -> str:
"""Anonymize IPv4 by zeroing last octet"""
parts = ip.split(".")
if len(parts) == 4:
parts[3] = "0"
return ".".join(parts)
return ip
test_cases = [
("192.168.1.100", "192.168.1.0"),
("10.0.0.1", "10.0.0.0"),
("172.16.255.255", "172.16.255.0"),
]
for original, expected in test_cases:
assert anonymize_ip(original) == expected
def test_user_data_anonymization(self):
"""Test user data anonymization for deleted accounts"""
def anonymize_user_data(user_data: dict) -> dict:
"""Anonymize user data while keeping audit trail"""
anonymized = user_data.copy()
anonymized["email"] = f"deleted-{user_data['id']}@anonymized.local"
anonymized["name"] = None
anonymized["password_hash"] = None
return anonymized
original = {
"id": "123",
"email": "real@example.com",
"name": "John Doe",
"password_hash": "bcrypt_hash"
}
anonymized = anonymize_user_data(original)
assert "@anonymized.local" in anonymized["email"]
assert anonymized["name"] is None
assert anonymized["password_hash"] is None
assert anonymized["id"] == original["id"] # ID preserved for audit
class TestExportFormat:
"""Tests für Datenexport-Format"""
def test_export_includes_all_user_data(self):
"""Test that export includes all required data sections"""
required_sections = [
"user", # Personal data
"consents", # Consent history
"cookie_consents", # Cookie preferences
"audit_log", # Activity log
"exported_at", # Export timestamp
]
# Mock export response
mock_export = {
"user": {},
"consents": [],
"cookie_consents": [],
"audit_log": [],
"exported_at": "2024-01-01T00:00:00Z"
}
for section in required_sections:
assert section in mock_export, f"Export must include {section}"
def test_export_is_machine_readable(self):
"""Test that export can be provided in machine-readable format"""
import json
mock_data = {
"user": {"email": "test@example.com"},
"exported_at": "2024-01-01T00:00:00Z"
}
# Should be valid JSON
json_str = json.dumps(mock_data)
parsed = json.loads(json_str)
assert parsed == mock_data
class TestConsentValidation:
"""Tests für Consent-Validierung"""
def test_consent_requires_version_id(self):
"""Test that consent requires a specific document version"""
consent_request = {
"document_type": "terms",
"version_id": "version-123",
"consented": True
}
assert "version_id" in consent_request
assert consent_request["version_id"] is not None
def test_consent_tracks_ip_and_timestamp(self):
"""Test that consent tracks IP and timestamp for proof"""
consent_record = {
"user_id": "user-123",
"document_version_id": "version-123",
"consented": True,
"ip_address": "192.168.1.1",
"consented_at": "2024-01-01T00:00:00Z"
}
assert "ip_address" in consent_record
assert "consented_at" in consent_record
def test_withdrawal_is_possible(self):
"""Test that consent can be withdrawn (Art. 7(3) GDPR)"""
# Withdrawal should be as easy as giving consent
withdraw_request = {
"consent_id": "consent-123"
}
assert "consent_id" in withdraw_request
class TestSecurityHeaders:
"""Tests für Sicherheits-Header"""
def test_required_security_headers(self):
"""Test that API responses include security headers"""
required_headers = [
"X-Content-Type-Options", # nosniff
"X-Frame-Options", # DENY or SAMEORIGIN
"Content-Security-Policy", # CSP
"X-XSS-Protection", # Legacy but useful
]
# These should be set by the application
for header in required_headers:
assert header is not None, f"Header {header} should be set"
if __name__ == "__main__":
pytest.main([__file__, "-v"])