gwoe-antragspruefer/tests/test_domain_behavior.py
Dotty Dotter 2902164eff test: 467 -> 574 Tests (+107) — DDD, abgeordnetenwatch, monitoring, v2, Bug-Regressions
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>
2026-04-25 20:55:57 +02:00

155 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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