"""Tests for date utility functions.""" import datetime from app.utils.dates import add_months, age_in_months, fourth_birthday class TestAddMonths: """Test add_months function.""" def test_add_positive_months(self) -> None: """Adding positive months increments the month correctly.""" date = datetime.date(2026, 1, 15) result = add_months(date, 1) assert result == datetime.date(2026, 2, 15) def test_add_multiple_months(self) -> None: """Adding multiple months increments year when needed.""" date = datetime.date(2026, 11, 15) result = add_months(date, 2) assert result == datetime.date(2027, 1, 15) def test_add_12_months(self) -> None: """Adding exactly 12 months increments year only.""" date = datetime.date(2026, 3, 15) result = add_months(date, 12) assert result == datetime.date(2027, 3, 15) def test_add_many_months(self) -> None: """Adding many months works correctly.""" date = datetime.date(2026, 1, 15) result = add_months(date, 48) # 4 years assert result == datetime.date(2030, 1, 15) def test_subtract_months(self) -> None: """Subtracting months (negative n) works correctly.""" date = datetime.date(2026, 3, 15) result = add_months(date, -1) assert result == datetime.date(2026, 2, 15) def test_subtract_year(self) -> None: """Subtracting 12 months decrements year.""" date = datetime.date(2026, 3, 15) result = add_months(date, -12) assert result == datetime.date(2025, 3, 15) def test_subtract_across_year_boundary(self) -> None: """Subtracting months crossing year boundary works.""" date = datetime.date(2026, 1, 15) result = add_months(date, -2) assert result == datetime.date(2025, 11, 15) def test_month_end_handling_jan_to_feb_leap_year(self) -> None: """Jan 31 + 1 month = Feb 29 in leap year.""" date = datetime.date(2024, 1, 31) # 2024 is a leap year result = add_months(date, 1) assert result == datetime.date(2024, 2, 29) def test_month_end_handling_jan_to_feb_non_leap_year(self) -> None: """Jan 31 + 1 month = Feb 28 in non-leap year.""" date = datetime.date(2025, 1, 31) # 2025 is not a leap year result = add_months(date, 1) assert result == datetime.date(2025, 2, 28) def test_month_end_handling_mar_to_feb(self) -> None: """Mar 31 - 1 month = Feb 28/29.""" date = datetime.date(2025, 3, 31) result = add_months(date, -1) assert result == datetime.date(2025, 2, 28) def test_month_end_handling_may_to_apr(self) -> None: """May 31 - 1 month = Apr 30.""" date = datetime.date(2026, 5, 31) result = add_months(date, -1) assert result == datetime.date(2026, 4, 30) def test_add_zero_months(self) -> None: """Adding 0 months returns the same date.""" date = datetime.date(2026, 6, 15) result = add_months(date, 0) assert result == date def test_leap_year_edge_case(self) -> None: """Feb 29 in leap year + 1 year = Feb 28 in non-leap year.""" date = datetime.date(2024, 2, 29) result = add_months(date, 12) assert result == datetime.date(2025, 2, 28) def test_leap_year_century_2000_is_leap(self) -> None: """Year 2000 is a leap year (divisible by 400).""" date = datetime.date(2000, 1, 31) result = add_months(date, 1) assert result == datetime.date(2000, 2, 29) def test_leap_year_century_1900_not_leap(self) -> None: """Year 1900 is not a leap year (divisible by 100 but not 400).""" date = datetime.date(1900, 1, 31) result = add_months(date, 1) assert result == datetime.date(1900, 2, 28) class TestAgeInMonths: """Test age_in_months function.""" def test_same_day(self) -> None: """Same birth and reference date = 0 months.""" birth = datetime.date(2026, 1, 15) on = datetime.date(2026, 1, 15) assert age_in_months(birth, on) == 0 def test_one_month_later(self) -> None: """One month after birth date.""" birth = datetime.date(2026, 1, 15) on = datetime.date(2026, 2, 15) assert age_in_months(birth, on) == 1 def test_one_month_not_complete(self) -> None: """Day before one-month anniversary.""" birth = datetime.date(2026, 1, 15) on = datetime.date(2026, 2, 14) assert age_in_months(birth, on) == 0 def test_twelve_months(self) -> None: """Exactly one year.""" birth = datetime.date(2026, 1, 15) on = datetime.date(2027, 1, 15) assert age_in_months(birth, on) == 12 def test_48_months(self) -> None: """Exactly 4 years (48 months).""" birth = datetime.date(2026, 1, 15) on = datetime.date(2030, 1, 15) assert age_in_months(birth, on) == 48 def test_month_boundary_not_reached(self) -> None: """Before the day of month anniversary.""" birth = datetime.date(2026, 1, 31) on = datetime.date(2026, 2, 28) assert age_in_months(birth, on) == 0 def test_month_boundary_reached(self) -> None: """On or after the day of month anniversary.""" birth = datetime.date(2026, 1, 31) on = datetime.date(2026, 2, 28) assert age_in_months(birth, on) == 0 def test_month_boundary_on_31st_to_mar(self) -> None: """Jan 31 to Mar 31.""" birth = datetime.date(2026, 1, 31) on = datetime.date(2026, 3, 31) assert age_in_months(birth, on) == 2 def test_negative_age(self) -> None: """Reference date before birth date (not yet born).""" birth = datetime.date(2026, 6, 15) on = datetime.date(2026, 1, 15) assert age_in_months(birth, on) == -5 def test_leap_year_boundary(self) -> None: """Born on Feb 29 (leap year).""" birth = datetime.date(2024, 2, 29) on = datetime.date(2025, 2, 28) assert age_in_months(birth, on) == 11 def test_leap_year_boundary_anniversary(self) -> None: """Born on Feb 29; checking on Mar 1 (anniversary passed).""" birth = datetime.date(2024, 2, 29) on = datetime.date(2025, 3, 1) assert age_in_months(birth, on) == 12 class TestFourthBirthday: """Test fourth_birthday function.""" def test_regular_date(self) -> None: """Fourth birthday calculation.""" birth = datetime.date(2026, 6, 15) result = fourth_birthday(birth) assert result == datetime.date(2030, 6, 15) def test_leap_year_feb_29(self) -> None: """Born on Feb 29; fourth birthday in non-leap year.""" birth = datetime.date(2024, 2, 29) result = fourth_birthday(birth) assert result == datetime.date(2028, 2, 29) def test_jan_31(self) -> None: """Born on Jan 31; fourth birthday.""" birth = datetime.date(2022, 1, 31) result = fourth_birthday(birth) assert result == datetime.date(2026, 1, 31) def test_dec_31(self) -> None: """Born on Dec 31; fourth birthday crosses year.""" birth = datetime.date(2022, 12, 31) result = fourth_birthday(birth) assert result == datetime.date(2026, 12, 31) def test_fourth_birthday_is_48_months_later(self) -> None: """Verify fourth birthday is exactly 48 months after birth.""" birth = datetime.date(2020, 5, 10) result = fourth_birthday(birth) assert age_in_months(birth, result) == 48