Files
Remote-Terminal-for-MeshCore/tests/test_health_mqtt_status.py

172 lines
6.7 KiB
Python

"""Tests for health endpoint fanout status fields.
Verifies that build_health_data correctly reports fanout module statuses
via the fanout_manager.
"""
from unittest.mock import patch
import pytest
from app.routers.health import build_health_data
from app.version_info import AppBuildInfo
class TestHealthFanoutStatus:
"""Test fanout_statuses in build_health_data."""
@pytest.mark.asyncio
async def test_no_fanout_modules_returns_empty(self, test_db):
"""fanout_statuses should be empty dict when no modules are running."""
with patch("app.fanout.manager.fanout_manager") as mock_fm:
mock_fm.get_statuses.return_value = {}
data = await build_health_data(True, "TCP: 1.2.3.4:4000")
assert data["fanout_statuses"] == {}
@pytest.mark.asyncio
async def test_fanout_statuses_reflect_manager(self, test_db):
"""fanout_statuses should return whatever the manager reports."""
mock_statuses = {
"uuid-1": {"name": "Private MQTT", "type": "mqtt_private", "status": "connected"},
"uuid-2": {
"name": "Community MQTT",
"type": "mqtt_community",
"status": "disconnected",
},
}
with patch("app.fanout.manager.fanout_manager") as mock_fm:
mock_fm.get_statuses.return_value = mock_statuses
data = await build_health_data(True, "Serial: /dev/ttyUSB0")
assert data["fanout_statuses"] == mock_statuses
@pytest.mark.asyncio
async def test_health_status_ok_when_connected(self, test_db):
"""Health status is 'ok' when radio is connected."""
with (
patch(
"app.routers.health.RawPacketRepository.get_oldest_undecrypted", return_value=None
),
patch("app.routers.health.radio_manager") as mock_rm,
patch(
"app.routers.health.get_app_build_info",
return_value=AppBuildInfo(
version="3.4.1",
version_source="pyproject",
commit_hash="abcdef12",
commit_source="git",
),
),
):
mock_rm.is_setup_in_progress = False
mock_rm.is_setup_complete = True
data = await build_health_data(True, "Serial: /dev/ttyUSB0")
assert data["status"] == "ok"
assert data["radio_connected"] is True
assert data["radio_initializing"] is False
assert data["radio_state"] == "connected"
assert data["connection_info"] == "Serial: /dev/ttyUSB0"
assert data["app_info"] == {
"version": "3.4.1",
"commit_hash": "abcdef12",
}
@pytest.mark.asyncio
async def test_health_includes_cached_radio_device_info(self, test_db):
"""Health includes device metadata captured during post-connect setup."""
with (
patch(
"app.routers.health.RawPacketRepository.get_oldest_undecrypted", return_value=None
),
patch("app.routers.health.radio_manager") as mock_rm,
):
mock_rm.is_setup_in_progress = False
mock_rm.is_setup_complete = True
mock_rm.connection_desired = True
mock_rm.is_reconnecting = False
mock_rm.device_info_loaded = True
mock_rm.device_model = "T-Echo"
mock_rm.firmware_build = "2025-02-01"
mock_rm.firmware_version = "1.2.3"
mock_rm.max_contacts = 350
mock_rm.max_channels = 64
data = await build_health_data(True, "Serial: /dev/ttyUSB0")
assert data["radio_device_info"] == {
"model": "T-Echo",
"firmware_build": "2025-02-01",
"firmware_version": "1.2.3",
"max_contacts": 350,
"max_channels": 64,
}
@pytest.mark.asyncio
async def test_health_status_degraded_when_disconnected(self, test_db):
"""Health status is 'degraded' when radio is disconnected."""
with patch(
"app.routers.health.RawPacketRepository.get_oldest_undecrypted", return_value=None
):
data = await build_health_data(False, None)
assert data["status"] == "degraded"
assert data["radio_connected"] is False
assert data["radio_initializing"] is False
assert data["radio_state"] == "disconnected"
assert data["connection_info"] is None
@pytest.mark.asyncio
async def test_health_status_degraded_while_radio_initializing(self, test_db):
"""Health stays degraded while transport is up but post-connect setup is incomplete."""
with (
patch(
"app.routers.health.RawPacketRepository.get_oldest_undecrypted", return_value=None
),
patch("app.routers.health.radio_manager") as mock_rm,
):
mock_rm.is_setup_in_progress = True
mock_rm.is_setup_complete = False
data = await build_health_data(True, "Serial: /dev/ttyUSB0")
assert data["status"] == "degraded"
assert data["radio_connected"] is True
assert data["radio_initializing"] is True
assert data["radio_state"] == "initializing"
@pytest.mark.asyncio
async def test_health_state_paused_when_operator_disabled_connection(self, test_db):
"""Health reports paused when the operator has disabled reconnect attempts."""
with (
patch(
"app.routers.health.RawPacketRepository.get_oldest_undecrypted", return_value=None
),
patch("app.routers.health.radio_manager") as mock_rm,
):
mock_rm.is_setup_in_progress = False
mock_rm.is_setup_complete = False
mock_rm.connection_desired = False
mock_rm.is_reconnecting = False
data = await build_health_data(False, "BLE: AA:BB:CC:DD:EE:FF")
assert data["radio_state"] == "paused"
assert data["radio_connected"] is False
@pytest.mark.asyncio
async def test_health_state_connecting_while_reconnect_in_progress(self, test_db):
"""Health reports connecting while retries are active but transport is not up yet."""
with (
patch(
"app.routers.health.RawPacketRepository.get_oldest_undecrypted", return_value=None
),
patch("app.routers.health.radio_manager") as mock_rm,
):
mock_rm.is_setup_in_progress = False
mock_rm.is_setup_complete = False
mock_rm.connection_desired = True
mock_rm.is_reconnecting = True
data = await build_health_data(False, None)
assert data["radio_state"] == "connecting"
assert data["radio_connected"] is False