Misc bugs around dm region scope + scope display, and flood-scope leak

This commit is contained in:
Jack Kingsman
2026-07-08 20:11:57 -07:00
parent db954c0f26
commit 0633ab724c
9 changed files with 251 additions and 53 deletions
+47
View File
@@ -1398,6 +1398,53 @@ class TestCreateDMMessageFromDecrypted:
assert broadcast["paths"][0]["path"] == "aabbcc"
assert broadcast["paths"][0]["received_at"] == 1700000001
@pytest.mark.asyncio
async def test_dm_includes_region_scope_in_broadcast(self, test_db, captured_broadcasts):
"""A region-scoped (transport-routed) flood DM threads transport_code/region
into the stored row and the broadcast, so bots see `scoped`/`region` for DMs
(issue #300 DM half) and the UI can badge the scope."""
from app.decoder import DecryptedDirectMessage
from app.packet_processor import create_dm_message_from_decrypted
packet_id, _ = await RawPacketRepository.create(b"region_test_dm", 1700000000)
decrypted = DecryptedDirectMessage(
timestamp=1700000000,
flags=0,
message="Scoped DM",
dest_hash="fa",
src_hash="a1",
)
broadcasts, mock_broadcast = captured_broadcasts
with patch("app.packet_processor.broadcast_event", mock_broadcast):
msg_id = await create_dm_message_from_decrypted(
packet_id=packet_id,
decrypted=decrypted,
their_public_key=self.A1B2C3_PUB,
our_public_key=self.FACE12_PUB,
received_at=1700000001,
outgoing=False,
transport_code=0x1234,
region="#Esperance",
)
assert msg_id is not None
message_broadcasts = [b for b in broadcasts if b["type"] == "message"]
assert len(message_broadcasts) == 1
broadcast = message_broadcasts[0]["data"]
# Bots derive `scoped = transport_code is not None` and read `region`.
assert broadcast["transport_code"] == 0x1234
assert broadcast["region"] == "#Esperance"
# Persisted so the scope survives a raw-packet purge, mirroring channel msgs.
stored = await MessageRepository.get_by_id(msg_id)
assert stored is not None
assert stored.transport_code == 0x1234
assert stored.region == "#Esperance"
class TestDMDecryptionFunction:
"""Test the DM decryption function with real crypto."""
+44
View File
@@ -1426,6 +1426,50 @@ class TestPathHashModeOverride:
assert "path hash mode" in exc_info.value.detail.lower()
mc.commands.send_chan_msg.assert_not_awaited()
@pytest.mark.asyncio
async def test_send_channel_msg_restores_scope_when_phm_apply_fails(self, test_db):
"""A flood-scope override that was applied must still be restored when the
subsequent path-hash-mode apply fails. Regression: the scope apply and the
PHM apply used to sit outside the try/finally, so a PHM error left the radio
stuck on the temporary region scope for all later traffic."""
mc = _make_mc(name="MyNode")
# PHM apply (first call) errors -> raises; PHM restore (second call, in the
# finally) succeeds so this test isolates the scope-restore behavior.
mc.commands.set_path_hash_mode = AsyncMock(
side_effect=[
MagicMock(type=EventType.ERROR, payload="unsupported mode"),
_make_radio_result(),
]
)
chan_key = "f6" * 16
await ChannelRepository.upsert(key=chan_key, name="#both")
await ChannelRepository.update_flood_scope_override(chan_key, "Esperance")
await ChannelRepository.update_path_hash_mode_override(chan_key, 2)
await AppSettingsRepository.update(flood_scope="Baseline")
radio_manager.path_hash_mode = 0
radio_manager.path_hash_mode_supported = True
with (
patch("app.routers.messages.radio_manager.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch("app.routers.messages.broadcast_event"),
pytest.raises(HTTPException) as exc_info,
):
await send_channel_message(
SendChannelMessageRequest(channel_key=chan_key, text="hello")
)
assert exc_info.value.status_code == 422
assert "path hash mode" in exc_info.value.detail.lower()
# The send never happens...
mc.commands.send_chan_msg.assert_not_awaited()
# ...but the applied region scope is restored to the global baseline.
assert mc.commands.set_flood_scope.await_args_list == [
call("#Esperance"),
call("#Baseline"),
]
@pytest.mark.asyncio
async def test_send_channel_msg_phm_restore_failure_broadcasts_error(self, test_db):
"""Message sends OK but restore failure after 3 attempts broadcasts an error."""