"""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