Files
breakpilot-compliance/backend-compliance/tests/test_cra_meldewesen.py
T
Benjamin Bönisch 8f21650d74
CI / detect-changes (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 15s
CI / validate-canonical-controls (push) Successful in 13s
CI / loc-budget (push) Successful in 25s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m9s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 31s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
feat(sdk): Kunden-Dokumente + CRA-Meldewesen, Screening aus Frontend genommen
- /sdk/dokumente: Kundensicht nur auf veroeffentlichte Rechtsdokumente
  (Ansehen + Download); Proxy mit Allow-List nur /public — Templates/Drafts/
  Generator bleiben unerreichbar.
- /sdk/cra-meldewesen: CRA Art. 14 Meldewesen (24h/72h/14d-Kaskade) mit
  Fristen-Tracking + ENISA-SRP-Export-Entwurf (kein Live-API). Backend:
  cra_meldewesen (pure, getestet) + cra_incident_store (schema-neutral ueber
  compliance_cra_documents) + /api/v1/cra/incidents (additiv, contract-safe).
- Screening (Self-Scan) aus dem Frontend genommen: Flow-Stepper-Eintrag
  ausgeblendet (visibleWhen), Dashboard-Kachel + Import-Button entfernt.
  Repo-Scanning laeuft extern im Compliance-Scanner; Backend-Router bleibt
  vorerst gemountet (Contract-Stabilitaet).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 21:21:28 +02:00

88 lines
3.7 KiB
Python

"""CRA Art. 14 reporting cascade — pure deadline + ENISA-export logic."""
import pytest
from compliance.services.cra_meldewesen import (
compute_deadlines, next_open_stage, build_enisa_report, report_completeness,
)
AWARE = "2026-09-15T08:00:00+00:00"
class TestDeadlines:
def test_due_times_are_24h_72h_14d_after_awareness(self):
d = compute_deadlines(AWARE, now_iso=AWARE)
by = {x["key"]: x for x in d}
assert by["early_warning"]["due_at"] == "2026-09-16T08:00:00+00:00"
assert by["notification"]["due_at"] == "2026-09-18T08:00:00+00:00"
assert by["final"]["due_at"] == "2026-09-29T08:00:00+00:00"
def test_all_pending_right_after_awareness(self):
d = compute_deadlines(AWARE, now_iso="2026-09-15T09:00:00+00:00")
assert all(x["status"] == "pending" for x in d)
def test_overdue_when_now_past_due_and_unsubmitted(self):
d = compute_deadlines(AWARE, now_iso="2026-09-17T00:00:00+00:00")
by = {x["key"]: x for x in d}
assert by["early_warning"]["status"] == "overdue" # 24h passed
assert by["notification"]["status"] == "pending" # 72h not yet
def test_due_soon_within_window(self):
# 4h before the 24h deadline → due_soon
d = compute_deadlines(AWARE, now_iso="2026-09-16T04:00:00+00:00")
by = {x["key"]: x for x in d}
assert by["early_warning"]["status"] == "due_soon"
def test_submitted_overrides_timing(self):
d = compute_deadlines(
AWARE, submissions={"early_warning": "2026-09-16T07:00:00+00:00"},
now_iso="2026-09-20T00:00:00+00:00")
by = {x["key"]: x for x in d}
assert by["early_warning"]["status"] == "submitted"
assert by["notification"]["status"] == "overdue"
def test_next_open_stage(self):
d = compute_deadlines(AWARE, submissions={"early_warning": AWARE}, now_iso=AWARE)
assert next_open_stage(d) == "notification"
d2 = compute_deadlines(
AWARE, submissions={k: AWARE for k in ("early_warning", "notification", "final")},
now_iso=AWARE)
assert next_open_stage(d2) is None
class TestEnisaReport:
def _incident(self):
return {
"manufacturer": "OWIS GmbH", "product_name": "PS 90+", "product_version": "2.1",
"kind": "exploited_vulnerability", "severity": "high", "aware_at": AWARE,
"summary": "Aktiv ausgenutzte Auth-Umgehung", "contact": "psirt@owis.eu",
"impact": "Fernsteuerung möglich", "root_cause": "fehlende Tokenprüfung",
"patch_available": True,
}
def test_early_warning_has_base_not_detail(self):
r = build_enisa_report(self._incident(), "early_warning")
assert r["manufacturer"] == "OWIS GmbH" and r["severity"] == "high"
assert "impact" not in r and "root_cause" not in r
assert r["submission_target"].startswith("ENISA")
def test_notification_adds_impact_not_rootcause(self):
r = build_enisa_report(self._incident(), "notification")
assert r["impact"] == "Fernsteuerung möglich"
assert "root_cause" not in r
def test_final_adds_root_cause_and_patch(self):
r = build_enisa_report(self._incident(), "final")
assert r["root_cause"] == "fehlende Tokenprüfung"
assert r["patch_available"] is True
def test_unknown_stage_raises(self):
with pytest.raises(ValueError):
build_enisa_report(self._incident(), "nonsense")
def test_completeness_flags_missing(self):
thin = {"manufacturer": "X", "aware_at": AWARE}
c = report_completeness(thin, "early_warning")
assert not c["complete"]
assert "product_name" in c["missing"]
assert "manufacturer" in c["filled"]