mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
"""Tests for settings router endpoints and validation behavior."""
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from app.models import AppSettings
|
|
from app.repository import AppSettingsRepository
|
|
from app.routers.settings import (
|
|
AppSettingsUpdate,
|
|
FavoriteRequest,
|
|
MigratePreferencesRequest,
|
|
migrate_preferences,
|
|
toggle_favorite,
|
|
update_settings,
|
|
)
|
|
|
|
|
|
class TestUpdateSettings:
|
|
@pytest.mark.asyncio
|
|
async def test_forwards_only_provided_fields(self, test_db):
|
|
result = await update_settings(
|
|
AppSettingsUpdate(
|
|
max_radio_contacts=321,
|
|
advert_interval=3600,
|
|
)
|
|
)
|
|
|
|
assert result.max_radio_contacts == 321
|
|
assert result.advert_interval == 3600
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_advert_interval_below_minimum_is_clamped_to_one_hour(self, test_db):
|
|
result = await update_settings(AppSettingsUpdate(advert_interval=600))
|
|
assert result.advert_interval == 3600
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_advert_interval_zero_stays_disabled(self, test_db):
|
|
result = await update_settings(AppSettingsUpdate(advert_interval=0))
|
|
assert result.advert_interval == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_advert_interval_above_minimum_is_preserved(self, test_db):
|
|
result = await update_settings(AppSettingsUpdate(advert_interval=86400))
|
|
assert result.advert_interval == 86400
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_empty_patch_returns_current_settings(self, test_db):
|
|
result = await update_settings(AppSettingsUpdate())
|
|
|
|
# Should return default settings without error
|
|
assert isinstance(result, AppSettings)
|
|
assert result.max_radio_contacts == 200 # default
|
|
|
|
@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"))
|
|
assert result.flood_scope == "#MyRegion"
|
|
|
|
fresh = await AppSettingsRepository.get()
|
|
assert fresh.flood_scope == "#MyRegion"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_flood_scope_default_empty(self, test_db):
|
|
"""Fresh DB should have flood_scope as empty string."""
|
|
settings = await AppSettingsRepository.get()
|
|
assert settings.flood_scope == ""
|
|
|
|
@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 "))
|
|
assert result.flood_scope == "#MyRegion"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_flood_scope_applies_to_radio(self, test_db):
|
|
"""When radio is connected, setting flood_scope calls set_flood_scope on radio."""
|
|
mock_mc = AsyncMock()
|
|
mock_mc.commands.set_flood_scope = AsyncMock()
|
|
|
|
mock_rm = AsyncMock()
|
|
mock_rm.is_connected = True
|
|
mock_rm.meshcore = mock_mc
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
@asynccontextmanager
|
|
async def mock_radio_op(name):
|
|
yield mock_mc
|
|
|
|
mock_rm.radio_operation = mock_radio_op
|
|
|
|
with patch("app.radio.radio_manager", mock_rm):
|
|
await update_settings(AppSettingsUpdate(flood_scope="#TestRegion"))
|
|
|
|
mock_mc.commands.set_flood_scope.assert_awaited_once_with("#TestRegion")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_flood_scope_empty_resets_radio(self, test_db):
|
|
"""Setting flood_scope to empty calls set_flood_scope("") on radio."""
|
|
# First set a non-empty scope
|
|
await update_settings(AppSettingsUpdate(flood_scope="#TestRegion"))
|
|
|
|
mock_mc = AsyncMock()
|
|
mock_mc.commands.set_flood_scope = AsyncMock()
|
|
|
|
mock_rm = AsyncMock()
|
|
mock_rm.is_connected = True
|
|
mock_rm.meshcore = mock_mc
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
@asynccontextmanager
|
|
async def mock_radio_op(name):
|
|
yield mock_mc
|
|
|
|
mock_rm.radio_operation = mock_radio_op
|
|
|
|
with patch("app.radio.radio_manager", mock_rm):
|
|
await update_settings(AppSettingsUpdate(flood_scope=""))
|
|
|
|
mock_mc.commands.set_flood_scope.assert_awaited_once_with("")
|
|
|
|
|
|
class TestToggleFavorite:
|
|
@pytest.mark.asyncio
|
|
async def test_adds_when_not_favorited(self, test_db):
|
|
request = FavoriteRequest(type="contact", id="aa" * 32)
|
|
result = await toggle_favorite(request)
|
|
|
|
assert len(result.favorites) == 1
|
|
assert result.favorites[0].type == "contact"
|
|
assert result.favorites[0].id == "aa" * 32
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_removes_when_already_favorited(self, test_db):
|
|
# Pre-add a favorite
|
|
await AppSettingsRepository.add_favorite("channel", "ABCD")
|
|
|
|
request = FavoriteRequest(type="channel", id="ABCD")
|
|
result = await toggle_favorite(request)
|
|
|
|
assert result.favorites == []
|
|
|
|
|
|
class TestMigratePreferences:
|
|
@pytest.mark.asyncio
|
|
async def test_maps_frontend_payload_and_returns_migrated_true(self, test_db):
|
|
request = MigratePreferencesRequest(
|
|
favorites=[FavoriteRequest(type="contact", id="aa" * 32)],
|
|
sort_order="alpha",
|
|
last_message_times={"contact-aaaaaaaaaaaa": 123},
|
|
)
|
|
|
|
response = await migrate_preferences(request)
|
|
|
|
assert response.migrated is True
|
|
assert response.settings.preferences_migrated is True
|
|
assert response.settings.sidebar_sort_order == "alpha"
|
|
assert len(response.settings.favorites) == 1
|
|
assert response.settings.favorites[0].type == "contact"
|
|
assert response.settings.favorites[0].id == "aa" * 32
|
|
assert response.settings.last_message_times == {"contact-aaaaaaaaaaaa": 123}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_returns_migrated_false_when_already_done(self, test_db):
|
|
# First migration
|
|
first_request = MigratePreferencesRequest(
|
|
favorites=[FavoriteRequest(type="contact", id="bb" * 32)],
|
|
sort_order="recent",
|
|
last_message_times={},
|
|
)
|
|
await migrate_preferences(first_request)
|
|
|
|
# Second attempt should be no-op
|
|
second_request = MigratePreferencesRequest(
|
|
favorites=[],
|
|
sort_order="recent",
|
|
last_message_times={},
|
|
)
|
|
response = await migrate_preferences(second_request)
|
|
|
|
assert response.migrated is False
|
|
assert response.settings.preferences_migrated is True
|