Files
breakpilot-compliance/backend-compliance/tests/test_cra_applicability.py
T
Benjamin Admin 3afb0e7f4d
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 33s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (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 10s
CI / validate-canonical-controls (push) Successful in 12s
CI / detect-changes (push) Successful in 20s
CI / loc-budget (push) Successful in 24s
CI / branch-name (push) Has been skipped
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 3m11s
feat(cra): neutrale Eingangstür-Verdict-Engine (zwingend/ratsam/nicht betroffen)
Reine, deterministische Verdict-Schicht ueber der bestehenden Annex-III/IV-
Klassifikation (kein vierter Klassifizierer): trennt Rechtspflicht von Markt-
Druck. Kern: das Inverkehrbringen (ab 11.12.2027), nicht der Entwicklungs-
zeitpunkt, entscheidet — Bestandsprodukte, die nach der Frist weiter verkauft
werden, fallen unter CRA. Producer-Typen (component/end_device/machine_
integrator/software_app) steuern Default-Annahmen (Anlagenbauer: Vernetzung/OTA
vorausgesetzt) + Verdict-Betonung (Komponente => Markt-Druck). Plus Evidence-
Checkliste (SBOM/VDP/Patch/Lifecycle/Threat-Model/Logging/Auth/Incident) +
Reifegrad. /readiness additiv erweitert (verdict/maturity/digital_elements/
producer_type). 15 Tests gruen. Beispiele: OWIS PS90+, ZwickRoell roboTest.

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

83 lines
3.1 KiB
Python

"""Neutral CRA applicability verdict (Eingangstür): legal duty vs market pull."""
from compliance.services.cra_applicability import (
ZWINGEND, RATSAM, NICHT_BETROFFEN, COMPONENT, MACHINE_INTEGRATOR,
compute_verdict, maturity, in_scope, EVIDENCE_ITEMS,
)
class TestInScope:
def test_digital_element_classes_are_in_scope(self):
for c in ("STANDARD", "IMPORTANT_I", "IMPORTANT_II", "CRITICAL"):
assert in_scope(c) is True
def test_not_in_scope(self):
assert in_scope("NOT_IN_SCOPE") is False
assert in_scope("") is False
class TestVerdict:
def test_zwingend_when_in_scope_and_after_cutoff(self):
v = compute_verdict("STANDARD", True)
assert v["tier"] == ZWINGEND
assert v["in_scope"] is True
def test_ratsam_when_in_scope_but_not_after_cutoff(self):
v = compute_verdict("STANDARD", False)
assert v["tier"] == RATSAM
assert any("Geltungszeitraum" in r for r in v["reasons"])
def test_nicht_betroffen_when_no_digital_elements(self):
v = compute_verdict("NOT_IN_SCOPE", True)
assert v["tier"] == NICHT_BETROFFEN
assert v["in_scope"] is False
def test_unknown_market_date_assumed_after_cutoff(self):
# None = unknown -> conservative -> zwingend
v = compute_verdict("STANDARD", None)
assert v["tier"] == ZWINGEND
assert v["placed_on_market_after_cutoff"] is True
def test_component_has_market_pull_without_explicit_signal(self):
v = compute_verdict("STANDARD", True, producer_type=COMPONENT)
assert v["market_pull"] is True
assert any("Markt-Druck" in r for r in v["reasons"])
def test_customers_request_sets_market_pull(self):
v = compute_verdict("STANDARD", True, producer_type="end_device", customers_request=True)
assert v["market_pull"] is True
def test_end_device_no_signal_no_market_pull(self):
v = compute_verdict("STANDARD", True, producer_type="end_device")
assert v["market_pull"] is False
def test_not_betroffen_with_market_pull_adds_hint_not_pull_reason(self):
v = compute_verdict("NOT_IN_SCOPE", True, producer_type=COMPONENT)
assert v["tier"] == NICHT_BETROFFEN
assert any("anfragen" in r for r in v["reasons"])
def test_class_passthrough_uppercased(self):
v = compute_verdict("important_ii", True)
assert v["cra_class"] == "IMPORTANT_II"
class TestMaturity:
def test_empty_is_zero(self):
m = maturity([])
assert m["pct"] == 0
assert len(m["missing"]) == len(EVIDENCE_ITEMS)
assert m["present"] == []
def test_partial(self):
m = maturity(["sbom", "vdp"])
assert m["pct"] == round(100 * 2 / len(EVIDENCE_ITEMS))
assert {e["key"] for e in m["present"]} == {"sbom", "vdp"}
def test_unknown_keys_ignored(self):
m = maturity(["sbom", "nonsense"])
assert {e["key"] for e in m["present"]} == {"sbom"}
def test_full(self):
m = maturity([e["key"] for e in EVIDENCE_ITEMS])
assert m["pct"] == 100
assert m["missing"] == []