94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Unit tests for `modules.shared.dateRange`.
|
|
Pure-Python, no DB / no API.
|
|
"""
|
|
|
|
from datetime import date, datetime, time as dtTime
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from modules.shared.dateRange import (
|
|
daysInRange,
|
|
isoDateRangeToLocalEpoch,
|
|
parseIsoDate,
|
|
parseIsoDateRange,
|
|
)
|
|
|
|
|
|
class TestParseIsoDate:
|
|
def test_validIsoDate(self):
|
|
assert parseIsoDate("2026-04-15", "dateFrom") == date(2026, 4, 15)
|
|
|
|
def test_emptyStringRaises400(self):
|
|
with pytest.raises(HTTPException) as exc:
|
|
parseIsoDate("", "dateFrom")
|
|
assert exc.value.status_code == 400
|
|
assert "dateFrom" in exc.value.detail
|
|
|
|
def test_invalidFormatRaises400(self):
|
|
with pytest.raises(HTTPException) as exc:
|
|
parseIsoDate("15.04.2026", "dateTo")
|
|
assert exc.value.status_code == 400
|
|
assert "dateTo" in exc.value.detail
|
|
assert "15.04.2026" in exc.value.detail
|
|
|
|
def test_nonStringRaises400(self):
|
|
with pytest.raises(HTTPException) as exc:
|
|
parseIsoDate(None, "dateFrom") # type: ignore[arg-type]
|
|
assert exc.value.status_code == 400
|
|
|
|
|
|
class TestParseIsoDateRange:
|
|
def test_validRange(self):
|
|
f, t = parseIsoDateRange("2026-04-01", "2026-04-15")
|
|
assert f == date(2026, 4, 1)
|
|
assert t == date(2026, 4, 15)
|
|
|
|
def test_sameDayIsValid(self):
|
|
f, t = parseIsoDateRange("2026-04-15", "2026-04-15")
|
|
assert f == t
|
|
|
|
def test_invertedRangeRaises400(self):
|
|
with pytest.raises(HTTPException) as exc:
|
|
parseIsoDateRange("2026-04-15", "2026-04-01")
|
|
assert exc.value.status_code == 400
|
|
assert "dateFrom must be <= dateTo" in exc.value.detail
|
|
|
|
|
|
class TestIsoDateRangeToLocalEpoch:
|
|
def test_inclusiveEndOfDay(self):
|
|
"""`dateTo` boundary covers full last day (23:59:59.999999 local)."""
|
|
fromTs, toTs = isoDateRangeToLocalEpoch("2026-04-15", "2026-04-15")
|
|
startOfDay = datetime.combine(date(2026, 4, 15), dtTime.min).timestamp()
|
|
endOfDay = datetime.combine(date(2026, 4, 15), dtTime.max).timestamp()
|
|
assert fromTs == startOfDay
|
|
assert toTs == endOfDay
|
|
# Single-day range covers ~24h - 1 microsecond.
|
|
assert (toTs - fromTs) > (24 * 3600 - 1)
|
|
|
|
def test_multiDayRange(self):
|
|
fromTs, toTs = isoDateRangeToLocalEpoch("2026-04-01", "2026-04-03")
|
|
# Three local days, end-inclusive: ~3 * 86400 seconds (- 1us).
|
|
assert 3 * 86400 - 1 < (toTs - fromTs) < 3 * 86400 + 1
|
|
|
|
def test_invalidRaises400(self):
|
|
with pytest.raises(HTTPException):
|
|
isoDateRangeToLocalEpoch("not-a-date", "2026-04-03")
|
|
|
|
|
|
class TestDaysInRange:
|
|
def test_singleDayIsOne(self):
|
|
assert daysInRange("2026-04-15", "2026-04-15") == 1
|
|
|
|
def test_threeDaysInclusive(self):
|
|
assert daysInRange("2026-04-01", "2026-04-03") == 3
|
|
|
|
def test_yearSpan(self):
|
|
assert daysInRange("2026-01-01", "2026-12-31") == 365
|
|
|
|
def test_invertedRangeRaises400(self):
|
|
with pytest.raises(HTTPException):
|
|
daysInRange("2026-04-15", "2026-04-01")
|