hermes-agent/tests/gateway/test_discord_model_picker.py
Teknium 9dbad1990b
test(discord): align clarify/model-picker tests with fail-closed component auth (#41338)
Three gateway tests broke on main after the component-auth security
hardening (test_discord_component_auth.py) made empty Discord component
allowlists fail-closed: a view built with allowed_user_ids=set() now
rejects every click instead of allowing anyone.

The clarify and model-picker BEHAVIOR tests still constructed their views
with an empty allowlist and expected the click to succeed — a stale
assumption from before the hardening. Fixed by giving each view an
allowlist containing the clicking user (the interaction's own id), which
is the realistic shape and what the security model requires.

Production code unchanged — this only updates the test fixtures to match
the intended (and separately pinned) fail-closed contract. The security
regression suite and these behavior suites now both pass.

Fixes:
- test_discord_clarify_buttons.py: test_choice_falls_back_to_label_text_when_entry_missing, test_other_flips_entry_to_awaiting_text
- test_discord_model_picker.py: test_model_picker_clears_controls_before_running_switch_callback
2026-06-07 08:27:40 -07:00

83 lines
2.6 KiB
Python

"""Regression tests for the Discord /model picker.
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
from plugins.platforms.discord.adapter import ModelPickerView
@pytest.mark.asyncio
async def test_model_picker_clears_controls_before_running_switch_callback():
events: list[object] = []
async def on_model_selected(chat_id: str, model_id: str, provider_slug: str) -> str:
events.append(("switch", chat_id, model_id, provider_slug))
return "Model switched"
async def edit_message(**kwargs):
events.append(
(
"initial-edit",
kwargs["embed"].title,
kwargs["embed"].description,
kwargs["view"],
)
)
async def edit_original_response(**kwargs):
events.append((
"final-edit",
kwargs["embed"].title,
kwargs["embed"].description,
kwargs["view"],
))
view = ModelPickerView(
providers=[
{
"slug": "copilot",
"name": "GitHub Copilot",
"models": ["gpt-5.4"],
"total_models": 1,
"is_current": True,
}
],
current_model="gpt-5-mini",
current_provider="copilot",
session_key="session-1",
on_model_selected=on_model_selected,
allowed_user_ids={"123"}, # matches the interaction user; empty = fail-closed
)
view._selected_provider = "copilot"
interaction = SimpleNamespace(
user=SimpleNamespace(id=123),
channel_id=456,
data={"values": ["gpt-5.4"]},
response=SimpleNamespace(
defer=AsyncMock(),
send_message=AsyncMock(),
edit_message=AsyncMock(side_effect=edit_message),
),
edit_original_response=AsyncMock(side_effect=edit_original_response),
)
await view._on_model_selected(interaction)
assert events == [
("initial-edit", "⚙ Switching Model", "Switching to `gpt-5.4`...", None),
("switch", "456", "gpt-5.4", "copilot"),
("final-edit", "⚙ Model Switched", "Model switched", None),
]
interaction.response.edit_message.assert_awaited_once()
interaction.response.defer.assert_not_called()
interaction.edit_original_response.assert_awaited_once()