mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-10 13:31:38 +00:00
WhatsAppAdapter lives under plugins/platforms/whatsapp/adapter.py on current upstream; the owner-forward test still imported the removed gateway.platforms.whatsapp module.
121 lines
4 KiB
Python
121 lines
4 KiB
Python
"""Tests for WhatsApp owner-message metadata and source-level text tagging.
|
|
|
|
The Node bridge sets ``fromOwner: true`` on inbound `fromMe` messages that
|
|
look owner-typed (linked-device send, not echoed from /send) when the
|
|
operator opts into ``WHATSAPP_FORWARD_OWNER_MESSAGES``. These tests pin
|
|
the adapter's responsibility: lift that flag onto
|
|
``MessageEvent.metadata["whatsapp_from_owner"]``, prefix ``MessageEvent.text``
|
|
with ``[owner reply] ``, and otherwise leave metadata absent and text
|
|
unchanged. The env-var gate itself lives in the bridge — the adapter just
|
|
trusts the payload.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
from gateway.config import Platform, PlatformConfig
|
|
from plugins.platforms.whatsapp.adapter import WhatsAppAdapter
|
|
|
|
|
|
def _make_adapter():
|
|
adapter = WhatsAppAdapter.__new__(WhatsAppAdapter)
|
|
adapter.platform = Platform.WHATSAPP
|
|
adapter.config = PlatformConfig(enabled=True)
|
|
adapter._message_handler = AsyncMock()
|
|
adapter._dm_policy = "open"
|
|
adapter._allow_from = set()
|
|
adapter._group_policy = "open"
|
|
adapter._group_allow_from = set()
|
|
adapter._mention_patterns = []
|
|
adapter._free_response_chats = set()
|
|
adapter._whatsapp_free_response_chats = lambda: set()
|
|
return adapter
|
|
|
|
|
|
def _dm_payload(**overrides):
|
|
payload = {
|
|
"messageId": "M1",
|
|
"chatId": "6281234567890@s.whatsapp.net",
|
|
"senderId": "6281234567890@s.whatsapp.net",
|
|
"senderName": "Customer",
|
|
"chatName": "Customer",
|
|
"isGroup": False,
|
|
"body": "hi from the linked phone",
|
|
"hasMedia": False,
|
|
"mediaType": "",
|
|
"mediaUrls": [],
|
|
"mentionedIds": [],
|
|
"quotedParticipant": "",
|
|
"botIds": [],
|
|
"timestamp": 0,
|
|
}
|
|
payload.update(overrides)
|
|
return payload
|
|
|
|
|
|
def test_metadata_flag_set_when_payload_has_from_owner():
|
|
adapter = _make_adapter()
|
|
payload = _dm_payload(fromOwner=True)
|
|
|
|
event = asyncio.run(adapter._build_message_event(payload))
|
|
|
|
assert event is not None
|
|
assert event.metadata.get("whatsapp_from_owner") is True
|
|
assert event.text.startswith("[owner reply] ")
|
|
assert event.text == "[owner reply] hi from the linked phone"
|
|
|
|
|
|
def test_from_owner_does_not_double_prefix_when_already_tagged():
|
|
adapter = _make_adapter()
|
|
payload = _dm_payload(
|
|
fromOwner=True,
|
|
body="[owner reply] already tagged",
|
|
)
|
|
|
|
event = asyncio.run(adapter._build_message_event(payload))
|
|
|
|
assert event is not None
|
|
assert event.metadata.get("whatsapp_from_owner") is True
|
|
assert event.text == "[owner reply] already tagged"
|
|
|
|
|
|
def test_from_owner_prefixes_empty_body_for_uniform_media_placeholders():
|
|
"""Owner media with empty caption still gets the marker (bridge may
|
|
substitute placeholders like ``[image received]`` upstream; empty stays
|
|
tagged for consistency)."""
|
|
adapter = _make_adapter()
|
|
payload = _dm_payload(fromOwner=True, body="")
|
|
|
|
event = asyncio.run(adapter._build_message_event(payload))
|
|
|
|
assert event is not None
|
|
assert event.metadata.get("whatsapp_from_owner") is True
|
|
assert event.text == "[owner reply] "
|
|
|
|
|
|
def test_metadata_flag_absent_by_default():
|
|
"""Default bridge payload (env flag off → field never present) must not
|
|
leak the metadata key. Plugins use ``.get(...)`` and rely on absence."""
|
|
adapter = _make_adapter()
|
|
payload = _dm_payload()
|
|
|
|
event = asyncio.run(adapter._build_message_event(payload))
|
|
|
|
assert event is not None
|
|
assert "whatsapp_from_owner" not in event.metadata
|
|
|
|
|
|
def test_metadata_flag_absent_when_explicitly_false():
|
|
"""Explicit fromOwner=false must not set the metadata key — plugins
|
|
test for truthiness, but absence is the canonical "not owner" state."""
|
|
adapter = _make_adapter()
|
|
payload = _dm_payload(fromOwner=False)
|
|
|
|
event = asyncio.run(adapter._build_message_event(payload))
|
|
|
|
assert event is not None
|
|
assert "whatsapp_from_owner" not in event.metadata
|