fix(ci): stub resolve_runtime_provider in cron wake-gate tests + shield update-check timeout test from thread race
Two additional CI failures surfaced when the first PR ran through GHA — both were pre-existing but blocked merge. 1) tests/cron/test_scheduler.py::TestRunJobWakeGate (3 tests) run_job calls resolve_runtime_provider BEFORE constructing AIAgent, so patching run_agent.AIAgent alone isn't enough — the resolver raises 'No inference provider configured' in hermetic CI (no API keys) and the test never reaches the mocked AIAgent. Added autouse fixture that stubs resolve_runtime_provider with a fake openrouter runtime. 2) tests/hermes_cli/test_update_check.py::test_get_update_result_timeout Observed on CI: assert 4950 is None. A background update-check thread (from an earlier test or hermes_cli.main's own prefetch_update_check call) raced a real git-fetch result (4950 commits behind origin/main) into banner._update_result during this test's wait(0.1). Wrap the test in patch.object(banner, 'check_for_updates', return_value=None) so any in-flight thread writes None rather than a real value. Validation: Under CI-parity env (env -i, no creds): 6/6 pass Broader suite (tests/hermes_cli + cron + gateway + run_agent/streaming + toolsets + discord_tool): 6033 passed, pre-existing failures in telegram_approval_buttons (3) and internal_event_bypass_pairing (1) are unrelated.
This commit is contained in:
parent
c9b833feb3
commit
ad4680cf74
@ -1239,6 +1239,30 @@ class TestParseWakeGate:
|
||||
class TestRunJobWakeGate:
|
||||
"""Integration tests for run_job wake-gate short-circuit."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _stub_runtime_provider(self):
|
||||
"""Stub ``resolve_runtime_provider`` for wake-gate tests.
|
||||
|
||||
``run_job`` resolves the runtime provider BEFORE constructing
|
||||
``AIAgent``, so these tests must mock ``resolve_runtime_provider``
|
||||
in addition to ``AIAgent`` — otherwise in a hermetic CI env (no
|
||||
API keys), the resolver raises and the test fails before the
|
||||
patched AIAgent is ever reached.
|
||||
"""
|
||||
fake_runtime = {
|
||||
"provider": "openrouter",
|
||||
"api_mode": "chat_completions",
|
||||
"base_url": "https://openrouter.ai/api/v1",
|
||||
"api_key": "test-key",
|
||||
"source": "stub",
|
||||
"requested_provider": None,
|
||||
}
|
||||
with patch(
|
||||
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||
return_value=fake_runtime,
|
||||
):
|
||||
yield
|
||||
|
||||
def _make_job(self, name="wake-gate-test", script="check.py"):
|
||||
"""Minimal valid cron job dict for run_job."""
|
||||
return {
|
||||
|
||||
@ -114,20 +114,30 @@ def test_prefetch_non_blocking():
|
||||
|
||||
|
||||
def test_get_update_result_timeout():
|
||||
"""get_update_result() returns None when check hasn't completed within timeout."""
|
||||
"""get_update_result() returns None when check hasn't completed within timeout.
|
||||
|
||||
Race protection: a background update-check thread from an earlier
|
||||
test, or from hermes_cli.main's own prefetch_update_check(), could
|
||||
write to module-level ``_update_result`` during this test's
|
||||
``wait(0.1)``. Observed on CI: a real git-fetch returned 4950
|
||||
commits-behind mid-test, failing ``assert 4950 is None``. Patching
|
||||
``check_for_updates`` for the duration of the test ensures any
|
||||
in-flight thread writes ``None`` rather than a real fetch result.
|
||||
"""
|
||||
import hermes_cli.banner as banner
|
||||
|
||||
# Reset module state — don't set the event
|
||||
banner._update_result = None
|
||||
banner._update_check_done = threading.Event()
|
||||
with patch.object(banner, "check_for_updates", return_value=None):
|
||||
# Fresh Event so we hit the timeout branch deterministically.
|
||||
banner._update_result = None
|
||||
banner._update_check_done = threading.Event()
|
||||
|
||||
start = time.monotonic()
|
||||
result = banner.get_update_result(timeout=0.1)
|
||||
elapsed = time.monotonic() - start
|
||||
start = time.monotonic()
|
||||
result = banner.get_update_result(timeout=0.1)
|
||||
elapsed = time.monotonic() - start
|
||||
|
||||
# Should have waited ~0.1s and returned None
|
||||
assert result is None
|
||||
assert elapsed < 0.5
|
||||
# Should have waited ~0.1s and returned None
|
||||
assert result is None
|
||||
assert elapsed < 0.5
|
||||
|
||||
|
||||
def test_invalidate_update_cache_clears_all_profiles(tmp_path):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user