Add path_bytes_per_hop to bot kwargs and encourage kwargs-only going forward

This commit is contained in:
Jack Kingsman
2026-03-12 10:23:31 -07:00
parent 9e8cf56b31
commit 30f6f95d8e
6 changed files with 343 additions and 54 deletions
+101 -1
View File
@@ -312,6 +312,67 @@ def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name,
)
assert result == "outgoing=True"
def test_new_10_param_bot_receives_path_bytes_per_hop(self):
"""Bots that declare path_bytes_per_hop receive it positionally."""
code = """
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, is_outgoing, path_bytes_per_hop):
return f"bytes={path_bytes_per_hop}"
"""
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="aabb",
path_bytes_per_hop=2,
)
assert result == "bytes=2"
def test_9_param_bot_with_path_bytes_only_receives_it(self):
"""Bots may opt into path_bytes_per_hop without also declaring is_outgoing."""
code = """
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, path_bytes_per_hop):
return f"bytes={path_bytes_per_hop}"
"""
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="aabb",
is_outgoing=True,
path_bytes_per_hop=2,
)
assert result == "bytes=2"
def test_legacy_bot_with_kwargs_receives_path_bytes_per_hop(self):
"""Bots using **kwargs receive the new path_bytes_per_hop field."""
code = """
def bot(sender_name, sender_key, message_text, is_dm, channel_key, channel_name, sender_timestamp, path, **kwargs):
return f"bytes={kwargs.get('path_bytes_per_hop', '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="aabb",
path_bytes_per_hop=2,
)
assert result == "bytes=2"
def test_channel_message_with_none_sender_key(self):
"""Channel messages correctly pass None for sender_key."""
code = """
@@ -419,7 +480,14 @@ class TestBotCodeValidation:
from app.routers.fanout import _validate_bot_config
# Should not raise
_validate_bot_config({"code": "def bot(): return 'hello'"})
_validate_bot_config(
{
"code": (
"def bot(sender_name, sender_key, message_text, is_dm, channel_key, "
"channel_name, sender_timestamp, path):\n return 'hello'"
)
}
)
def test_syntax_error_raises(self):
"""Syntax error in code raises HTTPException."""
@@ -456,6 +524,38 @@ class TestBotCodeValidation:
assert exc_info.value.status_code == 400
def test_missing_bot_function_raises(self):
"""Code must define a callable bot() function."""
from fastapi import HTTPException
from app.routers.fanout import _validate_bot_config
with pytest.raises(HTTPException) as exc_info:
_validate_bot_config({"code": "def helper():\n return 'hello'"})
assert exc_info.value.status_code == 400
assert "callable bot() function" in exc_info.value.detail
def test_unsupported_signature_raises(self):
"""Unsupported bot signatures are rejected with guidance."""
from fastapi import HTTPException
from app.routers.fanout import _validate_bot_config
with pytest.raises(HTTPException) as exc_info:
_validate_bot_config(
{
"code": (
"def bot(sender_name, sender_key, message_text, is_dm, channel_key, "
"channel_name, sender_timestamp, path, *, extra_required):\n"
" return extra_required"
)
}
)
assert exc_info.value.status_code == 400
assert "signature is not supported" in exc_info.value.detail.lower()
class TestBotMessageRateLimiting:
"""Test bot message rate limiting for repeater compatibility."""
+10 -2
View File
@@ -36,6 +36,7 @@ class TestBotModuleParameterExtraction:
sender_timestamp,
path,
is_outgoing,
path_bytes_per_hop,
):
captured["is_outgoing"] = is_outgoing
captured["is_dm"] = is_dm
@@ -84,6 +85,7 @@ class TestBotModuleParameterExtraction:
sender_timestamp,
path,
is_outgoing,
path_bytes_per_hop,
):
captured["is_outgoing"] = is_outgoing
return None
@@ -129,8 +131,10 @@ class TestBotModuleParameterExtraction:
sender_timestamp,
path,
is_outgoing,
path_bytes_per_hop,
):
captured["path"] = path
captured["path_bytes_per_hop"] = path_bytes_per_hop
return None
mod = BotModule("test", {"code": "def bot(**k): pass"}, name="Test")
@@ -150,11 +154,12 @@ class TestBotModuleParameterExtraction:
"type": "PRIV",
"conversation_key": "pk1",
"text": "hello",
"paths": [{"path": "aabb", "rssi": -50}],
"paths": [{"path": "aabbccdd", "path_len": 2, "rssi": -50}],
}
)
assert captured["path"] == "aabb"
assert captured["path"] == "aabbccdd"
assert captured["path_bytes_per_hop"] == 2
@pytest.mark.asyncio
async def test_channel_sender_prefix_stripped(self):
@@ -174,6 +179,7 @@ class TestBotModuleParameterExtraction:
sender_timestamp,
path,
is_outgoing,
path_bytes_per_hop,
):
captured["message_text"] = message_text
captured["sender_name"] = sender_name
@@ -221,6 +227,7 @@ class TestBotModuleParameterExtraction:
sender_timestamp,
path,
is_outgoing,
path_bytes_per_hop,
):
captured["channel_name"] = channel_name
return None
@@ -267,6 +274,7 @@ class TestBotModuleParameterExtraction:
sender_timestamp,
path,
is_outgoing,
path_bytes_per_hop,
):
captured["sender_name"] = sender_name
captured["sender_key"] = sender_key