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/dsms-gateway/test_main.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

613 lines
20 KiB
Python

"""
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": "<h1>Datenschutzerklärung</h1>",
"language": "de"
}
)
assert response.status_code == 200
data = response.json()
assert "cid" in data
assert data["document_id"] == "privacy-policy"
assert data["version"] == "2.0"
assert "checksum" in data
assert "archived_at" in data
def test_archive_legal_document_calculates_checksum(self, valid_auth_header, mock_ipfs_response):
"""Test: Checksum für HTML-Inhalt korrekt berechnet"""
content = "<h1>Test Content</h1>"
expected_checksum = hashlib.sha256(content.encode('utf-8')).hexdigest()
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": "terms",
"version": "1.0",
"content": content
}
)
assert response.status_code == 200
data = response.json()
assert data["checksum"] == expected_checksum
# ==================== Document Verification Tests ====================
class TestDocumentVerification:
"""Tests für die Dokumenten-Verifizierung"""
def test_verify_document_integrity_valid(self):
"""Test: Dokument mit gültiger Integrität"""
content = "Test content"
checksum = hashlib.sha256(content.encode('utf-8')).hexdigest()
package = {
"metadata": {
"document_type": "legal_document",
"checksum": checksum
},
"content": content
}
with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat:
mock_cat.return_value = json.dumps(package).encode()
response = client.get("/api/v1/verify/QmTestCid123")
assert response.status_code == 200
data = response.json()
assert data["exists"] is True
assert data["integrity_valid"] is True
assert data["stored_checksum"] == checksum
assert data["calculated_checksum"] == checksum
def test_verify_document_integrity_invalid(self):
"""Test: Dokument mit ungültiger Integrität (manipuliert)"""
package = {
"metadata": {
"document_type": "legal_document",
"checksum": "fake_checksum_12345"
},
"content": "Actual content"
}
with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat:
mock_cat.return_value = json.dumps(package).encode()
response = client.get("/api/v1/verify/QmTestCid123")
assert response.status_code == 200
data = response.json()
assert data["exists"] is True
assert data["integrity_valid"] is False
def test_verify_document_not_found(self):
"""Test: Nicht existierendes Dokument verifizieren"""
with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat:
mock_cat.side_effect = Exception("Not found")
response = client.get("/api/v1/verify/QmNonExistent")
assert response.status_code == 200
data = response.json()
assert data["exists"] is False
assert "error" in data
def test_verify_document_public_access(self):
"""Test: Verifizierung ist öffentlich zugänglich (keine Auth)"""
package = {
"metadata": {"checksum": "abc"},
"content": "test"
}
with patch("main.ipfs_cat", new_callable=AsyncMock) as mock_cat:
mock_cat.return_value = json.dumps(package).encode()
# Kein Authorization Header!
response = client.get("/api/v1/verify/QmTestCid123")
assert response.status_code == 200
# ==================== Node Info Tests ====================
class TestNodeInfo:
"""Tests für Node-Informationen"""
def test_get_node_info_success(self):
"""Test: Node-Informationen abrufen"""
id_response = {
"ID": "QmNodeId12345",
"ProtocolVersion": "ipfs/0.1.0",
"AgentVersion": "kubo/0.24.0",
"Addresses": ["/ip4/127.0.0.1/tcp/4001"]
}
stat_response = {
"RepoSize": 1048576,
"StorageMax": 10737418240,
"NumObjects": 42
}
with patch("main.httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
async def mock_post(url, **kwargs):
mock_resp = MagicMock()
if "id" in url:
mock_resp.status_code = 200
mock_resp.json.return_value = id_response
elif "stat" in url:
mock_resp.status_code = 200
mock_resp.json.return_value = stat_response
return mock_resp
mock_instance.post = mock_post
mock_client.return_value.__aenter__.return_value = mock_instance
response = client.get("/api/v1/node/info")
assert response.status_code == 200
data = response.json()
assert data["node_id"] == "QmNodeId12345"
assert data["num_objects"] == 42
def test_get_node_info_public_access(self):
"""Test: Node-Info ist öffentlich zugänglich"""
with patch("main.httpx.AsyncClient") as mock_client:
mock_instance = AsyncMock()
mock_instance.post.return_value = MagicMock(
status_code=200,
json=lambda: {}
)
mock_client.return_value.__aenter__.return_value = mock_instance
# Kein Authorization Header!
response = client.get("/api/v1/node/info")
assert response.status_code == 200
# ==================== Model Tests ====================
class TestModels:
"""Tests für Pydantic Models"""
def test_document_metadata_defaults(self):
"""Test: DocumentMetadata Default-Werte"""
metadata = DocumentMetadata(document_type="test")
assert metadata.document_type == "test"
assert metadata.document_id is None
assert metadata.version is None
assert metadata.language == "de"
assert metadata.encrypted is False
def test_document_metadata_all_fields(self):
"""Test: DocumentMetadata mit allen Feldern"""
metadata = DocumentMetadata(
document_type="legal_document",
document_id="doc-123",
version="1.0",
language="en",
created_at="2024-01-01T00:00:00",
checksum="abc123",
encrypted=True
)
assert metadata.document_type == "legal_document"
assert metadata.document_id == "doc-123"
assert metadata.version == "1.0"
assert metadata.language == "en"
assert metadata.encrypted is True
def test_stored_document_model(self, sample_document_metadata):
"""Test: StoredDocument Model"""
stored = StoredDocument(
cid="QmTest123",
size=1024,
metadata=sample_document_metadata,
gateway_url="http://localhost:8080/ipfs/QmTest123",
timestamp="2024-01-01T00:00:00"
)
assert stored.cid == "QmTest123"
assert stored.size == 1024
assert stored.metadata.document_type == "legal_document"
def test_document_list_model(self):
"""Test: DocumentList Model"""
doc_list = DocumentList(
documents=[{"cid": "Qm1"}, {"cid": "Qm2"}],
total=2
)
assert doc_list.total == 2
assert len(doc_list.documents) == 2
# ==================== Integration Tests ====================
class TestIntegration:
"""Integration Tests (erfordern laufenden IPFS Node)"""
@pytest.mark.skip(reason="Erfordert laufenden IPFS Node")
def test_full_document_lifecycle(self, valid_auth_header):
"""Integration Test: Vollständiger Dokument-Lebenszyklus"""
# 1. Dokument speichern
response = client.post(
"/api/v1/documents",
headers=valid_auth_header,
files={"file": ("test.txt", b"Test content", "text/plain")}
)
assert response.status_code == 200
cid = response.json()["cid"]
# 2. Dokument abrufen
response = client.get(
f"/api/v1/documents/{cid}",
headers=valid_auth_header
)
assert response.status_code == 200
# 3. Verifizieren
response = client.get(f"/api/v1/verify/{cid}")
assert response.status_code == 200
assert response.json()["integrity_valid"] is True
# 4. Unpinnen
response = client.delete(
f"/api/v1/documents/{cid}",
headers=valid_auth_header
)
assert response.status_code == 200
# ==================== Run Tests ====================
if __name__ == "__main__":
pytest.main([__file__, "-v"])