fix(gateway): resolve _get_dm_topic_info on adapter class, not instance
Follow-up to the synthetic-notification DM-topic routing fix. The new _is_telegram_dm_topic_target probed the adapter's _get_dm_topic_info via instance-level getattr, which a MagicMock auto-creates as a truthy callable — so any test double with a non-dm chat_type and a thread_id would be misclassified as a DM topic lane and have the fallback routing keys injected. Resolve the method on type(adapter) and treat only dict-shaped returns as an operator-declared topic, mirroring the existing guard in _rename_telegram_topic_for_session_title. Update the home-channel startup test to declare _get_dm_topic_info on a real adapter subclass instead of patching a MagicMock onto the instance.
This commit is contained in:
@ -14261,12 +14261,22 @@ class GatewayRunner:
|
||||
return False
|
||||
if chat_type == "dm":
|
||||
return True
|
||||
get_dm_topic_info = getattr(adapter, "_get_dm_topic_info", None)
|
||||
if callable(get_dm_topic_info) and chat_id:
|
||||
try:
|
||||
return bool(get_dm_topic_info(str(chat_id), str(thread_id)))
|
||||
except Exception:
|
||||
logger.debug("Failed to inspect Telegram DM topic metadata", exc_info=True)
|
||||
# Inspect operator-declared DM topics via the adapter's lookup. Resolve
|
||||
# the method on the CLASS, not the instance: getattr() on a MagicMock
|
||||
# auto-creates a callable child for any attribute, so an instance-level
|
||||
# lookup would report a DM topic for every test double. Only a
|
||||
# dict-shaped return counts as an operator-declared topic — a bare
|
||||
# MagicMock or other sentinel must not. Mirrors the guard in
|
||||
# _rename_telegram_topic_for_session_title.
|
||||
if adapter is not None and chat_id:
|
||||
get_dm_topic_info = getattr(type(adapter), "_get_dm_topic_info", None)
|
||||
if callable(get_dm_topic_info):
|
||||
try:
|
||||
topic_info = get_dm_topic_info(adapter, str(chat_id), str(thread_id))
|
||||
except Exception:
|
||||
logger.debug("Failed to inspect Telegram DM topic metadata", exc_info=True)
|
||||
else:
|
||||
return isinstance(topic_info, dict)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
|
||||
@ -261,7 +261,16 @@ async def test_send_home_channel_startup_notification_preserves_thread_metadata(
|
||||
name="Ops Topic",
|
||||
thread_id="777",
|
||||
)
|
||||
adapter._get_dm_topic_info = MagicMock(return_value={"name": "Ops Topic"})
|
||||
# Declare the DM-topic lookup on the adapter CLASS, not the instance.
|
||||
# _is_telegram_dm_topic_target resolves _get_dm_topic_info via type(adapter)
|
||||
# so a MagicMock auto-attribute (instance-level) is intentionally ignored;
|
||||
# a real adapter exposes the method on its class. Mirrors the fake-adapter
|
||||
# pattern in test_telegram_topic_mode.py.
|
||||
class _DmTopicAdapter(type(adapter)):
|
||||
def _get_dm_topic_info(self, chat_id, thread_id):
|
||||
return {"name": "Ops Topic"}
|
||||
|
||||
adapter.__class__ = _DmTopicAdapter
|
||||
adapter.send = AsyncMock(return_value=SendResult(success=True, message_id="home"))
|
||||
|
||||
delivered = await runner._send_home_channel_startup_notifications()
|
||||
|
||||
Reference in New Issue
Block a user