zcbot/tests/test_run_cancel.py

77 lines
2.4 KiB
Python

"""Web run cancellation: broker fast path and shared-DB fallback."""
from __future__ import annotations
import unittest
from contextlib import contextmanager
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from uuid import uuid4
from sqlalchemy.exc import SQLAlchemyError
class RunCancelCheckTests(unittest.TestCase):
def test_broker_signal_stops_without_db_poll(self) -> None:
from web import runs
tid = uuid4()
broker = MagicMock()
broker.is_cancelled.return_value = True
with (
patch.object(runs, "broker", broker),
patch.object(runs, "session_scope") as scope,
):
check = runs._RunCancelCheck(tid)
self.assertTrue(check())
scope.assert_not_called()
broker.is_cancelled.assert_called_once_with(tid)
def test_db_cancelling_falls_back_across_processes(self) -> None:
from web import runs
tid = uuid4()
broker = MagicMock()
broker.is_cancelled.return_value = False
result = MagicMock()
result.scalar_one_or_none.return_value = "cancelling"
session = SimpleNamespace(execute=MagicMock(return_value=result))
@contextmanager
def fake_scope():
yield session
with (
patch.object(runs, "broker", broker),
patch.object(runs, "session_scope", fake_scope),
patch.object(runs.time, "monotonic", side_effect=[10.0, 10.5, 11.0]),
):
check = runs._RunCancelCheck(tid)
self.assertFalse(check(), "一秒节流窗口内不查询 DB")
self.assertTrue(check(), "共享 DB 的 cancelling 应成为可靠兜底")
session.execute.assert_called_once()
def test_db_poll_failure_does_not_abort_run(self) -> None:
from web import runs
tid = uuid4()
broker = MagicMock()
broker.is_cancelled.return_value = False
@contextmanager
def broken_scope():
raise SQLAlchemyError("db unavailable")
yield
with (
patch.object(runs, "broker", broker),
patch.object(runs, "session_scope", broken_scope),
patch.object(runs.time, "monotonic", side_effect=[20.0, 21.0]),
):
self.assertFalse(runs._RunCancelCheck(tid)())
if __name__ == "__main__":
unittest.main()