gwoe-antragspruefer/tests/test_domain_behavior.py

155 lines
5.8 KiB
Python
Raw Permalink Normal View History

"""Tests für Domain-Verhalten auf Pydantic-Models (ADR 0008 Tag 4).
Die neuen Methoden auf ``Assessment`` und ``MatrixEntry`` machen
Invarianten aus dem LLM-System-Prompt server-seitig testbar. Sie
werfen (noch) nicht der Hintergrund steht in ADR 0008 Kapitel
Konsequenzen".
"""
from __future__ import annotations
import pytest
from app.models import Assessment, Empfehlung, MatrixEntry, Verbesserungspotenzial
# ─── MatrixEntry.ist_fundamental_kritisch ──────────────────────────────────
class TestMatrixEntryFundamentalKritisch:
@pytest.mark.parametrize("rating,expected", [
(-5, True), (-4, True),
(-3, False), (-1, False), (0, False),
(1, False), (5, False),
])
def test_boundary(self, rating, expected):
m = MatrixEntry(field="D4", label="x", aspect="y", rating=rating)
assert m.ist_fundamental_kritisch() is expected
# ─── MatrixEntry.to_symbol ─────────────────────────────────────────────────
class TestMatrixEntrySymbol:
@pytest.mark.parametrize("rating,expected", [
(5, "++"), (4, "++"),
(3, "+"), (1, "+"),
(0, ""),
(-1, ""), (-3, ""),
(-4, ""), (-5, ""),
])
def test_symbol_from_rating(self, rating, expected):
m = MatrixEntry(field="A1", label="x", aspect="y", rating=rating)
assert m.to_symbol() == expected
# ─── Assessment-Behavior ───────────────────────────────────────────────────
def _make_assessment(*, score: float = 5.0, empfehlung=Empfehlung.UEBERARBEITEN,
matrix_ratings: list[int] | None = None) -> Assessment:
matrix = [
MatrixEntry(field="D4", label="x", aspect="y", rating=r)
for r in (matrix_ratings or [])
]
return Assessment(
drucksache="18/1",
title="Test",
fraktionen=["SPD"],
datum="2024-01-01",
gwoe_score=score,
gwoe_begruendung="Test",
gwoe_matrix=matrix,
gwoe_schwerpunkt=[],
wahlprogramm_scores=[],
empfehlung=empfehlung,
verbesserungspotenzial=Verbesserungspotenzial.MITTEL,
)
class TestAssessmentEmpfehlungHelpers:
def test_ist_ablehnung_true_for_ablehnen(self):
a = _make_assessment(empfehlung=Empfehlung.ABLEHNEN, score=1.0)
assert a.ist_ablehnung() is True
def test_ist_ablehnung_false_otherwise(self):
a = _make_assessment(empfehlung=Empfehlung.UNTERSTUETZEN_MIT)
assert a.ist_ablehnung() is False
def test_ist_uneingeschraenkt(self):
a = _make_assessment(empfehlung=Empfehlung.UNEINGESCHRAENKT, score=9.0)
assert a.ist_uneingeschraenkt_unterstuetzend() is True
def test_ist_uneingeschraenkt_false_for_other(self):
a = _make_assessment(empfehlung=Empfehlung.UEBERARBEITEN)
assert a.ist_uneingeschraenkt_unterstuetzend() is False
class TestAssessmentHatFundamentalKritischesFeld:
def test_empty_matrix(self):
a = _make_assessment(matrix_ratings=[])
assert a.hat_fundamental_kritisches_feld() is False
def test_no_critical_field(self):
a = _make_assessment(matrix_ratings=[2, 3, -1, -3])
assert a.hat_fundamental_kritisches_feld() is False
def test_single_critical_field(self):
a = _make_assessment(matrix_ratings=[2, -4, 1])
assert a.hat_fundamental_kritisches_feld() is True
def test_multiple_critical_fields(self):
a = _make_assessment(matrix_ratings=[-4, -5, -4])
assert a.hat_fundamental_kritisches_feld() is True
class TestAssessmentVerletztScoreCap:
"""Die zentrale Invariante: Bei rating ≤ -4 muss gwoe_score ≤ 3."""
def test_no_critical_field_never_violates(self):
a = _make_assessment(score=10.0, matrix_ratings=[1, 2, -3])
assert a.verletzt_score_cap() is False
def test_critical_field_with_high_score_violates(self):
a = _make_assessment(score=7.0, matrix_ratings=[-4])
assert a.verletzt_score_cap() is True
def test_critical_field_with_capped_score_ok(self):
a = _make_assessment(score=3.0, matrix_ratings=[-4])
assert a.verletzt_score_cap() is False
def test_critical_field_with_score_exactly_cap_ok(self):
"""Boundary: 3.0 ist noch OK, 3.01 wäre Verletzung."""
a = _make_assessment(score=3.0, matrix_ratings=[-5])
assert a.verletzt_score_cap() is False
def test_critical_field_with_score_just_above_cap_violates(self):
a = _make_assessment(score=3.1, matrix_ratings=[-5])
assert a.verletzt_score_cap() is True
class TestAssessmentMethodsCoexistWithSerialization:
"""Sanity-Check: Die neuen Methoden brechen nicht die Pydantic-
Serialisierung, die von der DB/API-Grenze gebraucht wird."""
def test_model_dump_still_works(self):
a = _make_assessment(score=5.0, matrix_ratings=[2, -1])
dumped = a.model_dump(by_alias=True)
assert dumped["gwoeScore"] == 5.0
assert "ist_ablehnung" not in dumped # Methoden leaken nicht in JSON
def test_model_validate_via_alias(self):
data = {
"drucksache": "18/2",
"title": "t",
"fraktionen": [],
"datum": "2024-01-01",
"gwoeScore": 8.0,
"gwoeBegründung": "x",
"gwoeMatrix": [
{"field": "A1", "label": "x", "aspect": "y", "rating": 3}
],
"gwoeSchwerpunkt": [],
"wahlprogrammScores": [],
"empfehlung": "Uneingeschränkt unterstützen",
"verbesserungspotenzial": "gering",
}
a = Assessment.model_validate(data)
assert a.ist_uneingeschraenkt_unterstuetzend() is True