fix(quality): Ruff/CVE/TS-Fixes, 104 neue Tests, Complexity-Refactoring
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 30s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 17s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 30s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 17s
- Ruff: 144 auto-fixes (unused imports, == None → is None), F821/F811/F841 manuell - CVEs: python-multipart>=0.0.22, weasyprint>=68.0, pillow>=12.1.1, npm audit fix (0 vulns) - TS: 5 tote Drafting-Engine-Dateien entfernt, allowed-facts/sanitizer/StepHeader/context fixes - Tests: +104 (ISMS 58, Evidence 18, VVT 14, Generation 14) → 1449 passed - Refactoring: collect_ci_evidence (F→A), row_to_response (E→A), extract_requirements (E→A) - Dead Code: pca-platform, 7 Go-Handler, dsr_api.py, duplicate Schemas entfernt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -432,3 +432,337 @@ class TestVVTCsvExport:
|
||||
text = self._collect_csv_body(response)
|
||||
lines = text.strip().split('\n')
|
||||
assert len(lines) == 1
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Endpoint Tests (TestClient + mock DB)
|
||||
# =============================================================================
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from fastapi import FastAPI
|
||||
from compliance.api.vvt_routes import router
|
||||
|
||||
_app = FastAPI()
|
||||
_app.include_router(router)
|
||||
_client = TestClient(_app)
|
||||
|
||||
DEFAULT_TENANT = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e"
|
||||
|
||||
|
||||
def _make_db_activity(**kwargs):
|
||||
"""Create a mock VVTActivityDB object for query results."""
|
||||
act = VVTActivityDB()
|
||||
act.id = kwargs.get("id", uuid.uuid4())
|
||||
act.tenant_id = kwargs.get("tenant_id", DEFAULT_TENANT)
|
||||
act.vvt_id = kwargs.get("vvt_id", "VVT-001")
|
||||
act.name = kwargs.get("name", "Test Verarbeitung")
|
||||
act.description = kwargs.get("description", None)
|
||||
act.purposes = kwargs.get("purposes", ["Vertragserfuellung"])
|
||||
act.legal_bases = kwargs.get("legal_bases", ["Art. 6 Abs. 1b"])
|
||||
act.data_subject_categories = kwargs.get("data_subject_categories", ["Kunden"])
|
||||
act.personal_data_categories = kwargs.get("personal_data_categories", ["Email"])
|
||||
act.recipient_categories = kwargs.get("recipient_categories", [])
|
||||
act.third_country_transfers = kwargs.get("third_country_transfers", [])
|
||||
act.retention_period = kwargs.get("retention_period", {"duration": "3 Jahre"})
|
||||
act.tom_description = kwargs.get("tom_description", None)
|
||||
act.business_function = kwargs.get("business_function", "IT")
|
||||
act.systems = kwargs.get("systems", [])
|
||||
act.deployment_model = kwargs.get("deployment_model", None)
|
||||
act.data_sources = kwargs.get("data_sources", [])
|
||||
act.data_flows = kwargs.get("data_flows", [])
|
||||
act.protection_level = kwargs.get("protection_level", "MEDIUM")
|
||||
act.dpia_required = kwargs.get("dpia_required", False)
|
||||
act.structured_toms = kwargs.get("structured_toms", {})
|
||||
act.status = kwargs.get("status", "DRAFT")
|
||||
act.responsible = kwargs.get("responsible", None)
|
||||
act.owner = kwargs.get("owner", None)
|
||||
act.last_reviewed_at = kwargs.get("last_reviewed_at", None)
|
||||
act.next_review_at = kwargs.get("next_review_at", None)
|
||||
act.created_by = kwargs.get("created_by", "system")
|
||||
act.dsfa_id = kwargs.get("dsfa_id", None)
|
||||
act.created_at = kwargs.get("created_at", datetime(2026, 1, 15, 10, 0))
|
||||
act.updated_at = kwargs.get("updated_at", None)
|
||||
return act
|
||||
|
||||
|
||||
def _make_db_org(**kwargs):
|
||||
"""Create a mock VVTOrganizationDB object."""
|
||||
org = VVTOrganizationDB()
|
||||
org.id = kwargs.get("id", uuid.uuid4())
|
||||
org.tenant_id = kwargs.get("tenant_id", DEFAULT_TENANT)
|
||||
org.organization_name = kwargs.get("organization_name", "BreakPilot GmbH")
|
||||
org.industry = kwargs.get("industry", "IT")
|
||||
org.locations = kwargs.get("locations", ["Berlin"])
|
||||
org.employee_count = kwargs.get("employee_count", 50)
|
||||
org.dpo_name = kwargs.get("dpo_name", "Max DSB")
|
||||
org.dpo_contact = kwargs.get("dpo_contact", "dsb@example.com")
|
||||
org.vvt_version = kwargs.get("vvt_version", "1.0")
|
||||
org.last_review_date = kwargs.get("last_review_date", None)
|
||||
org.next_review_date = kwargs.get("next_review_date", None)
|
||||
org.review_interval = kwargs.get("review_interval", "annual")
|
||||
org.created_at = kwargs.get("created_at", datetime(2026, 1, 1))
|
||||
org.updated_at = kwargs.get("updated_at", None)
|
||||
return org
|
||||
|
||||
|
||||
def _make_audit_entry(**kwargs):
|
||||
"""Create a mock VVTAuditLogDB object."""
|
||||
entry = VVTAuditLogDB()
|
||||
entry.id = kwargs.get("id", uuid.uuid4())
|
||||
entry.tenant_id = kwargs.get("tenant_id", DEFAULT_TENANT)
|
||||
entry.action = kwargs.get("action", "CREATE")
|
||||
entry.entity_type = kwargs.get("entity_type", "activity")
|
||||
entry.entity_id = kwargs.get("entity_id", uuid.uuid4())
|
||||
entry.changed_by = kwargs.get("changed_by", "system")
|
||||
entry.old_values = kwargs.get("old_values", None)
|
||||
entry.new_values = kwargs.get("new_values", {"name": "Test"})
|
||||
entry.created_at = kwargs.get("created_at", datetime(2026, 1, 15, 10, 0))
|
||||
return entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db():
|
||||
from classroom_engine.database import get_db
|
||||
from compliance.api.tenant_utils import get_tenant_id
|
||||
db = MagicMock()
|
||||
_app.dependency_overrides[get_db] = lambda: db
|
||||
_app.dependency_overrides[get_tenant_id] = lambda: DEFAULT_TENANT
|
||||
yield db
|
||||
_app.dependency_overrides.clear()
|
||||
|
||||
|
||||
class TestExportEndpoint:
|
||||
"""Tests for GET /vvt/export (JSON and CSV)."""
|
||||
|
||||
def test_export_json_with_activities(self, mock_db):
|
||||
act = _make_db_activity(vvt_id="VVT-EXP-001", name="Export Test")
|
||||
org = _make_db_org()
|
||||
# mock chained query for org
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.first.return_value = org
|
||||
# mock chained query for activities
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.all.return_value = [act]
|
||||
|
||||
resp = _client.get("/vvt/export?format=json")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "exported_at" in data
|
||||
assert "organization" in data
|
||||
assert data["organization"]["name"] == "BreakPilot GmbH"
|
||||
assert len(data["activities"]) == 1
|
||||
assert data["activities"][0]["vvt_id"] == "VVT-EXP-001"
|
||||
|
||||
def test_export_json_empty_dataset(self, mock_db):
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.all.return_value = []
|
||||
|
||||
resp = _client.get("/vvt/export?format=json")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["organization"] is None
|
||||
assert data["activities"] == []
|
||||
|
||||
def test_export_csv_returns_streaming_response(self, mock_db):
|
||||
act = _make_db_activity(vvt_id="VVT-CSV-E01", name="CSV Endpoint Test")
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.first.return_value = None
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.all.return_value = [act]
|
||||
|
||||
resp = _client.get("/vvt/export?format=csv")
|
||||
assert resp.status_code == 200
|
||||
assert "text/csv" in resp.headers.get("content-type", "")
|
||||
assert "attachment" in resp.headers.get("content-disposition", "")
|
||||
body = resp.text
|
||||
assert "VVT-CSV-E01" in body
|
||||
assert "CSV Endpoint Test" in body
|
||||
|
||||
def test_export_invalid_format_rejected(self, mock_db):
|
||||
resp = _client.get("/vvt/export?format=xml")
|
||||
assert resp.status_code == 422 # validation error
|
||||
|
||||
|
||||
class TestStatsEndpoint:
|
||||
"""Tests for GET /vvt/stats."""
|
||||
|
||||
def test_stats_empty_tenant(self, mock_db):
|
||||
mock_db.query.return_value.filter.return_value.all.return_value = []
|
||||
|
||||
resp = _client.get("/vvt/stats")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 0
|
||||
assert data["by_status"] == {}
|
||||
assert data["dpia_required_count"] == 0
|
||||
assert data["overdue_review_count"] == 0
|
||||
|
||||
def test_stats_with_activities(self, mock_db):
|
||||
past = datetime(2025, 1, 1, tzinfo=timezone.utc)
|
||||
acts = [
|
||||
_make_db_activity(status="DRAFT", business_function="HR", dpia_required=True, next_review_at=past),
|
||||
_make_db_activity(status="APPROVED", business_function="IT", dpia_required=False),
|
||||
_make_db_activity(status="DRAFT", business_function="HR", dpia_required=False, third_country_transfers=["USA"]),
|
||||
]
|
||||
mock_db.query.return_value.filter.return_value.all.return_value = acts
|
||||
|
||||
resp = _client.get("/vvt/stats")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 3
|
||||
assert data["by_status"]["DRAFT"] == 2
|
||||
assert data["by_status"]["APPROVED"] == 1
|
||||
assert data["by_business_function"]["HR"] == 2
|
||||
assert data["by_business_function"]["IT"] == 1
|
||||
assert data["dpia_required_count"] == 1
|
||||
assert data["third_country_count"] == 1
|
||||
assert data["draft_count"] == 2
|
||||
assert data["approved_count"] == 1
|
||||
assert data["overdue_review_count"] == 1
|
||||
|
||||
|
||||
class TestAuditLogEndpoint:
|
||||
"""Tests for GET /vvt/audit-log."""
|
||||
|
||||
def test_audit_log_returns_entries(self, mock_db):
|
||||
entry = _make_audit_entry(action="CREATE", entity_type="activity")
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [entry]
|
||||
|
||||
resp = _client.get("/vvt/audit-log")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["action"] == "CREATE"
|
||||
assert data[0]["entity_type"] == "activity"
|
||||
|
||||
def test_audit_log_empty(self, mock_db):
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = []
|
||||
|
||||
resp = _client.get("/vvt/audit-log")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == []
|
||||
|
||||
def test_audit_log_pagination_params(self, mock_db):
|
||||
mock_db.query.return_value.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = []
|
||||
|
||||
resp = _client.get("/vvt/audit-log?limit=10&offset=20")
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
class TestVersioningEndpoints:
|
||||
"""Tests for GET /vvt/activities/{id}/versions and /versions/{v}."""
|
||||
|
||||
@patch("compliance.api.versioning_utils.list_versions")
|
||||
def test_list_versions_returns_list(self, mock_list_versions, mock_db):
|
||||
act_id = str(uuid.uuid4())
|
||||
mock_list_versions.return_value = [
|
||||
{"id": str(uuid.uuid4()), "version_number": 2, "status": "draft",
|
||||
"change_summary": "Updated name", "changed_sections": [],
|
||||
"created_by": "admin", "approved_by": None, "approved_at": None,
|
||||
"created_at": "2026-01-15T10:00:00"},
|
||||
{"id": str(uuid.uuid4()), "version_number": 1, "status": "draft",
|
||||
"change_summary": "Initial", "changed_sections": [],
|
||||
"created_by": "system", "approved_by": None, "approved_at": None,
|
||||
"created_at": "2026-01-14T09:00:00"},
|
||||
]
|
||||
|
||||
resp = _client.get(f"/vvt/activities/{act_id}/versions")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data) == 2
|
||||
assert data[0]["version_number"] == 2
|
||||
assert data[1]["version_number"] == 1
|
||||
|
||||
@patch("compliance.api.versioning_utils.list_versions")
|
||||
def test_list_versions_empty(self, mock_list_versions, mock_db):
|
||||
act_id = str(uuid.uuid4())
|
||||
mock_list_versions.return_value = []
|
||||
|
||||
resp = _client.get(f"/vvt/activities/{act_id}/versions")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == []
|
||||
|
||||
@patch("compliance.api.versioning_utils.get_version")
|
||||
def test_get_specific_version(self, mock_get_version, mock_db):
|
||||
act_id = str(uuid.uuid4())
|
||||
mock_get_version.return_value = {
|
||||
"id": str(uuid.uuid4()),
|
||||
"version_number": 1,
|
||||
"status": "approved",
|
||||
"snapshot": {"name": "Test", "status": "APPROVED"},
|
||||
"change_summary": "Initial version",
|
||||
"changed_sections": ["name", "status"],
|
||||
"created_by": "admin",
|
||||
"approved_by": "dpo",
|
||||
"approved_at": "2026-01-16T12:00:00",
|
||||
"created_at": "2026-01-15T10:00:00",
|
||||
}
|
||||
|
||||
resp = _client.get(f"/vvt/activities/{act_id}/versions/1")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["version_number"] == 1
|
||||
assert data["snapshot"]["name"] == "Test"
|
||||
assert data["approved_by"] == "dpo"
|
||||
|
||||
@patch("compliance.api.versioning_utils.get_version")
|
||||
def test_get_version_not_found(self, mock_get_version, mock_db):
|
||||
act_id = str(uuid.uuid4())
|
||||
mock_get_version.return_value = None
|
||||
|
||||
resp = _client.get(f"/vvt/activities/{act_id}/versions/999")
|
||||
assert resp.status_code == 404
|
||||
assert "not found" in resp.json()["detail"].lower()
|
||||
|
||||
|
||||
class TestExportCsvEdgeCases:
|
||||
"""Additional edge cases for CSV export helper."""
|
||||
|
||||
def _collect_csv_body(self, response) -> str:
|
||||
import asyncio
|
||||
async def _read():
|
||||
chunks = []
|
||||
async for chunk in response.body_iterator:
|
||||
chunks.append(chunk)
|
||||
return ''.join(chunks)
|
||||
return asyncio.get_event_loop().run_until_complete(_read())
|
||||
|
||||
def test_export_csv_with_third_country_transfers(self):
|
||||
from compliance.api.vvt_routes import _export_csv
|
||||
act = _make_db_activity(
|
||||
third_country_transfers=["USA", "China"],
|
||||
vvt_id="VVT-TC-001",
|
||||
name="Third Country Test",
|
||||
)
|
||||
response = _export_csv([act])
|
||||
text = self._collect_csv_body(response)
|
||||
assert "Ja" in text # third_country_transfers truthy -> "Ja"
|
||||
|
||||
def test_export_csv_no_third_country_transfers(self):
|
||||
from compliance.api.vvt_routes import _export_csv
|
||||
act = _make_db_activity(
|
||||
third_country_transfers=[],
|
||||
vvt_id="VVT-NTC-001",
|
||||
name="No Third Country",
|
||||
)
|
||||
response = _export_csv([act])
|
||||
text = self._collect_csv_body(response)
|
||||
assert "Nein" in text # empty list -> "Nein"
|
||||
|
||||
def test_export_csv_multiple_activities(self):
|
||||
from compliance.api.vvt_routes import _export_csv
|
||||
acts = [
|
||||
_make_db_activity(vvt_id="VVT-M-001", name="First"),
|
||||
_make_db_activity(vvt_id="VVT-M-002", name="Second"),
|
||||
_make_db_activity(vvt_id="VVT-M-003", name="Third"),
|
||||
]
|
||||
response = _export_csv(acts)
|
||||
text = self._collect_csv_body(response)
|
||||
lines = text.strip().split('\n')
|
||||
# 1 header + 3 data rows
|
||||
assert len(lines) == 4
|
||||
assert "VVT-M-001" in lines[1]
|
||||
assert "VVT-M-002" in lines[2]
|
||||
assert "VVT-M-003" in lines[3]
|
||||
|
||||
def test_export_csv_content_disposition_filename(self):
|
||||
from compliance.api.vvt_routes import _export_csv
|
||||
response = _export_csv([])
|
||||
assert "vvt_export_" in response.headers.get("content-disposition", "")
|
||||
assert ".csv" in response.headers.get("content-disposition", "")
|
||||
|
||||
Reference in New Issue
Block a user