155 lines
5.8 KiB
Python
155 lines
5.8 KiB
Python
|
|
"""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
|