Files
moneyfy/backend/tests/test_recurrence_holidays.py
moneyfyandClaude Opus 5 70d73cf8d3 feat(recurrence): Expansions-Engine für wiederkehrende Posten
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
2026-09-09 13:21:51 +02:00

103 lines
3.7 KiB
Python
Raw Permalink 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 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")