mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-06-30 15:01:12 +02:00
Add hourly sync and crow loudly if it finds a discrepancy
This commit is contained in:
+4
-5
@@ -751,8 +751,8 @@ class TestPostConnectSetupOrdering:
|
||||
mock_mc.commands.set_flood_scope.assert_awaited_once_with("")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_polling_disabled_by_default(self):
|
||||
"""Post-connect setup does not start fallback polling unless explicitly enabled."""
|
||||
async def test_message_polling_starts_hourly_audit_by_default(self):
|
||||
"""Post-connect setup always starts the message audit task by default."""
|
||||
from app.models import AppSettings
|
||||
from app.radio import RadioManager
|
||||
|
||||
@@ -780,11 +780,11 @@ class TestPostConnectSetupOrdering:
|
||||
):
|
||||
await rm.post_connect_setup()
|
||||
|
||||
mock_start_message_polling.assert_not_called()
|
||||
mock_start_message_polling.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_polling_starts_when_env_flag_enabled(self):
|
||||
"""Post-connect setup starts fallback polling when the env-backed setting is enabled."""
|
||||
"""Post-connect setup also starts the same task when aggressive fallback is enabled."""
|
||||
from app.models import AppSettings
|
||||
from app.radio import RadioManager
|
||||
|
||||
@@ -809,7 +809,6 @@ class TestPostConnectSetupOrdering:
|
||||
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") as mock_start_message_polling,
|
||||
patch("app.services.radio_lifecycle.settings.enable_message_poll_fallback", True),
|
||||
):
|
||||
await rm.post_connect_setup()
|
||||
|
||||
|
||||
@@ -1191,6 +1191,34 @@ def _sleep_controller(*, cancel_after: int = 2):
|
||||
class TestMessagePollLoopRaces:
|
||||
"""Regression tests for disconnect/reconnect race paths in _message_poll_loop."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uses_hourly_audit_interval_when_fallback_disabled(self):
|
||||
rm, _mc = _make_connected_manager()
|
||||
mock_sleep, sleep_calls = _sleep_controller(cancel_after=1)
|
||||
|
||||
with (
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("app.radio_sync.settings.enable_message_poll_fallback", False),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
):
|
||||
await _message_poll_loop()
|
||||
|
||||
assert sleep_calls == [3600]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uses_fast_poll_interval_when_fallback_enabled(self):
|
||||
rm, _mc = _make_connected_manager()
|
||||
mock_sleep, sleep_calls = _sleep_controller(cancel_after=1)
|
||||
|
||||
with (
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("app.radio_sync.settings.enable_message_poll_fallback", True),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
):
|
||||
await _message_poll_loop()
|
||||
|
||||
assert sleep_calls == [10]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_disconnect_race_between_precheck_and_lock(self):
|
||||
"""RadioDisconnectedError between is_connected and radio_operation()
|
||||
@@ -1248,6 +1276,46 @@ class TestMessagePollLoopRaces:
|
||||
|
||||
mock_poll.assert_called_once_with(mock_mc)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hourly_audit_crows_loudly_when_it_finds_hidden_messages(self):
|
||||
rm, mock_mc = _make_connected_manager()
|
||||
mock_sleep, _ = _sleep_controller(cancel_after=2)
|
||||
|
||||
with (
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("app.radio_sync.settings.enable_message_poll_fallback", False),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
patch("app.radio_sync.poll_for_messages", new_callable=AsyncMock, return_value=2),
|
||||
patch("app.radio_sync.logger") as mock_logger,
|
||||
patch("app.radio_sync.broadcast_error") as mock_broadcast_error,
|
||||
):
|
||||
await _message_poll_loop()
|
||||
|
||||
mock_logger.error.assert_called_once()
|
||||
mock_broadcast_error.assert_called_once_with(
|
||||
"A periodic poll task has discovered radio inconsistencies.",
|
||||
"Please check the logs for recommendations (search "
|
||||
"'MESHCORE_ENABLE_MESSAGE_POLL_FALLBACK').",
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fast_poll_logs_missed_messages_without_error_toast(self):
|
||||
rm, mock_mc = _make_connected_manager()
|
||||
mock_sleep, _ = _sleep_controller(cancel_after=2)
|
||||
|
||||
with (
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("app.radio_sync.settings.enable_message_poll_fallback", True),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
patch("app.radio_sync.poll_for_messages", new_callable=AsyncMock, return_value=2),
|
||||
patch("app.radio_sync.logger") as mock_logger,
|
||||
patch("app.radio_sync.broadcast_error") as mock_broadcast_error,
|
||||
):
|
||||
await _message_poll_loop()
|
||||
|
||||
mock_logger.warning.assert_called_once()
|
||||
mock_broadcast_error.assert_not_called()
|
||||
|
||||
|
||||
class TestPeriodicAdvertLoopRaces:
|
||||
"""Regression tests for disconnect/reconnect race paths in _periodic_advert_loop."""
|
||||
@@ -1356,7 +1424,9 @@ class TestPeriodicSyncLoopRaces:
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
patch("app.radio_sync.cleanup_expired_acks") as mock_cleanup,
|
||||
patch("app.radio_sync.should_run_full_periodic_sync", new_callable=AsyncMock) as mock_check,
|
||||
patch(
|
||||
"app.radio_sync.should_run_full_periodic_sync", new_callable=AsyncMock
|
||||
) as mock_check,
|
||||
patch("app.radio_sync.sync_and_offload_all", new_callable=AsyncMock) as mock_sync,
|
||||
patch("app.radio_sync.sync_radio_time", new_callable=AsyncMock) as mock_time,
|
||||
):
|
||||
@@ -1380,7 +1450,9 @@ class TestPeriodicSyncLoopRaces:
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
patch("app.radio_sync.cleanup_expired_acks") as mock_cleanup,
|
||||
patch("app.radio_sync.should_run_full_periodic_sync", new_callable=AsyncMock) as mock_check,
|
||||
patch(
|
||||
"app.radio_sync.should_run_full_periodic_sync", new_callable=AsyncMock
|
||||
) as mock_check,
|
||||
patch("app.radio_sync.sync_and_offload_all", new_callable=AsyncMock) as mock_sync,
|
||||
patch("app.radio_sync.sync_radio_time", new_callable=AsyncMock) as mock_time,
|
||||
):
|
||||
@@ -1405,7 +1477,11 @@ class TestPeriodicSyncLoopRaces:
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
patch("app.radio_sync.cleanup_expired_acks") as mock_cleanup,
|
||||
patch("app.radio_sync.should_run_full_periodic_sync", new_callable=AsyncMock, return_value=True),
|
||||
patch(
|
||||
"app.radio_sync.should_run_full_periodic_sync",
|
||||
new_callable=AsyncMock,
|
||||
return_value=True,
|
||||
),
|
||||
patch("app.radio_sync.sync_and_offload_all", new_callable=AsyncMock) as mock_sync,
|
||||
patch("app.radio_sync.sync_radio_time", new_callable=AsyncMock) as mock_time,
|
||||
):
|
||||
@@ -1425,7 +1501,11 @@ class TestPeriodicSyncLoopRaces:
|
||||
patch("app.radio_sync.radio_manager", rm),
|
||||
patch("asyncio.sleep", side_effect=mock_sleep),
|
||||
patch("app.radio_sync.cleanup_expired_acks") as mock_cleanup,
|
||||
patch("app.radio_sync.should_run_full_periodic_sync", new_callable=AsyncMock, return_value=False),
|
||||
patch(
|
||||
"app.radio_sync.should_run_full_periodic_sync",
|
||||
new_callable=AsyncMock,
|
||||
return_value=False,
|
||||
),
|
||||
patch("app.radio_sync.sync_and_offload_all", new_callable=AsyncMock) as mock_sync,
|
||||
patch("app.radio_sync.sync_radio_time", new_callable=AsyncMock) as mock_time,
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user