All checks were successful
CI / lint-and-test (push) Successful in 21s
- Introduced a new `normalize_lang` function to standardize language codes across the application, ensuring consistent handling of user language preferences. - Refactored date handling utilities by adding `parse_utc_iso` and `parse_utc_iso_naive` functions for better parsing of ISO 8601 date strings, enhancing timezone awareness. - Updated various modules to utilize the new language normalization and date parsing functions, improving code clarity and maintainability. - Enhanced error handling in date validation to raise specific `DateRangeValidationError` exceptions, providing clearer feedback on validation issues. - Improved test coverage for date range validation and language normalization functionalities, ensuring robustness and reliability.
109 lines
2.9 KiB
Python
109 lines
2.9 KiB
Python
"""Unit tests for utils (dates, user, handover)."""
|
|
|
|
from datetime import date
|
|
|
|
import pytest
|
|
|
|
from duty_teller.utils.dates import (
|
|
DateRangeValidationError,
|
|
day_start_iso,
|
|
day_end_iso,
|
|
duty_to_iso,
|
|
parse_iso_date,
|
|
validate_date_range,
|
|
)
|
|
from duty_teller.utils.user import build_full_name
|
|
from duty_teller.utils.handover import parse_handover_time
|
|
|
|
|
|
# --- dates ---
|
|
|
|
|
|
def test_day_start_iso():
|
|
assert day_start_iso(date(2026, 2, 18)) == "2026-02-18T00:00:00Z"
|
|
|
|
|
|
def test_day_end_iso():
|
|
assert day_end_iso(date(2026, 2, 18)) == "2026-02-18T23:59:59Z"
|
|
|
|
|
|
def test_duty_to_iso():
|
|
assert duty_to_iso(date(2026, 2, 18), 6, 0) == "2026-02-18T06:00:00Z"
|
|
|
|
|
|
def test_parse_iso_date_valid():
|
|
assert parse_iso_date("2026-02-18") == date(2026, 2, 18)
|
|
assert parse_iso_date(" 2026-02-18 ") == date(2026, 2, 18)
|
|
|
|
|
|
def test_parse_iso_date_invalid():
|
|
assert parse_iso_date("") is None
|
|
assert parse_iso_date("2026-02-31") is None # invalid day
|
|
assert parse_iso_date("18-02-2026") is None
|
|
assert parse_iso_date("not-a-date") is None
|
|
|
|
|
|
def test_validate_date_range_ok():
|
|
validate_date_range("2025-01-01", "2025-01-31") # no raise
|
|
|
|
|
|
def test_validate_date_range_bad_format():
|
|
with pytest.raises(DateRangeValidationError) as exc_info:
|
|
validate_date_range("01-01-2025", "2025-01-31")
|
|
assert exc_info.value.kind == "bad_format"
|
|
with pytest.raises(DateRangeValidationError) as exc_info:
|
|
validate_date_range("2025-01-01", "invalid")
|
|
assert exc_info.value.kind == "bad_format"
|
|
|
|
|
|
def test_validate_date_range_from_after_to():
|
|
with pytest.raises(DateRangeValidationError) as exc_info:
|
|
validate_date_range("2025-02-01", "2025-01-01")
|
|
assert exc_info.value.kind == "from_after_to"
|
|
|
|
|
|
# --- user ---
|
|
|
|
|
|
def test_build_full_name_both():
|
|
assert build_full_name("John", "Doe") == "John Doe"
|
|
|
|
|
|
def test_build_full_name_first_only():
|
|
assert build_full_name("John", None) == "John"
|
|
|
|
|
|
def test_build_full_name_last_only():
|
|
assert build_full_name(None, "Doe") == "Doe"
|
|
|
|
|
|
def test_build_full_name_empty():
|
|
assert build_full_name("", "") == "User"
|
|
assert build_full_name(None, None) == "User"
|
|
|
|
|
|
# --- handover ---
|
|
|
|
|
|
def test_parse_handover_utc():
|
|
assert parse_handover_time("09:00") == (9, 0)
|
|
assert parse_handover_time("09:00 UTC") == (9, 0)
|
|
assert parse_handover_time(" 06:30 ") == (6, 30)
|
|
|
|
|
|
def test_parse_handover_with_tz():
|
|
# Europe/Moscow UTC+3 in winter: 09:00 Moscow = 06:00 UTC
|
|
assert parse_handover_time("09:00 Europe/Moscow") == (6, 0)
|
|
|
|
|
|
def test_parse_handover_invalid():
|
|
assert parse_handover_time("") is None
|
|
assert parse_handover_time("not a time") is None
|
|
# 25:00 is normalized to 1:00 by hour % 24; use non-matching string
|
|
assert parse_handover_time("12") is None
|
|
|
|
|
|
def test_parse_handover_invalid_timezone_returns_none():
|
|
"""Invalid IANA timezone string -> ZoneInfo raises, returns None."""
|
|
assert parse_handover_time("09:00 NotAReal/Timezone") is None
|