mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
test(gateway): unify discord mock via shared conftest; drop duplicated mock in model_picker test
The cherry-picked model_picker test installed its own discord mock at module-import time via a local _ensure_discord_mock(), overwriting sys.modules['discord'] with a mock that lacked attributes other gateway tests needed (Intents.default(), File, app_commands.Choice). On pytest-xdist workers that collected test_discord_model_picker.py first, the shared mock in tests/gateway/conftest.py got clobbered and downstream tests failed with AttributeError / TypeError against missing mock attrs. Classic sys.modules cross-test pollution (see xdist-cross-test-pollution skill). Fix: - Extend the canonical _ensure_discord_mock() in tests/gateway/conftest.py to cover everything the model_picker test needs: real View/Select/ Button/SelectOption classes (not MagicMock sentinels), an Embed class that preserves title/description/color kwargs for assertion, and Color.greyple. - Strip the duplicated mock-setup block from test_discord_model_picker.py and rely on the shared mock that conftest installs at collection time. Regression check: scripts/run_tests.sh tests/gateway/ tests/hermes_cli/ -k 'discord or model or copilot or provider' -o 'addopts=' 1291 passed (was 1288 passed + 3 xdist-ordered failures before this commit).
This commit is contained in:
parent
fe34741f32
commit
42d6ab5082
2 changed files with 71 additions and 116 deletions
|
|
@ -88,11 +88,63 @@ def _ensure_discord_mock() -> None:
|
|||
discord_mod.Thread = type("Thread", (), {})
|
||||
discord_mod.ForumChannel = type("ForumChannel", (), {})
|
||||
discord_mod.Interaction = object
|
||||
discord_mod.Embed = MagicMock
|
||||
discord_mod.Message = type("Message", (), {})
|
||||
|
||||
# Embed: accept the kwargs production code / tests use
|
||||
# (title, description, color). MagicMock auto-attributes work too,
|
||||
# but some tests construct and inspect .title/.description directly.
|
||||
class _FakeEmbed:
|
||||
def __init__(self, *, title=None, description=None, color=None, **_):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.color = color
|
||||
discord_mod.Embed = _FakeEmbed
|
||||
|
||||
# ui.View / ui.Select / ui.Button: real classes (not MagicMock) so
|
||||
# tests that subclass ModelPickerView / iterate .children / clear
|
||||
# items work.
|
||||
class _FakeView:
|
||||
def __init__(self, timeout=None):
|
||||
self.timeout = timeout
|
||||
self.children = []
|
||||
def add_item(self, item):
|
||||
self.children.append(item)
|
||||
def clear_items(self):
|
||||
self.children.clear()
|
||||
|
||||
class _FakeSelect:
|
||||
def __init__(self, *, placeholder=None, options=None, custom_id=None, **_):
|
||||
self.placeholder = placeholder
|
||||
self.options = options or []
|
||||
self.custom_id = custom_id
|
||||
self.callback = None
|
||||
self.disabled = False
|
||||
|
||||
class _FakeButton:
|
||||
def __init__(self, *, label=None, style=None, custom_id=None, emoji=None,
|
||||
url=None, disabled=False, row=None, sku_id=None, **_):
|
||||
self.label = label
|
||||
self.style = style
|
||||
self.custom_id = custom_id
|
||||
self.emoji = emoji
|
||||
self.url = url
|
||||
self.disabled = disabled
|
||||
self.row = row
|
||||
self.sku_id = sku_id
|
||||
self.callback = None
|
||||
|
||||
class _FakeSelectOption:
|
||||
def __init__(self, *, label=None, value=None, description=None, **_):
|
||||
self.label = label
|
||||
self.value = value
|
||||
self.description = description
|
||||
discord_mod.SelectOption = _FakeSelectOption
|
||||
|
||||
discord_mod.ui = SimpleNamespace(
|
||||
View=object,
|
||||
View=_FakeView,
|
||||
Select=_FakeSelect,
|
||||
Button=_FakeButton,
|
||||
button=lambda *a, **k: (lambda fn: fn),
|
||||
Button=object,
|
||||
)
|
||||
discord_mod.ButtonStyle = SimpleNamespace(
|
||||
success=1, primary=2, secondary=2, danger=3,
|
||||
|
|
@ -100,7 +152,7 @@ def _ensure_discord_mock() -> None:
|
|||
)
|
||||
discord_mod.Color = SimpleNamespace(
|
||||
orange=lambda: 1, green=lambda: 2, blue=lambda: 3,
|
||||
red=lambda: 4, purple=lambda: 5,
|
||||
red=lambda: 4, purple=lambda: 5, greyple=lambda: 6,
|
||||
)
|
||||
|
||||
# app_commands — needed by _register_slash_commands auto-registration
|
||||
|
|
|
|||
|
|
@ -1,118 +1,16 @@
|
|||
"""Regression tests for the Discord /model picker."""
|
||||
"""Regression tests for the Discord /model picker.
|
||||
|
||||
from types import ModuleType, SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
import sys
|
||||
Uses the shared discord mock from tests/gateway/conftest.py (installed
|
||||
at collection time via _ensure_discord_mock()). Previously this file
|
||||
installed its own mock at module-import time and clobbered sys.modules,
|
||||
breaking other gateway tests under pytest-xdist.
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _ensure_discord_mock():
|
||||
existing = sys.modules.get("discord")
|
||||
if isinstance(existing, ModuleType) and getattr(existing, "__file__", None):
|
||||
return
|
||||
|
||||
class _FakeView:
|
||||
def __init__(self, timeout=None):
|
||||
self.timeout = timeout
|
||||
self.children = []
|
||||
|
||||
def add_item(self, item):
|
||||
self.children.append(item)
|
||||
|
||||
def clear_items(self):
|
||||
self.children.clear()
|
||||
|
||||
class _FakeSelect:
|
||||
def __init__(self, *, placeholder, options, custom_id):
|
||||
self.placeholder = placeholder
|
||||
self.options = options
|
||||
self.custom_id = custom_id
|
||||
self.callback = None
|
||||
self.disabled = False
|
||||
|
||||
class _FakeButton:
|
||||
def __init__(self, *, label, style, custom_id=None, emoji=None, url=None, disabled=False, row=None, sku_id=None):
|
||||
self.label = label
|
||||
self.style = style
|
||||
self.custom_id = custom_id
|
||||
self.emoji = emoji
|
||||
self.url = url
|
||||
self.disabled = disabled
|
||||
self.row = row
|
||||
self.sku_id = sku_id
|
||||
self.callback = None
|
||||
|
||||
class _FakeSelectOption:
|
||||
def __init__(self, *, label, value, description=None):
|
||||
self.label = label
|
||||
self.value = value
|
||||
self.description = description
|
||||
|
||||
class _FakeEmbed:
|
||||
def __init__(self, *, title, description, color):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.color = color
|
||||
|
||||
class _FakeColor:
|
||||
@staticmethod
|
||||
def green():
|
||||
return "green"
|
||||
|
||||
@staticmethod
|
||||
def blue():
|
||||
return "blue"
|
||||
|
||||
@staticmethod
|
||||
def red():
|
||||
return "red"
|
||||
|
||||
@staticmethod
|
||||
def greyple():
|
||||
return "greyple"
|
||||
|
||||
class _FakeButtonStyle:
|
||||
green = "green"
|
||||
grey = "grey"
|
||||
red = "red"
|
||||
blurple = "blurple"
|
||||
|
||||
discord_mod = sys.modules.get("discord") or MagicMock()
|
||||
discord_mod.Intents.default.return_value = MagicMock()
|
||||
discord_mod.DMChannel = type("DMChannel", (), {})
|
||||
discord_mod.Thread = type("Thread", (), {})
|
||||
discord_mod.ForumChannel = type("ForumChannel", (), {})
|
||||
discord_mod.Interaction = object
|
||||
discord_mod.Message = type("Message", (), {})
|
||||
discord_mod.SelectOption = _FakeSelectOption
|
||||
discord_mod.Embed = _FakeEmbed
|
||||
discord_mod.Color = _FakeColor
|
||||
discord_mod.ButtonStyle = _FakeButtonStyle
|
||||
discord_mod.app_commands = getattr(
|
||||
discord_mod,
|
||||
"app_commands",
|
||||
SimpleNamespace(describe=lambda **kwargs: (lambda fn: fn)),
|
||||
)
|
||||
discord_mod.ui = SimpleNamespace(
|
||||
View=_FakeView,
|
||||
Select=_FakeSelect,
|
||||
Button=_FakeButton,
|
||||
button=lambda **kwargs: (lambda fn: fn),
|
||||
)
|
||||
|
||||
ext_mod = MagicMock()
|
||||
commands_mod = MagicMock()
|
||||
commands_mod.Bot = MagicMock
|
||||
ext_mod.commands = commands_mod
|
||||
|
||||
sys.modules["discord"] = discord_mod
|
||||
sys.modules.setdefault("discord.ext", ext_mod)
|
||||
sys.modules.setdefault("discord.ext.commands", commands_mod)
|
||||
|
||||
|
||||
_ensure_discord_mock()
|
||||
|
||||
from gateway.platforms.discord import ModelPickerView
|
||||
|
||||
|
||||
|
|
@ -135,7 +33,12 @@ async def test_model_picker_clears_controls_before_running_switch_callback():
|
|||
)
|
||||
|
||||
async def edit_original_response(**kwargs):
|
||||
events.append(("final-edit", kwargs["embed"].title, kwargs["embed"].description, kwargs["view"]))
|
||||
events.append((
|
||||
"final-edit",
|
||||
kwargs["embed"].title,
|
||||
kwargs["embed"].description,
|
||||
kwargs["view"],
|
||||
))
|
||||
|
||||
view = ModelPickerView(
|
||||
providers=[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue