mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-03 07:22:31 +02:00
Add incoming region to kwargs
This commit is contained in:
@@ -397,6 +397,106 @@ def bot(**kwargs):
|
||||
)
|
||||
assert result == "Alice|Hi|True|2"
|
||||
|
||||
def test_kwargs_bot_receives_region(self):
|
||||
"""Bots using **kwargs receive the region of a scoped channel message (#300)."""
|
||||
code = """
|
||||
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, **kwargs):
|
||||
return f"region={kwargs.get('region', 'missing')}"
|
||||
"""
|
||||
result = execute_bot_code(
|
||||
code=code,
|
||||
sender_name="Someone",
|
||||
sender_key=None,
|
||||
message_text="Hi",
|
||||
is_dm=False,
|
||||
channel_key="AABBCCDD",
|
||||
channel_name="#general",
|
||||
sender_timestamp=None,
|
||||
path=None,
|
||||
region="EU",
|
||||
)
|
||||
assert result == "region=EU"
|
||||
|
||||
def test_named_region_param_bot_receives_region(self):
|
||||
"""Bots may opt into region by naming the parameter (with a default)."""
|
||||
code = """
|
||||
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, region=None):
|
||||
return f"region={region}"
|
||||
"""
|
||||
result = execute_bot_code(
|
||||
code=code,
|
||||
sender_name="Someone",
|
||||
sender_key=None,
|
||||
message_text="Hi",
|
||||
is_dm=False,
|
||||
channel_key="AABBCCDD",
|
||||
channel_name="#general",
|
||||
sender_timestamp=None,
|
||||
path=None,
|
||||
region="US",
|
||||
)
|
||||
assert result == "region=US"
|
||||
|
||||
def test_required_keyword_only_region_param_is_supported(self):
|
||||
"""A required keyword-only `region` parameter is accepted (allow-set parity)."""
|
||||
code = """
|
||||
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, *, region):
|
||||
return f"region={region}"
|
||||
"""
|
||||
result = execute_bot_code(
|
||||
code=code,
|
||||
sender_name="Someone",
|
||||
sender_key=None,
|
||||
message_text="Hi",
|
||||
is_dm=False,
|
||||
channel_key="AABBCCDD",
|
||||
channel_name="#general",
|
||||
sender_timestamp=None,
|
||||
path=None,
|
||||
region="EU",
|
||||
)
|
||||
assert result == "region=EU"
|
||||
|
||||
def test_kwargs_bot_receives_none_region_for_unscoped_message(self):
|
||||
"""region is delivered as None (not absent) for DMs / unscoped flood."""
|
||||
code = """
|
||||
def bot(**kwargs):
|
||||
return f"region={kwargs.get('region', 'missing')}"
|
||||
"""
|
||||
result = execute_bot_code(
|
||||
code=code,
|
||||
sender_name="Alice",
|
||||
sender_key="abc123",
|
||||
message_text="Hi",
|
||||
is_dm=True,
|
||||
channel_key=None,
|
||||
channel_name=None,
|
||||
sender_timestamp=None,
|
||||
path=None,
|
||||
)
|
||||
assert result == "region=None"
|
||||
|
||||
def test_legacy_positional_bot_unaffected_by_region(self):
|
||||
"""Historical positional bots keep binding unchanged; region is never passed positionally."""
|
||||
code = """
|
||||
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, is_outgoing):
|
||||
return f"ok:{message_text}"
|
||||
"""
|
||||
result = execute_bot_code(
|
||||
code=code,
|
||||
sender_name="Alice",
|
||||
sender_key="abc123",
|
||||
message_text="Hi",
|
||||
is_dm=True,
|
||||
channel_key=None,
|
||||
channel_name=None,
|
||||
sender_timestamp=None,
|
||||
path=None,
|
||||
is_outgoing=False,
|
||||
region="EU",
|
||||
)
|
||||
assert result == "ok:Hi"
|
||||
|
||||
def test_channel_message_with_none_sender_key(self):
|
||||
"""Channel messages correctly pass None for sender_key."""
|
||||
code = """
|
||||
|
||||
@@ -38,6 +38,7 @@ class TestBotModuleParameterExtraction:
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["is_outgoing"] = is_outgoing
|
||||
captured["is_dm"] = is_dm
|
||||
@@ -88,6 +89,7 @@ class TestBotModuleParameterExtraction:
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["is_outgoing"] = is_outgoing
|
||||
return None
|
||||
@@ -135,6 +137,7 @@ class TestBotModuleParameterExtraction:
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["path"] = path
|
||||
captured["path_bytes_per_hop"] = path_bytes_per_hop
|
||||
@@ -164,6 +167,103 @@ class TestBotModuleParameterExtraction:
|
||||
assert captured["path"] == "aabbccdd"
|
||||
assert captured["path_bytes_per_hop"] == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_region_extracted_from_payload(self):
|
||||
"""A channel message's resolved region is pulled from data and forwarded (#300)."""
|
||||
from app.fanout.bot import BotModule
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_execute(
|
||||
code,
|
||||
sender_name,
|
||||
sender_key,
|
||||
message_text,
|
||||
is_dm,
|
||||
channel_key,
|
||||
channel_name,
|
||||
sender_timestamp,
|
||||
path,
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["region"] = region
|
||||
return None
|
||||
|
||||
mod = BotModule("test", {"code": "def bot(**k): pass"}, name="Test")
|
||||
|
||||
with (
|
||||
patch("app.fanout.bot_exec.execute_bot_code", side_effect=fake_execute),
|
||||
patch(
|
||||
"app.fanout.bot_exec._bot_semaphore",
|
||||
MagicMock(__aenter__=AsyncMock(), __aexit__=AsyncMock()),
|
||||
),
|
||||
patch("app.fanout.bot.asyncio.sleep", new_callable=AsyncMock),
|
||||
patch("app.repository.ChannelRepository") as mock_chan,
|
||||
):
|
||||
mock_chan.get_by_key = AsyncMock(return_value=MagicMock(name="#test"))
|
||||
await mod._run_for_message(
|
||||
{
|
||||
"type": "CHAN",
|
||||
"conversation_key": "ch1",
|
||||
"text": "Alice: hello",
|
||||
"sender_name": "Alice",
|
||||
"channel_name": "#test",
|
||||
"region": "EU",
|
||||
}
|
||||
)
|
||||
|
||||
assert captured["region"] == "EU"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_region_absent_defaults_to_none(self):
|
||||
"""A message without a region key forwards region=None to bot execution."""
|
||||
from app.fanout.bot import BotModule
|
||||
|
||||
captured = {}
|
||||
|
||||
def fake_execute(
|
||||
code,
|
||||
sender_name,
|
||||
sender_key,
|
||||
message_text,
|
||||
is_dm,
|
||||
channel_key,
|
||||
channel_name,
|
||||
sender_timestamp,
|
||||
path,
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["region"] = region
|
||||
return None
|
||||
|
||||
mod = BotModule("test", {"code": "def bot(**k): pass"}, name="Test")
|
||||
|
||||
with (
|
||||
patch("app.fanout.bot_exec.execute_bot_code", side_effect=fake_execute),
|
||||
patch(
|
||||
"app.fanout.bot_exec._bot_semaphore",
|
||||
MagicMock(__aenter__=AsyncMock(), __aexit__=AsyncMock()),
|
||||
),
|
||||
patch("app.fanout.bot.asyncio.sleep", new_callable=AsyncMock),
|
||||
patch("app.repository.ContactRepository") as mock_contact,
|
||||
):
|
||||
mock_contact.get_by_key = AsyncMock(return_value=MagicMock(name="Alice"))
|
||||
await mod._run_for_message(
|
||||
{
|
||||
"type": "PRIV",
|
||||
"conversation_key": "pk1",
|
||||
"text": "hello",
|
||||
}
|
||||
)
|
||||
|
||||
assert captured["region"] is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_channel_sender_prefix_stripped(self):
|
||||
"""Channel message text has 'SenderName: ' prefix stripped."""
|
||||
@@ -184,6 +284,7 @@ class TestBotModuleParameterExtraction:
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["message_text"] = message_text
|
||||
captured["sender_name"] = sender_name
|
||||
@@ -233,6 +334,7 @@ class TestBotModuleParameterExtraction:
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["channel_name"] = channel_name
|
||||
return None
|
||||
@@ -281,6 +383,7 @@ class TestBotModuleParameterExtraction:
|
||||
is_outgoing,
|
||||
path_bytes_per_hop,
|
||||
packet_hash,
|
||||
region,
|
||||
):
|
||||
captured["sender_name"] = sender_name
|
||||
captured["sender_key"] = sender_key
|
||||
|
||||
@@ -1749,6 +1749,45 @@ class TestBotModuleLifecycle:
|
||||
assert mod._active is False
|
||||
assert len(mod._tasks) == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_channel_message_region_reaches_bot_kwargs(self):
|
||||
"""A scoped channel message's resolved region flows to bot **kwargs (#300)."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from app.fanout.bot import BotModule
|
||||
|
||||
mod = BotModule(
|
||||
"bot1",
|
||||
{"code": "def bot(**k): return f\"region={k.get('region')}\""},
|
||||
name="Test Bot",
|
||||
)
|
||||
mod._active = True
|
||||
|
||||
captured: dict = {}
|
||||
|
||||
async def capture(response, is_dm, sender_key, channel_key):
|
||||
captured["response"] = response
|
||||
|
||||
with (
|
||||
patch("app.fanout.bot.asyncio.sleep", new_callable=AsyncMock),
|
||||
patch("app.fanout.bot_exec.process_bot_response", side_effect=capture),
|
||||
):
|
||||
await mod.on_message(
|
||||
{
|
||||
"type": "CHAN",
|
||||
"conversation_key": "AABBCCDD",
|
||||
"text": "Alice: hi",
|
||||
"sender_name": "Alice",
|
||||
"channel_name": "#general",
|
||||
"outgoing": False,
|
||||
"region": "EU",
|
||||
}
|
||||
)
|
||||
if mod._tasks:
|
||||
await asyncio.gather(*mod._tasks, return_exceptions=True)
|
||||
|
||||
assert captured.get("response") == "region=EU"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Manager restart failure tests
|
||||
|
||||
Reference in New Issue
Block a user