"""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()
