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