Neue Tests in dieser Migration: - test_database.py (Merkliste-CRUD, Subscriptions, abgeordnetenwatch-Joins) - test_clustering.py (82% Coverage) - test_drucksache_typen.py (100%) - test_mail.py (86%) - test_monitoring.py (23 Tests) - test_abgeordnetenwatch.py (23 Tests, inkl. Drucksache-Extraction) - test_redline_parser.py (20 Tests fuer §INS§/§DEL§-Marker) - test_bug_regressions.py (PRAGMA, JWT-azp, CDU-PDF, PFLICHT-FRAKTIONEN, NRW-Titel) - test_embeddings_v3_v4.py (WRITE/READ-Pattern) - test_wahlprogramm_check.py (#128) - test_wahlprogramm_fetch.py (#138) - test_antrag/bewertung/abonnement_repository.py + test_llm_bewerter.py (DDD) - test_domain_behavior.py (5 Domain-Methoden boundary tests) - tests/e2e/test_ui.py (Playwright) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
|