"""Tests for app.utils.time_utils.""" import datetime import time from app.utils.time_utils import ( add_minutes, hours_ago, is_expired, since_unix, utc_from_timestamp, utc_now, ) from app.utils.constants import TIME_RANGE_SLACK_SECONDS class TestUtcNow: """Tests for :func:`utc_now`.""" def test_utc_now_returns_timezone_aware_datetime(self) -> None: result = utc_now() assert result.tzinfo is not None def test_utc_now_timezone_is_utc(self) -> None: result = utc_now() assert result.tzinfo == datetime.UTC def test_utc_now_is_recent(self) -> None: before = datetime.datetime.now(datetime.UTC) result = utc_now() after = datetime.datetime.now(datetime.UTC) assert before <= result <= after class TestUtcFromTimestamp: """Tests for :func:`utc_from_timestamp`.""" def test_utc_from_timestamp_epoch_returns_utc_epoch(self) -> None: result = utc_from_timestamp(0.0) assert result == datetime.datetime(1970, 1, 1, tzinfo=datetime.UTC) def test_utc_from_timestamp_returns_aware_datetime(self) -> None: result = utc_from_timestamp(1_000_000_000.0) assert result.tzinfo is not None class TestAddMinutes: """Tests for :func:`add_minutes`.""" def test_add_minutes_positive(self) -> None: dt = datetime.datetime(2024, 1, 1, 12, 0, 0, tzinfo=datetime.UTC) result = add_minutes(dt, 30) expected = datetime.datetime(2024, 1, 1, 12, 30, 0, tzinfo=datetime.UTC) assert result == expected def test_add_minutes_negative(self) -> None: dt = datetime.datetime(2024, 1, 1, 12, 0, 0, tzinfo=datetime.UTC) result = add_minutes(dt, -60) expected = datetime.datetime(2024, 1, 1, 11, 0, 0, tzinfo=datetime.UTC) assert result == expected class TestIsExpired: """Tests for :func:`is_expired`.""" def test_is_expired_past_timestamp_returns_true(self) -> None: past = datetime.datetime(2000, 1, 1, tzinfo=datetime.UTC) assert is_expired(past) is True def test_is_expired_future_timestamp_returns_false(self) -> None: future = datetime.datetime(2099, 1, 1, tzinfo=datetime.UTC) assert is_expired(future) is False class TestHoursAgo: """Tests for :func:`hours_ago`.""" def test_hours_ago_returns_past_datetime(self) -> None: result = hours_ago(24) assert result < utc_now() def test_hours_ago_correct_delta(self) -> None: before = utc_now() result = hours_ago(1) after = utc_now() expected_min = before - datetime.timedelta(hours=1, seconds=1) expected_max = after - datetime.timedelta(hours=1) + datetime.timedelta(seconds=1) assert expected_min <= result <= expected_max class TestSinceUnix: """Tests for :func:`since_unix`.""" def test_since_unix_24h_returns_unix_timestamp(self) -> None: """Verify since_unix returns an integer timestamp.""" result = since_unix("24h") assert isinstance(result, int) def test_since_unix_24h_is_roughly_24_hours_ago(self) -> None: """Verify 24h preset returns a timestamp ~24 hours in the past.""" before = int(time.time()) result = since_unix("24h") after = int(time.time()) # Allow 1 second tolerance for execution time expected_min = before - (24 * 3600) - TIME_RANGE_SLACK_SECONDS - 1 expected_max = after - (24 * 3600) - TIME_RANGE_SLACK_SECONDS + 1 assert expected_min <= result <= expected_max def test_since_unix_7d_is_roughly_7_days_ago(self) -> None: """Verify 7d preset returns a timestamp ~7 days in the past.""" before = int(time.time()) result = since_unix("7d") after = int(time.time()) # Allow 1 second tolerance for execution time expected_min = before - (7 * 24 * 3600) - TIME_RANGE_SLACK_SECONDS - 1 expected_max = after - (7 * 24 * 3600) - TIME_RANGE_SLACK_SECONDS + 1 assert expected_min <= result <= expected_max def test_since_unix_includes_slack_window(self) -> None: """Verify 60-second slack is included in all presets.""" now = int(time.time()) result = since_unix("24h") # Verify slack is included: result should be (now - 24h - 60s) # within tolerance diff_without_slack = now - result expected_without_slack = 24 * 3600 actual_slack = diff_without_slack - expected_without_slack # The slack should be ~60 seconds assert actual_slack >= TIME_RANGE_SLACK_SECONDS - 1 assert actual_slack <= TIME_RANGE_SLACK_SECONDS + 1