Files
breakpilot-compliance/backend-compliance/tests/test_risk_routes.py
Benjamin Admin bd9796725a
All checks were successful
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) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 21s
feat(compliance-kern): Tests, MkDocs + RAG-Controls Button für Anforderungen/Controls/Nachweise/Risiken
- 74 neue Tests (test_risk_routes, test_evidence_routes, test_requirement_routes, test_control_routes)
  Enum-Mocking (.value), ControlStatusEnum-Validierung, db.query() direkte Mocks
- MkDocs: docs-src/services/sdk-modules/compliance-kern.md
  Endpunkt-Tabellen, Schema-Erklärungen, CI/CD-Beispiele, Risikomatrix
- controls/page.tsx: "KI-Controls aus RAG vorschlagen" Button
  POST /api/sdk/v1/compliance/ai/suggest-controls, Suggestion-Panel,
  Requirement-ID-Eingabe + Dropdown, Konfidenz-Anzeige, Hinzufügen-Aktion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:43:02 +01:00

246 lines
8.9 KiB
Python

"""Tests for Risk management routes (risk_routes.py)."""
from datetime import datetime, date
from unittest.mock import MagicMock, patch
from fastapi import FastAPI
from fastapi.testclient import TestClient
from compliance.api.risk_routes import router as risk_router
from classroom_engine.database import get_db
# ---------------------------------------------------------------------------
# App setup with mocked DB dependency
# ---------------------------------------------------------------------------
app = FastAPI()
app.include_router(risk_router)
mock_db = MagicMock()
def override_get_db():
yield mock_db
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)
RISK_UUID = "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb"
NOW = datetime(2024, 3, 1, 12, 0, 0)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def make_risk(overrides=None):
r = MagicMock()
r.id = RISK_UUID
r.risk_id = "RISK-001"
r.title = "Datenleck durch unsichere API"
r.description = "API ohne Auth"
r.category = "data_breach"
r.likelihood = 3
r.impact = 4
# inherent_risk and residual_risk are Enum → need .value
r.inherent_risk = MagicMock()
r.inherent_risk.value = "high"
r.residual_likelihood = 2
r.residual_impact = 3
r.residual_risk = MagicMock()
r.residual_risk.value = "medium"
r.status = "open"
r.mitigating_controls = ["TOM-001"]
r.owner = "CISO"
r.treatment_plan = "API absichern"
r.identified_date = date(2024, 1, 1)
r.review_date = date(2024, 6, 1)
r.last_assessed_at = NOW
r.created_at = NOW
r.updated_at = NOW
if overrides:
for k, v in overrides.items():
setattr(r, k, v)
return r
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestListRisks:
"""Tests for GET /risks."""
def test_list_empty(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_all.return_value = []
response = client.get("/risks")
assert response.status_code == 200
data = response.json()
assert data["risks"] == []
assert data["total"] == 0
def test_list_with_risk(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_all.return_value = [make_risk()]
response = client.get("/risks")
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
r = data["risks"][0]
assert r["risk_id"] == "RISK-001"
assert r["title"] == "Datenleck durch unsichere API"
assert r["inherent_risk"] == "high"
assert r["status"] == "open"
def test_list_filter_category(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_all.return_value = [make_risk()]
response = client.get("/risks", params={"category": "data_breach"})
assert response.status_code == 200
assert response.json()["total"] == 1
def test_list_filter_status(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_all.return_value = []
response = client.get("/risks", params={"status": "mitigated"})
assert response.status_code == 200
def test_list_filter_risk_level(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_all.return_value = [make_risk()]
response = client.get("/risks", params={"risk_level": "high"})
assert response.status_code == 200
def test_list_multiple(self):
r2 = make_risk()
r2.id = "bbbbbbbb-2222-2222-2222-bbbbbbbbbbbb"
r2.risk_id = "RISK-002"
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_all.return_value = [make_risk(), r2]
response = client.get("/risks")
assert response.status_code == 200
assert response.json()["total"] == 2
class TestCreateRisk:
"""Tests for POST /risks."""
def test_create_success(self):
risk = make_risk()
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.create.return_value = risk
response = client.post("/risks", json={
"risk_id": "RISK-001",
"title": "Datenleck durch unsichere API",
"category": "data_breach",
"likelihood": 3,
"impact": 4,
})
assert response.status_code == 200
data = response.json()
assert data["risk_id"] == "RISK-001"
assert data["inherent_risk"] == "high"
def test_create_missing_required_fields(self):
"""Missing risk_id → 422."""
response = client.post("/risks", json={
"title": "Ohne risk_id",
})
assert response.status_code == 422
def test_create_likelihood_out_of_range(self):
"""likelihood > 5 → 422."""
response = client.post("/risks", json={
"risk_id": "R-999",
"title": "Test",
"category": "test",
"likelihood": 6,
"impact": 3,
})
assert response.status_code == 422
class TestUpdateRisk:
"""Tests for PUT /risks/{risk_id}."""
def test_update_success(self):
updated = make_risk()
updated.title = "Aktualisiertes Risiko"
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
repo = MockRepo.return_value
# Update route uses get_by_risk_id (the risk_id string, not UUID)
repo.get_by_risk_id.return_value = make_risk()
repo.update.return_value = updated
response = client.put("/risks/RISK-001", json={"title": "Aktualisiertes Risiko"})
assert response.status_code == 200
assert response.json()["title"] == "Aktualisiertes Risiko"
def test_update_not_found(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_by_risk_id.return_value = None
response = client.put("/risks/RISK-999", json={"title": "Test"})
assert response.status_code == 404
def test_update_status_change(self):
updated = make_risk()
updated.status = "closed"
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
repo = MockRepo.return_value
repo.get_by_risk_id.return_value = make_risk()
repo.update.return_value = updated
response = client.put("/risks/RISK-001", json={"status": "closed"})
assert response.status_code == 200
assert response.json()["status"] == "closed"
class TestDeleteRisk:
"""Tests for DELETE /risks/{risk_id}."""
def test_delete_success(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
repo = MockRepo.return_value
repo.get_by_risk_id.return_value = make_risk()
# Delete uses db.delete(risk) directly
response = client.delete("/risks/RISK-001")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
def test_delete_not_found(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
MockRepo.return_value.get_by_risk_id.return_value = None
response = client.delete("/risks/RISK-999")
assert response.status_code == 404
class TestRiskMatrix:
"""Tests for GET /risks/matrix."""
def test_matrix_returns_structure(self):
# Schema: Dict[str, Dict[str, List[str]]] → {likelihood: {impact: [risk_ids]}}
matrix_data = {
"3": {"4": ["RISK-001"]},
"1": {"1": []},
}
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
repo = MockRepo.return_value
repo.get_risk_matrix.return_value = matrix_data
repo.get_all.return_value = [make_risk()]
response = client.get("/risks/matrix")
assert response.status_code == 200
data = response.json()
assert "matrix" in data
assert "risks" in data
assert len(data["risks"]) == 1
def test_matrix_empty(self):
with patch("compliance.api.risk_routes.RiskRepository") as MockRepo:
repo = MockRepo.return_value
repo.get_risk_matrix.return_value = {}
repo.get_all.return_value = []
response = client.get("/risks/matrix")
assert response.status_code == 200
data = response.json()
assert data["risks"] == []