from datetime import date from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from app.services.subscription_billing_calendar import ( advance_billing_periods, billing_date_for_period, is_danish_bank_day, next_billing_date, prorated_30_day_factor, resolve_month_date, validate_billing_schedule, ) import pytest from app.services.subscription_agreement import agreement_status def test_fixed_day_is_resolved_in_target_month(): assert next_billing_date(date(2026, 1, 17), "monthly", "fixed_day", 5) == date(2026, 2, 5) def test_first_bank_day_skips_weekend_and_new_year(): assert resolve_month_date(2026, 1, "first_business_day", None) == date(2026, 1, 2) def test_last_bank_day_skips_new_years_eve(): assert resolve_month_date(2026, 12, "last_business_day", None) == date(2026, 12, 30) def test_bank_holiday_after_ascension_is_closed(): assert not is_danish_bank_day(date(2026, 5, 15)) def test_biweekly_keeps_exact_interval_even_with_month_schedule(): assert next_billing_date(date(2026, 4, 1), "biweekly", "first_business_day", 1) == date(2026, 4, 15) def test_short_intervals_are_forced_to_runnable_anchor_schedule(): assert validate_billing_schedule("daily", "fixed_day", 17) == ("interval_anchor", 17) def test_monthly_interval_anchor_is_rejected(): with pytest.raises(ValueError, match="only valid"): validate_billing_schedule("monthly", "interval_anchor", 1) def test_new_fixed_day_rules_cannot_use_day_29_to_31(): with pytest.raises(ValueError, match="between 1 and 28"): validate_billing_schedule("quarterly", "fixed_day", 31) def test_period_can_be_invoiced_two_months_in_advance(): assert billing_date_for_period(date(2027, 1, 1), 2, "fixed_day", 1) == date(2026, 11, 1) def test_first_invoice_uses_next_scheduled_day_when_period_started_later_in_month(): # A subscription that begins on 31 August and invoices on day 1 must start # on 1 September, not retrospectively on 1 August. assert billing_date_for_period(date(2026, 8, 31), 0, "fixed_day", 1) == date(2026, 9, 1) def test_three_monthly_periods_cover_a_quarter(): assert advance_billing_periods(date(2027, 1, 1), "monthly", 3) == date(2027, 4, 1) def test_short_opening_period_uses_30_day_basis(): assert prorated_30_day_factor(date(2027, 1, 5), date(2027, 2, 1)) == 26 / 30 def test_agreement_status_prioritizes_failures_and_pending_changes(): subscriptions = [{"status": "active"}, {"status": "paused"}] assert agreement_status(subscriptions, [{"status": "failed"}]) == "Kræver handling" assert agreement_status(subscriptions, [{"status": "pending"}]) == "Ændring afventer" assert agreement_status(subscriptions, []) == "Delvist pauseret" def test_agreement_status_handles_partial_termination_and_closed(): assert agreement_status([{"status": "active"}, {"status": "cancelled"}], []) == "Delvist opsagt" assert agreement_status([{"status": "expired"}, {"status": "cancelled"}], []) == "Afsluttet"