Tighten up message broadcast contract

This commit is contained in:
Jack Kingsman
2026-03-06 15:55:04 -08:00
parent 3330028d27
commit dd13768a44
6 changed files with 220 additions and 8 deletions
+95
View File
@@ -203,6 +203,101 @@ class TestBotModuleParameterExtraction:
assert captured["message_text"] == "the actual message"
assert captured["sender_name"] == "Alice"
@pytest.mark.asyncio
async def test_channel_name_uses_payload_before_db_lookup(self):
"""Channel fanout payload channel_name is preserved even if the DB lookup misses."""
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,
):
captured["channel_name"] = channel_name
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=None)
await mod._run_for_message(
{
"type": "CHAN",
"conversation_key": "ch1",
"channel_name": "#payload",
"text": "Alice: hello",
"sender_name": "Alice",
}
)
assert captured["channel_name"] == "#payload"
@pytest.mark.asyncio
async def test_dm_sender_name_uses_payload_before_db_lookup(self):
"""Incoming DM sender_name from the message payload should be preserved."""
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,
):
captured["sender_name"] = sender_name
captured["sender_key"] = sender_key
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=None)
await mod._run_for_message(
{
"type": "PRIV",
"conversation_key": "pk1",
"sender_name": "PayloadAlice",
"sender_key": "pk1",
"text": "hello",
"outgoing": False,
}
)
assert captured["sender_name"] == "PayloadAlice"
assert captured["sender_key"] == "pk1"
# ---------------------------------------------------------------------------
# T2: Migration 036, 037, 038 tests
+86
View File
@@ -196,6 +196,54 @@ class TestFanoutMqttIntegration:
assert "alpha/dm:pk1" in topics
assert "beta/dm:pk1" in topics
@pytest.mark.asyncio
async def test_private_mqtt_preserves_full_message_payload(self, mqtt_broker, integration_db):
"""Private MQTT publishes the full message payload without dropping fields."""
from unittest.mock import patch
cfg = await FanoutConfigRepository.create(
config_type="mqtt_private",
name="Full Payload",
config=_private_config(mqtt_broker.port, "mesh"),
scope={"messages": "all", "raw_packets": "all"},
enabled=True,
)
payload = {
"type": "CHAN",
"conversation_key": "ch1",
"channel_name": "#general",
"text": "Alice: hello mqtt",
"sender_name": "Alice",
"sender_key": "ab" * 32,
"sender_timestamp": 1700000000,
"received_at": 1700000001,
"paths": [{"path": "aabb", "received_at": 1700000001}],
"outgoing": False,
"acked": 2,
}
manager = FanoutManager()
with (
patch("app.fanout.mqtt_base._broadcast_health"),
patch("app.websocket.broadcast_success"),
patch("app.websocket.broadcast_error"),
patch("app.websocket.broadcast_health"),
):
try:
await manager.load_from_db()
await _wait_connected(manager, cfg["id"])
await manager.broadcast_message(payload)
messages = await mqtt_broker.wait_for(1)
finally:
await manager.stop_all()
assert len(messages) == 1
topic, body = messages[0]
assert topic == "mesh/gm:ch1"
assert body == payload
@pytest.mark.asyncio
async def test_one_disabled_only_enabled_receives(self, mqtt_broker, integration_db):
"""Disabled integration must not publish any messages."""
@@ -565,6 +613,44 @@ class TestFanoutWebhookIntegration:
assert len(results) == 1
assert results[0]["headers"].get("x-custom") == "my-value"
@pytest.mark.asyncio
async def test_webhook_preserves_full_message_payload(self, webhook_server, integration_db):
"""Webhook delivers the full message payload body without dropping fields."""
cfg = await FanoutConfigRepository.create(
config_type="webhook",
name="Full Payload Hook",
config=_webhook_config(webhook_server.port),
scope={"messages": "all", "raw_packets": "none"},
enabled=True,
)
payload = {
"type": "CHAN",
"conversation_key": "ch1",
"channel_name": "#general",
"text": "Alice: hello webhook",
"sender_name": "Alice",
"sender_key": "ab" * 32,
"sender_timestamp": 1700000000,
"received_at": 1700000001,
"paths": [{"path": "aabb", "received_at": 1700000001}],
"outgoing": False,
"acked": 2,
}
manager = FanoutManager()
try:
await manager.load_from_db()
await _wait_connected(manager, cfg["id"])
await manager.broadcast_message(payload)
results = await webhook_server.wait_for(1)
finally:
await manager.stop_all()
assert len(results) == 1
assert results[0]["body"] == payload
@pytest.mark.asyncio
async def test_webhook_hmac_signature(self, webhook_server, integration_db):
"""Webhook sends HMAC-SHA256 signature when hmac_secret is configured."""
+3
View File
@@ -157,6 +157,7 @@ class TestOutgoingChannelBroadcast:
assert data["type"] == "CHAN"
assert data["conversation_key"] == chan_key.upper()
assert data["sender_name"] == "MyNode"
assert data["channel_name"] == "#general"
@pytest.mark.asyncio
async def test_send_channel_msg_response_includes_current_ack_count(self, test_db):
@@ -177,6 +178,7 @@ class TestOutgoingChannelBroadcast:
# Fresh message has acked=0
assert message.id is not None
assert message.acked == 0
assert message.channel_name == "#acked"
@pytest.mark.asyncio
async def test_send_channel_msg_includes_sender_key(self, test_db):
@@ -498,6 +500,7 @@ class TestResendChannelMessage:
assert event_type == "message"
assert event_data["id"] == result["message_id"]
assert event_data["outgoing"] is True
assert event_data["channel_name"] == "#broadcast"
@pytest.mark.asyncio
async def test_resend_byte_perfect_still_enforces_window(self, test_db):