148 lines
4.0 KiB
Python
148 lines
4.0 KiB
Python
"""unit test: run_bounded_cycle session lifetime, budget, and pass-through
|
|
|
|
run from the repo root:
|
|
uv run pytest tests/test_run_bounded_cycle.py
|
|
|
|
offline — get_default_session is faked at the runner's namespace so no proxies
|
|
or network are touched; run_monitor_cycle underneath runs real. Gap: the real
|
|
session wiring (timeout/max_clients reaching curl) is covered by the monitors'
|
|
own pool-size tests, not here.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
|
|
import aiohttp
|
|
import pytest
|
|
|
|
from monitor_utils import runner
|
|
from monitor_utils.models import CycleResult
|
|
from monitor_utils.runner import run_bounded_cycle
|
|
|
|
|
|
class FakeSession:
|
|
def __init__(self):
|
|
self.closed = False
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *exc):
|
|
self.closed = True
|
|
|
|
|
|
def stub_session(monkeypatch) -> dict:
|
|
"""Fake get_default_session, recording each call's kwargs and session."""
|
|
calls = []
|
|
|
|
def fake(proxies, headers=None, timeout=None, max_clients=None):
|
|
session = FakeSession()
|
|
calls.append(
|
|
{
|
|
"headers": headers,
|
|
"timeout": timeout,
|
|
"max_clients": max_clients,
|
|
"session": session,
|
|
}
|
|
)
|
|
return session
|
|
|
|
monkeypatch.setattr(runner, "get_default_session", fake)
|
|
return calls
|
|
|
|
|
|
def test_returns_cycle_result_and_closes_session(monkeypatch):
|
|
calls = stub_session(monkeypatch)
|
|
|
|
async def task(category, session, collection, **kwargs):
|
|
pass
|
|
|
|
result = asyncio.run(run_bounded_cycle(["a", "b"], task, None, None))
|
|
|
|
assert result == CycleResult(successes=2, failures=0)
|
|
assert calls[0]["session"].closed
|
|
|
|
|
|
def test_session_params_reach_the_factory(monkeypatch):
|
|
calls = stub_session(monkeypatch)
|
|
|
|
async def task(category, session, collection, **kwargs):
|
|
pass
|
|
|
|
asyncio.run(
|
|
run_bounded_cycle(
|
|
["a"],
|
|
task,
|
|
None,
|
|
None,
|
|
headers={"referer": "https://x/"},
|
|
session_timeout=8,
|
|
max_clients=81,
|
|
)
|
|
)
|
|
|
|
assert calls[0]["headers"] == {"referer": "https://x/"}
|
|
assert calls[0]["timeout"] == 8
|
|
assert calls[0]["max_clients"] == 81
|
|
|
|
|
|
def test_expired_budget_returns_none_not_raise(monkeypatch, caplog):
|
|
"""A cycle past its budget is abandoned and logged, and the loop lives on."""
|
|
|
|
calls = stub_session(monkeypatch)
|
|
|
|
async def parked(category, session, collection, **kwargs):
|
|
await asyncio.Event().wait()
|
|
|
|
with caplog.at_level(logging.ERROR, logger="monitor_utils.runner"):
|
|
result = asyncio.run(
|
|
run_bounded_cycle(["a"], parked, None, None, cycle_timeout=0.05)
|
|
)
|
|
|
|
assert result is None
|
|
assert calls[0]["session"].closed
|
|
assert any(
|
|
r.message == "cycle exceeded budget, abandoning cycle" for r in caplog.records
|
|
)
|
|
|
|
|
|
def test_expected_task_error_counts_as_failure(monkeypatch):
|
|
stub_session(monkeypatch)
|
|
|
|
async def flaky(category, session, collection, **kwargs):
|
|
raise aiohttp.ClientError("routine")
|
|
|
|
result = asyncio.run(run_bounded_cycle(["a"], flaky, None, None))
|
|
|
|
assert result == CycleResult(successes=0, failures=1)
|
|
|
|
|
|
def test_unexpected_error_propagates_and_closes_session(monkeypatch):
|
|
"""fail loud: a real bug must escape the helper, not vanish into a log."""
|
|
|
|
calls = stub_session(monkeypatch)
|
|
|
|
async def broken(category, session, collection, **kwargs):
|
|
raise RuntimeError("bug")
|
|
|
|
with pytest.raises(RuntimeError):
|
|
asyncio.run(run_bounded_cycle(["a"], broken, None, None))
|
|
|
|
assert calls[0]["session"].closed
|
|
|
|
|
|
def test_each_call_opens_a_fresh_session(monkeypatch):
|
|
calls = stub_session(monkeypatch)
|
|
|
|
async def task(category, session, collection, **kwargs):
|
|
pass
|
|
|
|
async def two_cycles():
|
|
await run_bounded_cycle(["a"], task, None, None)
|
|
await run_bounded_cycle(["a"], task, None, None)
|
|
|
|
asyncio.run(two_cycles())
|
|
|
|
assert len(calls) == 2
|
|
assert calls[0]["session"] is not calls[1]["session"]
|