Add background-hash-mark addition for region routing

Per https://buymeacoffee.com/ripplebiz/region-filtering:

> After some discussions, and that there is some confusion
around #channels and #regions, it's been decided to drop
the requirement to have the '#' prefix. So, region names
will just be plain alphanumeric (and '-'), with no # prefix.

> For backwards compatibility, the names will internally have
a '#' prepended, but for all client GUI's and command lines,
you generally won't see mention of '#' prefixes. The next
firmware release (v1.12.0) and subsequent Ripple firmware
and Liam's app will have modified UI to remove the '#' requirement.

So, silently add, but don't duplicate, for users who have already
added hashmarks.
This commit is contained in:
Jack Kingsman
2026-03-09 15:24:23 -07:00
parent e03ddcaaa7
commit b157ee14e4
13 changed files with 107 additions and 24 deletions
+15 -1
View File
@@ -270,7 +270,7 @@ class TestChannelFloodScopeOverride:
with patch("app.routers.channels.broadcast_event") as mock_broadcast:
response = await client.post(
f"/api/channels/{key}/flood-scope-override",
json={"flood_scope_override": "#Esperance"},
json={"flood_scope_override": "Esperance"},
)
assert response.status_code == 200
@@ -283,6 +283,20 @@ class TestChannelFloodScopeOverride:
mock_broadcast.assert_called_once()
assert mock_broadcast.call_args.args[0] == "channel"
@pytest.mark.asyncio
async def test_existing_hash_is_not_doubled(self, test_db, client):
key = "CC" * 16
await ChannelRepository.upsert(key=key, name="#flightless", is_hashtag=True)
response = await client.post(
f"/api/channels/{key}/flood-scope-override",
json={"flood_scope_override": "#Esperance"},
)
assert response.status_code == 200
data = response.json()
assert data["flood_scope_override"] == "#Esperance"
@pytest.mark.asyncio
async def test_blank_override_clears_channel_flood_scope_override(self, test_db, client):
key = "BB" * 16
+34
View File
@@ -655,6 +655,40 @@ class TestPostConnectSetupOrdering:
mock_mc.commands.set_flood_scope.assert_awaited_once_with("#TestRegion")
@pytest.mark.asyncio
async def test_plain_flood_scope_is_normalized_during_setup(self):
"""Legacy/plain stored flood_scope is normalized before applying to radio."""
from app.models import AppSettings
from app.radio import RadioManager
rm = RadioManager()
mock_mc = MagicMock()
mock_mc.start_auto_message_fetching = AsyncMock()
mock_mc.commands.set_flood_scope = AsyncMock()
rm._meshcore = mock_mc
mock_settings = AppSettings(flood_scope="TestRegion")
with (
patch("app.event_handlers.register_event_handlers"),
patch("app.keystore.export_and_store_private_key", new_callable=AsyncMock),
patch("app.radio_sync.sync_radio_time", new_callable=AsyncMock),
patch(
"app.repository.AppSettingsRepository.get",
new_callable=AsyncMock,
return_value=mock_settings,
),
patch("app.radio_sync.sync_and_offload_all", new_callable=AsyncMock, return_value={}),
patch("app.radio_sync.start_periodic_sync"),
patch("app.radio_sync.send_advertisement", new_callable=AsyncMock, return_value=False),
patch("app.radio_sync.start_periodic_advert"),
patch("app.radio_sync.drain_pending_messages", new_callable=AsyncMock, return_value=0),
patch("app.radio_sync.start_message_polling"),
):
await rm.post_connect_setup()
mock_mc.commands.set_flood_scope.assert_awaited_once_with("#TestRegion")
@pytest.mark.asyncio
async def test_flood_scope_empty_resets_during_setup(self):
"""Empty flood_scope calls set_flood_scope("") during post_connect_setup."""
+4 -4
View File
@@ -279,8 +279,8 @@ class TestOutgoingChannelBroadcast:
mc = _make_mc(name="MyNode")
chan_key = "de" * 16
await ChannelRepository.upsert(key=chan_key, name="#flightless")
await ChannelRepository.update_flood_scope_override(chan_key, "#Esperance")
await AppSettingsRepository.update(flood_scope="#Baseline")
await ChannelRepository.update_flood_scope_override(chan_key, "Esperance")
await AppSettingsRepository.update(flood_scope="Baseline")
with (
patch("app.routers.messages.require_connected", return_value=mc),
@@ -303,8 +303,8 @@ class TestOutgoingChannelBroadcast:
mc = _make_mc(name="MyNode")
chan_key = "df" * 16
await ChannelRepository.upsert(key=chan_key, name="#matching")
await ChannelRepository.update_flood_scope_override(chan_key, "#Esperance")
await AppSettingsRepository.update(flood_scope="#Esperance")
await ChannelRepository.update_flood_scope_override(chan_key, "Esperance")
await AppSettingsRepository.update(flood_scope="Esperance")
with (
patch("app.routers.messages.require_connected", return_value=mc),
+9 -3
View File
@@ -55,7 +55,7 @@ class TestUpdateSettings:
@pytest.mark.asyncio
async def test_flood_scope_round_trip(self, test_db):
"""Flood scope should be saved and retrieved correctly."""
result = await update_settings(AppSettingsUpdate(flood_scope="#MyRegion"))
result = await update_settings(AppSettingsUpdate(flood_scope="MyRegion"))
assert result.flood_scope == "#MyRegion"
fresh = await AppSettingsRepository.get()
@@ -70,7 +70,13 @@ class TestUpdateSettings:
@pytest.mark.asyncio
async def test_flood_scope_whitespace_stripped(self, test_db):
"""Flood scope should be stripped of whitespace."""
result = await update_settings(AppSettingsUpdate(flood_scope=" #MyRegion "))
result = await update_settings(AppSettingsUpdate(flood_scope=" MyRegion "))
assert result.flood_scope == "#MyRegion"
@pytest.mark.asyncio
async def test_flood_scope_existing_hash_is_not_doubled(self, test_db):
"""Existing leading hash should be preserved for backward compatibility."""
result = await update_settings(AppSettingsUpdate(flood_scope="#MyRegion"))
assert result.flood_scope == "#MyRegion"
@pytest.mark.asyncio
@@ -92,7 +98,7 @@ class TestUpdateSettings:
mock_rm.radio_operation = mock_radio_op
with patch("app.radio.radio_manager", mock_rm):
await update_settings(AppSettingsUpdate(flood_scope="#TestRegion"))
await update_settings(AppSettingsUpdate(flood_scope="TestRegion"))
mock_mc.commands.set_flood_scope.assert_awaited_once_with("#TestRegion")