feat(ticket): update email integration to use new priority constants for ticket classification feat(procurement): add procurement overview page with dynamic data loading and display test(subscriptions): add tests for billing calendar to ensure correct invoice dates feat(reminder): implement automated task lists with user-defined rules for reminders feat(migrations): create tables for managing delefiber product prices and mobile recorder provisioning history test(mobile_recorder): add tests for provisioning mobile recorders to ensure correct asset creation and updates
82 lines
3.0 KiB
Python
82 lines
3.0 KiB
Python
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"
|