Datenbankfreie Kernberechnung auf Basis schlanker Protokolle, sodass sowohl ORM-Objekte als auch Testdatenklassen verarbeitet werden. - expand() mit voller RFC-5545-Unterstützung, Fensterbegrenzung und Overlays - expand_by_due_date() für Sichten, die nach dem tatsächlichen Zahltag gruppieren - Werktagsverschiebung über den NRW-Feiertagskalender, nominales Datum als Schlüssel - Preishistorie, Ratenzahlungen, Vertragsfristen und Rücklagenberechnung - validate_rrule() und next_dates() für Eingabeprüfung und Terminvorschau - 81 Unit-Tests, davon alle in Abschnitt 2 geforderten Fälle Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
103 lines
3.7 KiB
Python
103 lines
3.7 KiB
Python
"""Tests der Wochenend- und Feiertagsverschiebung (Region NRW)."""
|
||
|
||
import pytest
|
||
|
||
from app.models.enums import BusinessDayShift
|
||
from app.services.recurrence import (
|
||
RecurrenceError,
|
||
expand,
|
||
is_business_day,
|
||
shift_to_business_day,
|
||
)
|
||
from tests.factories import FakeRecurrence, d
|
||
|
||
|
||
def test_fronleichnam_ist_in_nrw_kein_werktag() -> None:
|
||
"""Fronleichnam fällt 2026 auf Donnerstag, den 04.06. – in NRW ein Feiertag."""
|
||
assert is_business_day(d("2026-06-04"), "DE-NW") is False
|
||
assert is_business_day(d("2026-06-03"), "DE-NW") is True
|
||
assert is_business_day(d("2026-06-05"), "DE-NW") is True
|
||
|
||
|
||
def test_fronleichnam_ist_in_niedersachsen_ein_werktag() -> None:
|
||
"""Gegenprobe: derselbe Tag ist in einem anderen Bundesland ganz normal."""
|
||
assert is_business_day(d("2026-06-04"), "DE-NI") is True
|
||
|
||
|
||
def test_verschiebung_ueber_fronleichnam_nach_hinten() -> None:
|
||
recurrence = FakeRecurrence(
|
||
rrule="FREQ=MONTHLY;BYMONTHDAY=4",
|
||
dtstart=d("2026-06-04"),
|
||
business_day_shift=BusinessDayShift.NEXT,
|
||
)
|
||
|
||
result = expand(recurrence, d("2026-06-01"), d("2026-06-30"))
|
||
|
||
assert result[0].nominal_date == d("2026-06-04")
|
||
assert result[0].due_date == d("2026-06-05")
|
||
|
||
|
||
def test_verschiebung_ueber_fronleichnam_nach_vorne() -> None:
|
||
recurrence = FakeRecurrence(
|
||
rrule="FREQ=MONTHLY;BYMONTHDAY=4",
|
||
dtstart=d("2026-06-04"),
|
||
business_day_shift=BusinessDayShift.PREVIOUS,
|
||
)
|
||
|
||
result = expand(recurrence, d("2026-06-01"), d("2026-06-30"))
|
||
|
||
assert result[0].due_date == d("2026-06-03")
|
||
|
||
|
||
def test_ohne_verschiebung_bleibt_das_datum_stehen() -> None:
|
||
recurrence = FakeRecurrence(
|
||
rrule="FREQ=MONTHLY;BYMONTHDAY=4",
|
||
dtstart=d("2026-06-04"),
|
||
business_day_shift=BusinessDayShift.NONE,
|
||
)
|
||
|
||
result = expand(recurrence, d("2026-06-01"), d("2026-06-30"))
|
||
|
||
assert result[0].due_date == d("2026-06-04")
|
||
|
||
|
||
def test_neujahr_und_wochenende_werden_zusammen_uebersprungen() -> None:
|
||
"""01.01.2026 ist Donnerstag und Feiertag, der 02.01. ein normaler Freitag."""
|
||
assert shift_to_business_day(d("2026-01-01"), BusinessDayShift.NEXT) == d("2026-01-02")
|
||
|
||
|
||
def test_verschiebung_ueberspringt_mehrere_tage_am_stueck() -> None:
|
||
"""01.05.2026 ist ein Freitag und Feiertag – der nächste Werktag ist Montag."""
|
||
assert shift_to_business_day(d("2026-05-01"), BusinessDayShift.NEXT) == d("2026-05-04")
|
||
|
||
|
||
def test_weihnachten_verschiebt_rueckwaerts_ueber_mehrere_tage() -> None:
|
||
"""25.12.2026 Fr und 26.12. Sa sind Feiertage; rückwärts landet man am 24.12. (Do)."""
|
||
assert shift_to_business_day(d("2026-12-25"), BusinessDayShift.PREVIOUS) == d("2026-12-24")
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("tag", "erwartet"),
|
||
[
|
||
(d("2026-01-03"), False), # Samstag
|
||
(d("2026-01-04"), False), # Sonntag
|
||
(d("2026-01-05"), True), # Montag
|
||
(d("2026-04-03"), False), # Karfreitag
|
||
(d("2026-04-06"), False), # Ostermontag
|
||
(d("2026-05-14"), False), # Christi Himmelfahrt
|
||
(d("2026-05-25"), False), # Pfingstmontag
|
||
(d("2026-10-03"), False), # Tag der Deutschen Einheit (Samstag)
|
||
(d("2026-11-01"), False), # Allerheiligen (Sonntag, NRW)
|
||
(d("2026-10-31"), False), # Reformationstag – in NRW kein Feiertag, aber Samstag
|
||
(d("2026-08-15"), False), # Mariä Himmelfahrt – in NRW kein Feiertag, aber Samstag
|
||
(d("2026-08-17"), True), # Montag danach
|
||
],
|
||
)
|
||
def test_werktagserkennung_nrw(tag, erwartet: bool) -> None:
|
||
assert is_business_day(tag, "DE-NW") is erwartet
|
||
|
||
|
||
def test_unbekannte_region_meldet_fehler() -> None:
|
||
with pytest.raises(RecurrenceError):
|
||
is_business_day(d("2026-01-05"), "XX-YY")
|