Files
ss-tools/backend/tests/core/test_timezone.py
busya 997329e2a5 fix(agent): resolve ModuleNotFoundError for backend, add E2E test infra
- Dockerfile.agent: fix CMD (python -m src.agent.run), use backend/requirements.txt,
  minimal COPY (only src.agent + src.core.cot_logger), add GRACE contract
- docker-compose.yml: SERVICE_TOKEN_SECRET -> SERVICE_JWT (match code)
- docker-compose.enterprise-clean.yml: same env var fix
- docker/.env.agent.example: same env var fix
- build.sh: same env var fix
- chore: semantics-testing SKILL.md, backend tests, pyproject.toml
2026-06-14 15:41:46 +03:00

177 lines
6.5 KiB
Python

# #region Test.AppTimezone [C:3] [TYPE Module] [SEMANTICS test,timezone]
# @BRIEF Unit tests for AppTimezone module — timezone utilities, caching, and localization.
# @RELATION BINDS_TO -> [AppTimezone]
# @TEST_EDGE: invalid_timezone -> validate_timezone returns False for unknown names
# @TEST_EDGE: none_datetime -> localize returns None for None input
# @TEST_EDGE: cache_invalidation -> invalidate clears cache, next call re-reads env
import pytest
from datetime import datetime
from zoneinfo import ZoneInfo
from src.core.timezone import (
_get_default_tz_name,
get_app_timezone,
invalidate_timezone_cache,
validate_timezone,
localize,
now,
format_timezone_offset,
)
@pytest.fixture(autouse=True)
def _clean_cache():
"""Ensure cache is clean before and after each test."""
invalidate_timezone_cache()
yield
invalidate_timezone_cache()
# #region test_get_default_tz_name_default [C:2] [TYPE Function]
# @BRIEF Without APP_TIMEZONE env var, returns "Europe/Moscow".
def test_get_default_tz_name_default(monkeypatch):
monkeypatch.delenv("APP_TIMEZONE", raising=False)
assert _get_default_tz_name() == "Europe/Moscow"
# #endregion test_get_default_tz_name_default
# #region test_get_default_tz_name_custom [C:2] [TYPE Function]
# @BRIEF With APP_TIMEZONE set, returns the custom value.
def test_get_default_tz_name_custom(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "US/Eastern")
assert _get_default_tz_name() == "US/Eastern"
# #endregion test_get_default_tz_name_custom
# #region test_get_app_timezone_returns_zoneinfo [C:2] [TYPE Function]
# @BRIEF Returns a ZoneInfo instance for the configured timezone.
def test_get_app_timezone_returns_zoneinfo(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
tz = get_app_timezone()
assert isinstance(tz, ZoneInfo)
assert tz.key == "Europe/Moscow"
# #endregion test_get_app_timezone_returns_zoneinfo
# #region test_get_app_timezone_cached [C:2] [TYPE Function]
# @BRIEF Two consecutive calls return the exact same object (identity).
def test_get_app_timezone_cached(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "Europe/Berlin")
first = get_app_timezone()
second = get_app_timezone()
assert first is second
# #endregion test_get_app_timezone_cached
# #region test_invalidate_and_recreate_cycle [C:2] [TYPE Function]
# @BRIEF After invalidate, next call re-reads env and returns a new object.
def test_invalidate_and_recreate_cycle(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
before = get_app_timezone()
assert before.key == "Europe/Moscow"
invalidate_timezone_cache()
monkeypatch.setenv("APP_TIMEZONE", "US/Eastern")
after = get_app_timezone()
assert after.key == "US/Eastern"
assert before is not after
# #endregion test_invalidate_and_recreate_cycle
# #region test_validate_timezone_valid [C:2] [TYPE Function]
# @BRIEF Known IANA timezone names return True.
@pytest.mark.parametrize("tz_name", ["Europe/Moscow", "UTC", "US/Eastern", "Asia/Tokyo"])
def test_validate_timezone_valid(tz_name):
assert validate_timezone(tz_name) is True
# #endregion test_validate_timezone_valid
# #region test_validate_timezone_invalid [C:2] [TYPE Function]
# @BRIEF Unknown or garbage timezone names return False.
@pytest.mark.parametrize("tz_name", ["Invalid/Timezone", "NotATZ", "Foo/Bar/Baz"])
def test_validate_timezone_invalid(tz_name):
assert validate_timezone(tz_name) is False
# #endregion test_validate_timezone_invalid
# #region test_validate_timezone_none [C:2] [TYPE Function]
# @BRIEF None input returns False (TypeError caught internally).
def test_validate_timezone_none():
assert validate_timezone(None) is False
# #endregion test_validate_timezone_none
# #region test_validate_timezone_empty_string_raises [C:2] [TYPE Function]
# @BRIEF BUG: empty string raises ValueError — production code only catches KeyError/TypeError.
def test_validate_timezone_empty_string_raises():
with pytest.raises(ValueError, match="normalized relative paths"):
validate_timezone("")
# #endregion test_validate_timezone_empty_string_raises
# #region test_localize_naive_datetime [C:2] [TYPE Function]
# @BRIEF Naive UTC datetime is treated as UTC and converted to app timezone.
def test_localize_naive_datetime(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
invalidate_timezone_cache()
naive_utc = datetime(2024, 6, 15, 12, 0, 0)
result = localize(naive_utc)
# Hardcoded fixture: Moscow is UTC+3 in June (no DST)
assert result.hour == 15
assert result.tzinfo is not None
assert result.tzinfo.key == "Europe/Moscow"
# #endregion test_localize_naive_datetime
# #region test_localize_aware_datetime [C:2] [TYPE Function]
# @BRIEF Aware datetime in a different timezone is converted to app timezone.
def test_localize_aware_datetime(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
invalidate_timezone_cache()
aware_utc = datetime(2024, 1, 15, 10, 0, 0, tzinfo=ZoneInfo("UTC"))
result = localize(aware_utc)
# Hardcoded fixture: Moscow is UTC+3 in January (no DST)
assert result.hour == 13
assert result.tzinfo.key == "Europe/Moscow"
# #endregion test_localize_aware_datetime
# #region test_localize_none [C:2] [TYPE Function]
# @BRIEF None input returns None without error.
def test_localize_none():
assert localize(None) is None
# #endregion test_localize_none
# #region test_now_returns_aware [C:2] [TYPE Function]
# @BRIEF now() returns a timezone-aware datetime in the app timezone.
def test_now_returns_aware(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "UTC")
invalidate_timezone_cache()
result = now()
assert result.tzinfo is not None
assert result.tzinfo.key == "UTC"
# #endregion test_now_returns_aware
# #region test_format_timezone_offset [C:2] [TYPE Function]
# @BRIEF Returns formatted UTC offset string like "+03:00".
def test_format_timezone_offset(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
invalidate_timezone_cache()
offset = format_timezone_offset()
# Hardcoded fixture: Moscow is always UTC+3
assert offset == "+03:00"
# #endregion test_format_timezone_offset
# #region test_format_timezone_offset_utc [C:2] [TYPE Function]
# @BRIEF UTC timezone returns "+00:00".
def test_format_timezone_offset_utc(monkeypatch):
monkeypatch.setenv("APP_TIMEZONE", "UTC")
invalidate_timezone_cache()
offset = format_timezone_offset()
assert offset == "+00:00"
# #endregion test_format_timezone_offset_utc
# #endregion Test.AppTimezone