test: record getUpdates progress in mocked cold-connect polling flows

The strict cold-start readiness gate (#67498) means adapter.connect() no
longer returns True until the mocked start_polling records a successful
getUpdates round trip for its generation. Update the conflict-suite
Application mocks accordingly:

- fake_start_polling side effects call
  adapter._record_polling_progress(adapter._polling_generation) on the
  initial connect (retry generations intentionally do NOT auto-progress
  where a test asserts the conflict count survives an unproven retry).
- _build_polling_app takes the adapter so its start_polling mock can
  record progress.

Without this, the cold connects in these tests wait out the full 60s
readiness deadline and fail — which is exactly the fail-closed behavior
the gate is supposed to provide when polling shows no progress.
This commit is contained in:
teknium1 2026-07-24 18:08:00 -07:00 committed by Teknium
parent c8ff720508
commit 9ac8b24fd5
1 changed files with 29 additions and 5 deletions

View File

@ -103,6 +103,14 @@ async def test_polling_conflict_retries_before_fatal(monkeypatch):
async def fake_start_polling(**kwargs):
captured["error_callback"] = kwargs["error_callback"]
# Cold connect requires real getUpdates readiness (#67498) — simulate
# the first successful poll for the generation this call started, but
# only on the initial connect: the conflict-retry generation must NOT
# make progress here, or it would legitimately reset the conflict
# count this test asserts on.
if not captured.get("initial_done"):
captured["initial_done"] = True
adapter._record_polling_progress(adapter._polling_generation)
updater = SimpleNamespace(
start_polling=AsyncMock(side_effect=fake_start_polling),
@ -228,6 +236,8 @@ async def test_polling_conflict_becomes_fatal_after_retries(monkeypatch):
if call_count["n"] == 1:
# First call (initial connect) succeeds
captured["error_callback"] = kwargs["error_callback"]
# Cold connect requires getUpdates readiness (#67498).
adapter._record_polling_progress(adapter._polling_generation)
else:
# Retry calls fail
raise Exception("Connection refused")
@ -367,8 +377,12 @@ async def test_connect_clears_webhook_before_polling(monkeypatch):
lambda scope, identity: None,
)
async def _start_polling_with_progress(**_kwargs):
# Cold connect requires getUpdates readiness (#67498).
adapter._record_polling_progress(adapter._polling_generation)
updater = SimpleNamespace(
start_polling=AsyncMock(),
start_polling=AsyncMock(side_effect=_start_polling_with_progress),
stop=AsyncMock(),
running=True,
)
@ -428,8 +442,12 @@ async def test_connect_does_not_block_on_post_connect_housekeeping(monkeypatch):
# promptly and expose the still-running task; disconnect() must cancel it.
monkeypatch.setattr(adapter, "_run_post_connect_housekeeping", _hang_forever)
async def _start_polling_with_progress(**_kwargs):
# Cold connect requires getUpdates readiness (#67498).
adapter._record_polling_progress(adapter._polling_generation)
updater = SimpleNamespace(
start_polling=AsyncMock(),
start_polling=AsyncMock(side_effect=_start_polling_with_progress),
stop=AsyncMock(),
running=True,
)
@ -528,6 +546,8 @@ async def test_polling_conflict_reschedule_uses_running_loop(monkeypatch):
call_count["n"] += 1
if call_count["n"] == 1:
captured["error_callback"] = kwargs["error_callback"]
# Cold connect requires getUpdates readiness (#67498).
adapter._record_polling_progress(adapter._polling_generation)
else:
# Retry attempt fails so the handler enters the reschedule branch.
raise Exception("Connection refused")
@ -585,12 +605,14 @@ async def test_polling_conflict_reschedule_uses_running_loop(monkeypatch):
await _cancel_heartbeat(adapter)
def _build_polling_app(monkeypatch):
def _build_polling_app(monkeypatch, adapter):
"""Wire a mock PTB Application whose start_polling captures kwargs."""
captured = {}
async def fake_start_polling(**kwargs):
captured.update(kwargs)
# Cold connect requires getUpdates readiness (#67498).
adapter._record_polling_progress(adapter._polling_generation)
updater = SimpleNamespace(
start_polling=AsyncMock(side_effect=fake_start_polling),
@ -626,7 +648,7 @@ def _build_polling_app(monkeypatch):
async def test_cold_connect_drops_pending_updates(monkeypatch):
"""A cold first boot (is_reconnect=False) drops the stale Bot API queue."""
adapter = TelegramAdapter(PlatformConfig(enabled=True, token="***"))
captured = _build_polling_app(monkeypatch)
captured = _build_polling_app(monkeypatch, adapter)
ok = await adapter.connect() # default is_reconnect=False
@ -640,7 +662,7 @@ async def test_reconnect_preserves_pending_updates(monkeypatch):
"""A watcher reconnect (is_reconnect=True) preserves the queue Telegram
accumulated during the outage the core of #46621."""
adapter = TelegramAdapter(PlatformConfig(enabled=True, token="***"))
captured = _build_polling_app(monkeypatch)
captured = _build_polling_app(monkeypatch, adapter)
ok = await adapter.connect(is_reconnect=True)
@ -714,6 +736,8 @@ async def test_conflict_callback_disarms_before_scheduling(monkeypatch):
async def fake_start_polling(**kwargs):
captured["error_callback"] = kwargs["error_callback"]
# Cold connect requires getUpdates readiness (#67498).
adapter._record_polling_progress(adapter._polling_generation)
stop_event = asyncio.Event()
updater = SimpleNamespace(