From b4f3d1f14cc63c222ad4987606f4805938e89d3f Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Wed, 1 Apr 2026 11:31:20 -0700 Subject: [PATCH] Add additional info to debug endpoint. Closes #142. --- app/routers/debug.py | 26 +++++++++++++++++++++++++- tests/test_api.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/app/routers/debug.py b/app/routers/debug.py index 53ae228..5771531 100644 --- a/app/routers/debug.py +++ b/app/routers/debug.py @@ -12,8 +12,9 @@ from meshcore import EventType from pydantic import BaseModel, Field from app.config import get_recent_log_lines, settings +from app.models import AppSettings from app.radio_sync import get_contacts_selected_for_radio_sync, get_radio_channel_limit -from app.repository import MessageRepository, StatisticsRepository +from app.repository import AppSettingsRepository, MessageRepository, StatisticsRepository from app.routers.health import HealthResponse, build_health_data from app.services.radio_runtime import radio_runtime from app.version_info import get_app_build_info, git_output @@ -103,11 +104,21 @@ class DebugDatabaseInfo(BaseModel): total_outgoing: int +class DebugAppSettings(BaseModel): + max_radio_contacts: int + auto_decrypt_dm_on_advert: bool + advert_interval: int + flood_scope: str + blocked_keys_count: int + blocked_names_count: int + + class DebugSnapshotResponse(BaseModel): captured_at: str system: DebugSystemInfo application: DebugApplicationInfo health: HealthResponse + settings: DebugAppSettings runtime: DebugRuntimeInfo database: DebugDatabaseInfo radio_probe: DebugRadioProbe @@ -186,6 +197,17 @@ def _coerce_live_max_channels(device_info: dict[str, Any] | None) -> int | None: return None +def _build_debug_app_settings(app_settings: AppSettings) -> DebugAppSettings: + return DebugAppSettings( + max_radio_contacts=app_settings.max_radio_contacts, + auto_decrypt_dm_on_advert=app_settings.auto_decrypt_dm_on_advert, + advert_interval=app_settings.advert_interval, + flood_scope=app_settings.flood_scope, + blocked_keys_count=len(app_settings.blocked_keys), + blocked_names_count=len(app_settings.blocked_names), + ) + + async def _build_contact_audit( observed_contacts_payload: dict[str, dict[str, Any]], ) -> DebugContactAudit: @@ -293,6 +315,7 @@ async def _probe_radio() -> DebugRadioProbe: async def debug_support_snapshot() -> DebugSnapshotResponse: """Return a support/debug snapshot with recent logs and live radio state.""" health_data = await build_health_data(radio_runtime.is_connected, radio_runtime.connection_info) + app_settings = await AppSettingsRepository.get() message_totals = await StatisticsRepository.get_database_message_totals() radio_probe = await _probe_radio() channels_with_incoming_messages = ( @@ -303,6 +326,7 @@ async def debug_support_snapshot() -> DebugSnapshotResponse: system=_build_system_info(), application=_build_application_info(), health=HealthResponse(**health_data), + settings=_build_debug_app_settings(app_settings), runtime=DebugRuntimeInfo( connection_info=radio_runtime.connection_info, connection_desired=radio_runtime.connection_desired, diff --git a/tests/test_api.py b/tests/test_api.py index 4cf90f8..b5f3c3a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -213,6 +213,47 @@ class TestDebugEndpoint: assert response.status_code == 200 + @pytest.mark.asyncio + async def test_support_snapshot_includes_persisted_app_settings(self, test_db, client): + """Debug snapshot should expose the stored app settings row.""" + pub_key = "ab" * 32 + await _insert_contact(pub_key, "Alice") + + response = await client.patch( + "/api/settings", + json={ + "max_radio_contacts": 321, + "auto_decrypt_dm_on_advert": True, + "sidebar_sort_order": "alpha", + "advert_interval": 7200, + "flood_scope": "US-CA", + "blocked_keys": [pub_key], + "blocked_names": ["Mallory"], + }, + ) + assert response.status_code == 200 + + response = await client.post( + "/api/settings/favorites/toggle", + json={"type": "contact", "id": pub_key}, + ) + assert response.status_code == 200 + + response = await client.get("/api/debug") + + assert response.status_code == 200 + payload = response.json() + assert payload["settings"]["max_radio_contacts"] == 321 + assert payload["settings"]["auto_decrypt_dm_on_advert"] is True + assert payload["settings"]["advert_interval"] == 7200 + assert payload["settings"]["flood_scope"] == "#US-CA" + assert payload["settings"]["blocked_keys_count"] == 1 + assert payload["settings"]["blocked_names_count"] == 1 + assert "favorites" not in payload["settings"] + assert "blocked_keys" not in payload["settings"] + assert "blocked_names" not in payload["settings"] + assert "sidebar_sort_order" not in payload["settings"] + class TestRadioDisconnectedHandler: """Test that RadioDisconnectedError maps to 503."""