Files
Remote-Terminal-for-MeshCore/tests/test_radio_runtime_service.py
2026-03-09 23:42:46 -07:00

117 lines
3.7 KiB
Python

from contextlib import asynccontextmanager
from unittest.mock import AsyncMock
import pytest
from fastapi import HTTPException
from app.services.radio_runtime import RadioRuntime
class _Manager:
def __init__(
self,
*,
meshcore=None,
is_connected=False,
is_reconnecting=False,
is_setup_in_progress=False,
is_setup_complete=False,
connection_info=None,
path_hash_mode=0,
path_hash_mode_supported=False,
):
self.meshcore = meshcore
self.is_connected = is_connected
self.is_reconnecting = is_reconnecting
self.is_setup_in_progress = is_setup_in_progress
self.is_setup_complete = is_setup_complete
self.connection_info = connection_info
self.path_hash_mode = path_hash_mode
self.path_hash_mode_supported = path_hash_mode_supported
self.calls: list[tuple[str, dict]] = []
@asynccontextmanager
async def radio_operation(self, name: str, **kwargs):
self.calls.append((name, kwargs))
yield self.meshcore
def test_uses_latest_manager_from_getter():
first = _Manager(meshcore="mc1", is_connected=True, connection_info="first")
second = _Manager(meshcore="mc2", is_connected=True, connection_info="second")
current = {"manager": first}
runtime = RadioRuntime(lambda: current["manager"])
assert runtime.connection_info == "first"
assert runtime.require_connected() == "mc1"
current["manager"] = second
assert runtime.connection_info == "second"
assert runtime.require_connected() == "mc2"
def test_require_connected_preserves_http_semantics():
runtime = RadioRuntime(
_Manager(meshcore=None, is_connected=True, is_setup_in_progress=True),
)
with pytest.raises(HTTPException, match="Radio is initializing") as exc:
runtime.require_connected()
assert exc.value.status_code == 503
runtime = RadioRuntime(_Manager(meshcore=None, is_connected=False, is_setup_in_progress=False))
with pytest.raises(HTTPException, match="Radio not connected") as exc:
runtime.require_connected()
assert exc.value.status_code == 503
def test_require_connected_returns_fresh_meshcore_after_connectivity_check():
old_meshcore = object()
new_meshcore = object()
class _SwappingManager:
def __init__(self):
self._meshcore = old_meshcore
self.is_setup_in_progress = False
@property
def is_connected(self):
self._meshcore = new_meshcore
return True
@property
def meshcore(self):
return self._meshcore
runtime = RadioRuntime(_SwappingManager())
assert runtime.require_connected() is new_meshcore
@pytest.mark.asyncio
async def test_radio_operation_delegates_to_current_manager():
manager = _Manager(meshcore="meshcore", is_connected=True)
runtime = RadioRuntime(manager)
async with runtime.radio_operation("sync_contacts", pause_polling=True) as mc:
assert mc == "meshcore"
assert manager.calls == [("sync_contacts", {"pause_polling": True})]
@pytest.mark.asyncio
async def test_lifecycle_passthrough_methods_delegate_to_current_manager():
manager = _Manager(meshcore="meshcore", is_connected=True)
manager.start_connection_monitor = AsyncMock()
manager.stop_connection_monitor = AsyncMock()
manager.disconnect = AsyncMock()
runtime = RadioRuntime(manager)
await runtime.start_connection_monitor()
await runtime.stop_connection_monitor()
await runtime.disconnect()
manager.start_connection_monitor.assert_awaited_once()
manager.stop_connection_monitor.assert_awaited_once()
manager.disconnect.assert_awaited_once()