Files
aiturk-hermes-ide/tests/cron/test_cron_next_run_profile_timezone.py

81 lines
3.1 KiB
Python

"""Regression test for #97905 / carrier PR #92489.
A multiplex ticker (desktop dashboard backend, multiplex gateway) resolves
``hermes_time`` under the process's own startup profile, then ticks OTHER
profiles' cron stores via ``set_hermes_home_override()`` + ``use_cron_store()``.
Before the profile-keyed timezone cache, the first profile's resolved zone
was process-global, so ``compute_next_run`` / ``create_job`` / ``mark_job_run``
persisted ``next_run_at`` into the ticked profile's jobs.json with the FOREIGN
process's UTC offset — e.g. ``0 14 * * *`` for an America/New_York profile
stored as ``14:00+00:00``, which becomes due at 10:00 ET and fires four hours
early (#97905; single-profile sibling report #88220).
The invariant pinned here: ``next_run_at`` written during a foreign-process
tick carries the JOB-OWNING profile's configured UTC offset.
"""
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
import pytest
import hermes_time
@pytest.fixture(autouse=True)
def _fresh_tz_cache(monkeypatch):
monkeypatch.delenv("HERMES_TIMEZONE", raising=False)
hermes_time.reset_cache()
yield
hermes_time.reset_cache()
def test_foreign_process_tick_persists_owning_profile_offset(
tmp_path, monkeypatch
):
"""next_run_at persisted during a multiplex tick uses the ticked
profile's timezone, not the backend process's."""
backend_home = tmp_path / "backend" # desktop backend's own profile: UTC
profile_a = tmp_path / "north-caribbean" # job-owning profile: Eastern
backend_home.mkdir()
profile_a.mkdir()
(backend_home / "config.yaml").write_text(
"timezone: UTC\n", encoding="utf-8"
)
(profile_a / "config.yaml").write_text(
"timezone: America/New_York\n", encoding="utf-8"
)
monkeypatch.setenv("HERMES_HOME", str(backend_home))
# Backend process resolves its own timezone first (process startup).
assert hermes_time.now().utcoffset() == datetime.now(
ZoneInfo("UTC")
).utcoffset()
from hermes_constants import (
reset_hermes_home_override,
set_hermes_home_override,
)
from cron.jobs import create_job, load_jobs, use_cron_store
# Exactly the scoping the multiplex ticker applies per profile
# (cron/scheduler_provider.py::_tick_profiles).
token = set_hermes_home_override(str(profile_a))
try:
with use_cron_store(profile_a):
create_job(name="daily-2pm", prompt="x", schedule="0 14 * * *")
job = load_jobs()[0]
finally:
reset_hermes_home_override(token)
next_run = datetime.fromisoformat(job["next_run_at"])
expected_offset = datetime.now(ZoneInfo("America/New_York")).utcoffset()
assert next_run.utcoffset() == expected_offset, (
f"next_run_at {job['next_run_at']} persisted with foreign offset "
f"{next_run.utcoffset()} instead of the owning profile's "
f"{expected_offset} — fires (offset delta) early (#97905)"
)
# And the wall clock honors the cron expression in the profile's zone.
assert (next_run.hour, next_run.minute) == (14, 0)