Files
BanGUI/backend/tests/test_services/test_time_utils.py
Lukas 407ca83850 Add tests for since timestamp accuracy in ban_service
- test_since_unix_returns_utc_epoch: validates since_unix('24h') returns UTC epoch
- test_ban_trend_since_is_within_expected_range: validates 23h-ago ban falls in 24h+slack window

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-23 23:00:51 +02:00

149 lines
5.1 KiB
Python

"""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
def test_since_unix_returns_utc_epoch(self) -> None:
"""``since_unix('24h')`` returns a value within 24h + 60s of ``time.time()``."""
before = int(time.time())
result = since_unix("24h")
after = int(time.time())
# Allow 2 second tolerance for execution time
expected_min = before - (24 * 3600) - TIME_RANGE_SLACK_SECONDS - 2
expected_max = after - (24 * 3600) - TIME_RANGE_SLACK_SECONDS + 2
assert expected_min <= result <= expected_max