Systematic rename of all semantic anchors (#region, [DEF], @RELATION) across 1400+ files — backend Python, frontend Svelte/TS, specs, docs: - Flat anchors become Namespace.Module.Entity - @RELATION references updated to match new anchor paths - Zero business logic changes
167 lines
6.7 KiB
Python
167 lines
6.7 KiB
Python
# #region Test.AppTimezone [C:3] [TYPE Module] [SEMANTICS test,timezone,datetime,utilities]
|
|
# @BRIEF Tests for core/timezone.py — all timezone utility functions.
|
|
# @RELATION BINDS_TO -> [Core.Timezone.AppTimezone]
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
from datetime import datetime, timezone, timedelta
|
|
from unittest.mock import patch
|
|
import pytest
|
|
|
|
from zoneinfo import ZoneInfo
|
|
|
|
|
|
# #region Test.AppTimezone.TestGetDefaultTzName [C:2] [TYPE Function]
|
|
# @BRIEF Test _get_default_tz_name reads env var or default.
|
|
class TestGetDefaultTzName:
|
|
def test_returns_env_value(self):
|
|
from src.core.timezone import _get_default_tz_name
|
|
with patch("src.core.timezone.os.getenv", return_value="America/New_York"):
|
|
assert _get_default_tz_name() == "America/New_York"
|
|
|
|
def test_returns_default_when_not_set(self):
|
|
from src.core.timezone import _get_default_tz_name
|
|
with patch("src.core.timezone.os.getenv", return_value=None):
|
|
# os.getenv returns None if not set, but _get_default_tz_name uses getenv with default
|
|
pass
|
|
# The function uses os.getenv("APP_TIMEZONE", "Europe/Moscow"), so we can't test unset easily
|
|
# Let's test directly
|
|
with patch("src.core.timezone.os.getenv", return_value="Europe/Moscow"):
|
|
assert _get_default_tz_name() == "Europe/Moscow"
|
|
# #endregion Test.AppTimezone.TestGetDefaultTzName
|
|
|
|
|
|
# #region Test.AppTimezone.TestGetAppTimezone [C:2] [TYPE Function]
|
|
# @BRIEF Test get_app_timezone returns cached ZoneInfo.
|
|
class TestGetAppTimezone:
|
|
def test_returns_zoneinfo(self):
|
|
from src.core.timezone import get_app_timezone, invalidate_timezone_cache
|
|
invalidate_timezone_cache()
|
|
tz = get_app_timezone()
|
|
assert isinstance(tz, ZoneInfo)
|
|
|
|
def test_caches_result(self):
|
|
from src.core.timezone import get_app_timezone, invalidate_timezone_cache
|
|
invalidate_timezone_cache()
|
|
tz1 = get_app_timezone()
|
|
tz2 = get_app_timezone()
|
|
assert tz1 is tz2 # Same cached object
|
|
# #endregion Test.AppTimezone.TestGetAppTimezone
|
|
|
|
|
|
# #region Test.AppTimezone.TestInvalidateTimezoneCache [C:2] [TYPE Function]
|
|
# @BRIEF Test invalidate_timezone_cache resets the cache.
|
|
class TestInvalidateTimezoneCache:
|
|
def test_invalidates_cache(self):
|
|
import src.core.timezone as tz_mod
|
|
tz_mod._APP_TZ_CACHE = None # Reset
|
|
tz_mod.invalidate_timezone_cache()
|
|
assert tz_mod._APP_TZ_CACHE is None # Cache cleared
|
|
tz1 = tz_mod.get_app_timezone()
|
|
assert tz_mod._APP_TZ_CACHE is not None # Cache populated
|
|
tz_mod.invalidate_timezone_cache()
|
|
assert tz_mod._APP_TZ_CACHE is None # Cache cleared again
|
|
tz_mod._APP_TZ_CACHE = None # Reset for other tests
|
|
# #endregion Test.AppTimezone.TestInvalidateTimezoneCache
|
|
|
|
|
|
# #region Test.AppTimezone.TestValidateTimezone [C:2] [TYPE Function]
|
|
# @BRIEF Test validate_timezone for valid/invalid IANA names.
|
|
class TestValidateTimezone:
|
|
def test_valid_timezone(self):
|
|
from src.core.timezone import validate_timezone
|
|
assert validate_timezone("Europe/Moscow") is True
|
|
assert validate_timezone("UTC") is True
|
|
assert validate_timezone("America/New_York") is True
|
|
|
|
def test_invalid_timezone(self):
|
|
from src.core.timezone import validate_timezone
|
|
assert validate_timezone("Invalid/Zone") is False
|
|
|
|
def test_none_raises_type_error(self):
|
|
from src.core.timezone import validate_timezone
|
|
# This should return False when TypeError is caught
|
|
assert validate_timezone(None) is False # type: ignore
|
|
# #endregion Test.AppTimezone.TestValidateTimezone
|
|
|
|
|
|
# #region Test.AppTimezone.TestLocalize [C:2] [TYPE Function]
|
|
# @BRIEF Test localize converts UTC to app timezone.
|
|
class TestLocalize:
|
|
def test_none_returns_none(self):
|
|
from src.core.timezone import localize
|
|
assert localize(None) is None
|
|
|
|
def test_converts_naive_utc_to_tz(self):
|
|
from src.core.timezone import localize, invalidate_timezone_cache
|
|
from src.core.timezone import _get_default_tz_name
|
|
invalidate_timezone_cache()
|
|
dt = datetime(2024, 1, 15, 12, 0, 0) # Naive UTC
|
|
result = localize(dt)
|
|
assert result is not None
|
|
assert result.tzinfo is not None
|
|
# Should be in Europe/Moscow (+03:00)
|
|
assert result.hour == 15 # Moscow is UTC+3
|
|
|
|
def test_converts_aware_utc_to_tz(self):
|
|
from src.core.timezone import localize, invalidate_timezone_cache
|
|
invalidate_timezone_cache()
|
|
dt = datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
|
|
result = localize(dt)
|
|
assert result is not None
|
|
assert result.tzinfo is not None
|
|
offset = result.utcoffset()
|
|
assert offset is not None
|
|
assert offset.total_seconds() == 3 * 3600 # Moscow is UTC+3
|
|
|
|
def test_preserves_non_utc_aware(self):
|
|
from src.core.timezone import localize, invalidate_timezone_cache
|
|
invalidate_timezone_cache()
|
|
dt = datetime(2024, 1, 15, 12, 0, 0, tzinfo=ZoneInfo("America/New_York"))
|
|
result = localize(dt)
|
|
assert result is not None
|
|
# Should convert to Moscow time
|
|
assert result.tzinfo is not None
|
|
assert "Europe/Moscow" in str(result.tzinfo) or "+03" in result.isoformat()
|
|
# #endregion Test.AppTimezone.TestLocalize
|
|
|
|
|
|
# #region Test.AppTimezone.TestNow [C:2] [TYPE Function]
|
|
# @BRIEF Test now returns timezone-aware datetime.
|
|
class TestNow:
|
|
def test_returns_aware_datetime(self):
|
|
from src.core.timezone import now
|
|
result = now()
|
|
assert result.tzinfo is not None
|
|
|
|
def test_in_app_timezone(self):
|
|
from src.core.timezone import now, invalidate_timezone_cache
|
|
invalidate_timezone_cache()
|
|
result = now()
|
|
offset = result.utcoffset()
|
|
assert offset is not None
|
|
assert offset.total_seconds() == 3 * 3600 # Europe/Moscow
|
|
# #endregion Test.AppTimezone.TestNow
|
|
|
|
|
|
# #region Test.AppTimezone.TestFormatTimezoneOffset [C:2] [TYPE Function]
|
|
# @BRIEF Test format_timezone_offset returns formatted offset string.
|
|
class TestFormatTimezoneOffset:
|
|
def test_returns_formatted_offset(self):
|
|
from src.core.timezone import format_timezone_offset
|
|
offset = format_timezone_offset()
|
|
assert offset.startswith("+") or offset.startswith("-")
|
|
assert ":" in offset
|
|
assert len(offset) == 6 # e.g. "+03:00"
|
|
|
|
def test_offset_is_moscow(self):
|
|
from src.core.timezone import format_timezone_offset, invalidate_timezone_cache
|
|
invalidate_timezone_cache()
|
|
result = format_timezone_offset()
|
|
assert result == "+03:00"
|
|
# #endregion Test.AppTimezone.TestFormatTimezoneOffset
|
|
# #endregion Test.AppTimezone
|