"""Shared fixtures for gateway tests. The ``_ensure_telegram_mock`` helper guarantees that a minimal mock of the ``telegram`` package is registered in :data:`sys.modules` **before** any test file triggers ``from gateway.platforms.telegram import ...``. Without this, ``pytest-xdist`` workers that happen to collect ``test_telegram_caption_merge.py`` (bare top-level import, no per-file mock) first will cache ``ChatType = None`` from the production ImportError fallback, causing 30+ downstream test failures wherever ``ChatType.GROUP`` / ``ChatType.SUPERGROUP`` is accessed. Individual test files may still call their own ``_ensure_telegram_mock`` — it short-circuits when the mock is already present. """ import sys from unittest.mock import MagicMock def _ensure_telegram_mock() -> None: """Install a comprehensive telegram mock in sys.modules. Idempotent — skips when the real library is already imported. Uses ``sys.modules[name] = mod`` (overwrite) instead of ``setdefault`` so it wins even if a partial/broken import already cached a module with ``ChatType = None``. """ if "telegram" in sys.modules and hasattr(sys.modules["telegram"], "__file__"): return # Real library is installed — nothing to mock mod = MagicMock() mod.ext.ContextTypes.DEFAULT_TYPE = type(None) mod.constants.ParseMode.MARKDOWN = "Markdown" mod.constants.ParseMode.MARKDOWN_V2 = "MarkdownV2" mod.constants.ParseMode.HTML = "HTML" mod.constants.ChatType.PRIVATE = "private" mod.constants.ChatType.GROUP = "group" mod.constants.ChatType.SUPERGROUP = "supergroup" mod.constants.ChatType.CHANNEL = "channel" # Real exception classes so ``except (NetworkError, ...)`` clauses # in production code don't blow up with TypeError. mod.error.NetworkError = type("NetworkError", (OSError,), {}) mod.error.TimedOut = type("TimedOut", (OSError,), {}) mod.error.BadRequest = type("BadRequest", (Exception,), {}) mod.error.Forbidden = type("Forbidden", (Exception,), {}) mod.error.InvalidToken = type("InvalidToken", (Exception,), {}) mod.error.RetryAfter = type("RetryAfter", (Exception,), {"retry_after": 1}) mod.error.Conflict = type("Conflict", (Exception,), {}) # Update.ALL_TYPES used in start_polling() mod.Update.ALL_TYPES = [] for name in ( "telegram", "telegram.ext", "telegram.constants", "telegram.request", ): sys.modules[name] = mod sys.modules["telegram.error"] = mod.error def _ensure_discord_mock() -> None: """Install a comprehensive discord mock in sys.modules. Idempotent — skips when the real library is already imported. Uses ``sys.modules[name] = mod`` (overwrite) instead of ``setdefault`` so it wins even if a partial/broken import already cached the module. This mock is comprehensive — it includes **all** attributes needed by every gateway discord test file. Individual test files should call this function (it short-circuits when already present) rather than maintaining their own mock setup. """ if "discord" in sys.modules and hasattr(sys.modules["discord"], "__file__"): return # Real library is installed — nothing to mock from types import SimpleNamespace discord_mod = MagicMock() discord_mod.Intents.default.return_value = MagicMock() discord_mod.Client = MagicMock discord_mod.File = MagicMock discord_mod.DMChannel = type("DMChannel", (), {}) discord_mod.Thread = type("Thread", (), {}) discord_mod.ForumChannel = type("ForumChannel", (), {}) discord_mod.Interaction = object discord_mod.Embed = MagicMock discord_mod.ui = SimpleNamespace( View=object, button=lambda *a, **k: (lambda fn: fn), Button=object, ) discord_mod.ButtonStyle = SimpleNamespace( success=1, primary=2, secondary=2, danger=3, green=1, grey=2, blurple=2, red=3, ) discord_mod.Color = SimpleNamespace( orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4, purple=lambda: 5, ) # app_commands — needed by _register_slash_commands auto-registration class _FakeGroup: def __init__(self, *, name, description, parent=None): self.name = name self.description = description self.parent = parent self._children: dict = {} if parent is not None: parent.add_command(self) def add_command(self, cmd): self._children[cmd.name] = cmd class _FakeCommand: def __init__(self, *, name, description, callback, parent=None): self.name = name self.description = description self.callback = callback self.parent = parent discord_mod.app_commands = SimpleNamespace( describe=lambda **kwargs: (lambda fn: fn), choices=lambda **kwargs: (lambda fn: fn), Choice=lambda **kwargs: SimpleNamespace(**kwargs), Group=_FakeGroup, Command=_FakeCommand, ) ext_mod = MagicMock() commands_mod = MagicMock() commands_mod.Bot = MagicMock ext_mod.commands = commands_mod for name in ("discord", "discord.ext", "discord.ext.commands"): sys.modules[name] = discord_mod sys.modules["discord.ext"] = ext_mod sys.modules["discord.ext.commands"] = commands_mod # Run at collection time — before any test file's module-level imports. _ensure_telegram_mock() _ensure_discord_mock()