`_today()` в `services/legal_entities.py` берёт московскую дату (`common/clock.py`: `moscow_date`, `MOSCOW_TZ` = Europe/Moscow; без базы часовых поясов — фиксированный UTC+3, Москва без перехода на летнее время с 2014 г.). Тесты на границе суток: 23:30 МСК (20:30 UTC) — ещё 4-е, 00:30 МСК (21:30 UTC того же дня) — уже 5-е, и 4-е становится прошлым. README: даты перевода/вывода — по Москве. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
62 lines
2.6 KiB
Python
62 lines
2.6 KiB
Python
"""«Сегодня» по Москве для дат перевода/закрытия ЮЛ (issue #9 п.6, follow-up #8): граница суток — московская."""
|
||
|
||
import datetime
|
||
|
||
import pytest
|
||
|
||
from src.common.clock import MOSCOW_TZ, moscow_date
|
||
from src.common.exceptions import DomainValidationError
|
||
from src.services import legal_entities
|
||
from src.services.legal_entities import LegalEntityService
|
||
|
||
UTC = datetime.UTC
|
||
# 23:30 МСК 4 октября = 20:30 UTC 4 октября; 00:30 МСК 5 октября = 21:30 UTC 4 октября (UTC ещё 4-е)
|
||
LATE_EVENING_MSK = datetime.datetime(2026, 10, 4, 20, 30, tzinfo=UTC)
|
||
AFTER_MIDNIGHT_MSK = datetime.datetime(2026, 10, 4, 21, 30, tzinfo=UTC)
|
||
OCT_4 = datetime.date(2026, 10, 4)
|
||
OCT_5 = datetime.date(2026, 10, 5)
|
||
|
||
|
||
def test_moscow_zone_is_utc_plus_3() -> None:
|
||
assert LATE_EVENING_MSK.astimezone(MOSCOW_TZ).utcoffset() == datetime.timedelta(hours=3)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("moment", "expected"),
|
||
[(LATE_EVENING_MSK, OCT_4), (AFTER_MIDNIGHT_MSK, OCT_5)],
|
||
ids=["23:30-msk", "00:30-msk"],
|
||
)
|
||
def test_moscow_date_follows_moscow_midnight(moment: datetime.datetime, expected: datetime.date) -> None:
|
||
assert moscow_date(moment) == expected
|
||
assert moment.date() == OCT_4 # дата UTC в обоих случаях — 4-е
|
||
|
||
|
||
def test_moscow_date_rejects_naive_datetime() -> None:
|
||
with pytest.raises(ValueError, match="часов"):
|
||
moscow_date(datetime.datetime(2026, 10, 4, 21, 30)) # noqa: DTZ001 - проверяется отказ на naive
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("moment", "today"),
|
||
[(LATE_EVENING_MSK, OCT_4), (AFTER_MIDNIGHT_MSK, OCT_5)],
|
||
ids=["23:30-msk", "00:30-msk"],
|
||
)
|
||
def test_effective_date_today_is_moscow_date(
|
||
monkeypatch: pytest.MonkeyPatch, moment: datetime.datetime, today: datetime.date
|
||
) -> None:
|
||
monkeypatch.setattr(legal_entities, "utc_now", lambda: moment)
|
||
|
||
assert legal_entities._today() == today
|
||
LegalEntityService._check_effective_date(today) # сегодня по Москве — можно
|
||
with pytest.raises(DomainValidationError) as info:
|
||
LegalEntityService._check_effective_date(today - datetime.timedelta(days=1))
|
||
assert info.value.code == "EFFECTIVE_DATE_IN_PAST"
|
||
|
||
|
||
def test_after_moscow_midnight_utc_date_is_already_past(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
"""00:30 МСК 5-го: 4-е (ещё «сегодня» по UTC) — уже прошлое."""
|
||
monkeypatch.setattr(legal_entities, "utc_now", lambda: AFTER_MIDNIGHT_MSK)
|
||
|
||
with pytest.raises(DomainValidationError):
|
||
LegalEntityService._check_effective_date(OCT_4)
|