""" Unit Tests für DSMS Gateway Tests für alle API-Endpoints und Hilfsfunktionen """ import pytest import hashlib import json from unittest.mock import AsyncMock, patch, MagicMock from fastapi.testclient import TestClient from httpx import Response # Import der App from main import app, DocumentMetadata, StoredDocument, DocumentList # Test Client client = TestClient(app) # ==================== Fixtures ==================== @pytest.fixture def valid_auth_header(): """Gültiger Authorization Header für Tests""" return {"Authorization": "Bearer test-token-12345"} @pytest.fixture def sample_document_metadata(): """Beispiel-Metadaten für Tests""" return DocumentMetadata( document_type="legal_document", document_id="doc-123", version="1.0", language="de", created_at="2024-01-01T00:00:00", checksum="abc123", encrypted=False ) @pytest.fixture def mock_ipfs_response(): """Mock-Antwort von IPFS add""" return { "Hash": "QmTest1234567890abcdef", "Size": "1024" } # ==================== Health Check Tests ==================== class TestHealthCheck: """Tests für den Health Check Endpoint""" def test_health_check_ipfs_connected(self): """Test: Health Check wenn IPFS verbunden ist""" with patch("main.httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.post.return_value = MagicMock(status_code=200) mock_client.return_value.__aenter__.return_value = mock_instance response = client.get("/health") assert response.status_code == 200 data = response.json() assert "status" in data assert "ipfs_connected" in data assert "timestamp" in data def test_health_check_ipfs_disconnected(self): """Test: Health Check wenn IPFS nicht erreichbar""" with patch("main.httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.post.side_effect = Exception("Connection failed") mock_client.return_value.__aenter__.return_value = mock_instance response = client.get("/health") assert response.status_code == 200 data = response.json() assert data["status"] == "degraded" assert data["ipfs_connected"] is False # ==================== Authorization Tests ==================== class TestAuthorization: """Tests für die Autorisierung""" def test_documents_endpoint_without_auth_returns_401(self): """Test: Dokument-Endpoint ohne Auth gibt 401 zurück""" response = client.get("/api/v1/documents") assert response.status_code == 401 def test_documents_endpoint_with_invalid_token_returns_401(self): """Test: Ungültiges Token-Format gibt 401 zurück""" response = client.get( "/api/v1/documents", headers={"Authorization": "InvalidFormat"} ) assert response.status_code == 401 def test_documents_endpoint_with_valid_token_format(self, valid_auth_header): """Test: Gültiges Token-Format wird akzeptiert""" with patch("main.ipfs_pin_ls", new_callable=AsyncMock) as mock_pin_ls: mock_pin_ls.return_value = [] response = client.get( "/api/v1/documents", headers=valid_auth_header ) assert response.status_code == 200 # ==================== Document Storage Tests ==================== class TestDocumentStorage: """Tests für das Speichern von Dokumenten""" def test_store_document_success(self, valid_auth_header, mock_ipfs_response): """Test: Dokument erfolgreich speichern""" with patch("main.ipfs_add", new_callable=AsyncMock) as mock_add: mock_add.return_value = mock_ipfs_response test_content = b"Test document content" response = client.post( "/api/v1/documents", headers=valid_auth_header, files={"file": ("test.txt", test_content, "text/plain")}, data={ "document_type": "legal_document", "document_id": "doc-123", "version": "1.0", "language": "de" } ) assert response.status_code == 200 data = response.json() assert "cid" in data assert data["cid"] == "QmTest1234567890abcdef" assert "metadata" in data assert "gateway_url" in data def test_store_document_calculates_checksum(self, valid_auth_header, mock_ipfs_response): """Test: Checksum wird korrekt berechnet""" with patch("main.ipfs_add", new_callable=AsyncMock) as mock_add: mock_add.return_value = mock_ipfs_response test_content = b"Test content for checksum" expected_checksum = hashlib.sha256(test_content).hexdigest() response = client.post( "/api/v1/documents", headers=valid_auth_header, files={"file": ("test.txt", test_content, "text/plain")} ) assert response.status_code == 200 data = response.json() assert data["metadata"]["checksum"] == expected_checksum def test_store_document_without_file_returns_422(self, valid_auth_header): """Test: Fehlende Datei gibt 422 zurück""" response = client.post( "/api/v1/documents", headers=valid_auth_header ) assert response.status_code == 422 # ==================== Document Retrieval Tests ==================== class TestDocumentRetrieval: """Tests für das Abrufen von Dokumenten""" def test_get_document_success(self, valid_auth_header): """Test: Dokument erfolgreich abrufen""" test_content = b"Original content" package = { "metadata": { "document_type": "legal_document", "checksum": hashlib.sha256(test_content).hexdigest() }, "content_base64": test_content.hex(), "filename": "test.txt" } with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat: mock_cat.return_value = json.dumps(package).encode() response = client.get( "/api/v1/documents/QmTestCid123", headers=valid_auth_header ) assert response.status_code == 200 assert response.content == test_content def test_get_document_not_found(self, valid_auth_header): """Test: Nicht existierendes Dokument gibt 404 zurück""" with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat: from fastapi import HTTPException mock_cat.side_effect = HTTPException(status_code=404, detail="Not found") response = client.get( "/api/v1/documents/QmNonExistent", headers=valid_auth_header ) assert response.status_code == 404 def test_get_document_metadata_success(self, valid_auth_header): """Test: Dokument-Metadaten abrufen""" test_content = b"Content" package = { "metadata": { "document_type": "legal_document", "document_id": "doc-123", "version": "1.0" }, "content_base64": test_content.hex(), "filename": "test.txt" } with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat: mock_cat.return_value = json.dumps(package).encode() response = client.get( "/api/v1/documents/QmTestCid123/metadata", headers=valid_auth_header ) assert response.status_code == 200 data = response.json() assert data["cid"] == "QmTestCid123" assert data["metadata"]["document_type"] == "legal_document" # ==================== Document List Tests ==================== class TestDocumentList: """Tests für das Auflisten von Dokumenten""" def test_list_documents_empty(self, valid_auth_header): """Test: Leere Dokumentenliste""" with patch("main.ipfs_pin_ls", new_callable=AsyncMock) as mock_pin_ls: mock_pin_ls.return_value = [] response = client.get( "/api/v1/documents", headers=valid_auth_header ) assert response.status_code == 200 data = response.json() assert data["documents"] == [] assert data["total"] == 0 def test_list_documents_with_items(self, valid_auth_header): """Test: Dokumentenliste mit Einträgen""" package = { "metadata": {"document_type": "legal_document"}, "content_base64": "68656c6c6f", "filename": "test.txt" } with patch("main.ipfs_pin_ls", new_callable=AsyncMock) as mock_pin_ls: mock_pin_ls.return_value = ["QmCid1", "QmCid2"] with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat: mock_cat.return_value = json.dumps(package).encode() response = client.get( "/api/v1/documents", headers=valid_auth_header ) assert response.status_code == 200 data = response.json() assert data["total"] == 2 # ==================== Document Deletion Tests ==================== class TestDocumentDeletion: """Tests für das Löschen von Dokumenten""" def test_unpin_document_success(self, valid_auth_header): """Test: Dokument erfolgreich unpinnen""" with patch("main.httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.post.return_value = MagicMock(status_code=200) mock_client.return_value.__aenter__.return_value = mock_instance response = client.delete( "/api/v1/documents/QmTestCid123", headers=valid_auth_header ) assert response.status_code == 200 data = response.json() assert data["status"] == "unpinned" assert data["cid"] == "QmTestCid123" def test_unpin_document_not_found(self, valid_auth_header): """Test: Nicht existierendes Dokument unpinnen""" with patch("main.httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() mock_instance.post.return_value = MagicMock(status_code=404) mock_client.return_value.__aenter__.return_value = mock_instance response = client.delete( "/api/v1/documents/QmNonExistent", headers=valid_auth_header ) assert response.status_code == 404 # ==================== Legal Document Archive Tests ==================== class TestLegalDocumentArchive: """Tests für die Legal Document Archivierung""" def test_archive_legal_document_success(self, valid_auth_header, mock_ipfs_response): """Test: Legal Document erfolgreich archivieren""" with patch("main.ipfs_add", new_callable=AsyncMock) as mock_add: mock_add.return_value = mock_ipfs_response response = client.post( "/api/v1/legal-documents/archive", headers=valid_auth_header, params={ "document_id": "privacy-policy", "version": "2.0", "content": "