"""Tests for run_command function.""" from unittest.mock import MagicMock import pytest from meshmon.meshcore_client import run_command from .conftest import make_mock_event class TestRunCommandSuccess: """Tests for successful command execution.""" @pytest.mark.asyncio async def test_returns_success_tuple(self, mock_meshcore_client, monkeypatch): """Returns (True, event_type, payload, None) on success.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) event = make_mock_event("SELF_INFO", {"bat": 3850}) async def cmd(): return event success, event_type, payload, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is True assert event_type == "SELF_INFO" assert payload == {"bat": 3850} assert error is None @pytest.mark.asyncio async def test_extracts_payload_dict(self, mock_meshcore_client, monkeypatch): """Extracts payload when it's a dict.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) payload_data = {"voltage": 3.85, "uptime": 86400} event = make_mock_event("SELF_INFO", payload_data) async def cmd(): return event success, _, payload, _ = await run_command( mock_meshcore_client, cmd(), "test" ) assert payload == payload_data @pytest.mark.asyncio async def test_converts_object_payload(self, mock_meshcore_client, monkeypatch): """Converts object payload to dict.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) # Create object-like payload using a simple class with instance attributes # vars() only returns instance attributes, not class attributes class ObjPayload: def __init__(self): self.voltage = 3.85 obj_payload = ObjPayload() event = make_mock_event("SELF_INFO", payload=obj_payload) async def cmd(): return event success, _, payload, _ = await run_command( mock_meshcore_client, cmd(), "test" ) assert payload == {"voltage": 3.85} @pytest.mark.asyncio async def test_converts_namedtuple_payload(self, mock_meshcore_client, monkeypatch): """Converts namedtuple payload to dict.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) from collections import namedtuple Payload = namedtuple("Payload", ["voltage", "uptime"]) nt_payload = Payload(voltage=3.85, uptime=86400) event = make_mock_event("SELF_INFO") event.payload = nt_payload async def cmd(): return event success, _, payload, _ = await run_command( mock_meshcore_client, cmd(), "test" ) assert payload == {"voltage": 3.85, "uptime": 86400} class TestRunCommandFailure: """Tests for command failure scenarios.""" @pytest.mark.asyncio async def test_returns_failure_when_unavailable(self, mock_meshcore_client, monkeypatch): """Returns failure when meshcore not available.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", False) async def cmd(): return None # Create the coroutine cmd_coro = cmd() success, event_type, payload, error = await run_command( mock_meshcore_client, cmd_coro, "test" ) # Close the coroutine to prevent "never awaited" warning # since run_command returns early when MESHCORE_AVAILABLE=False cmd_coro.close() assert success is False assert event_type is None assert payload is None assert error == "meshcore not available" @pytest.mark.asyncio async def test_returns_failure_on_none_event(self, mock_meshcore_client, monkeypatch): """Returns failure when no event received.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) async def cmd(): return None success, _, _, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is False assert error == "No response received" @pytest.mark.asyncio async def test_returns_failure_on_error_event(self, mock_meshcore_client, monkeypatch): """Returns failure on ERROR event type.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) # Set up EventType mock mock_event_type = MagicMock() mock_event_type.ERROR = "ERROR" monkeypatch.setattr("meshmon.meshcore_client.EventType", mock_event_type) event = MagicMock() event.type = mock_event_type.ERROR event.payload = "Command failed" async def cmd(): return event success, event_type, payload, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is False assert event_type == "ERROR" assert payload is None assert error == "Command failed" @pytest.mark.asyncio async def test_returns_failure_on_timeout(self, mock_meshcore_client, monkeypatch): """Returns failure on timeout.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) async def cmd(): raise TimeoutError() success, _, _, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is False assert error == "Timeout" @pytest.mark.asyncio async def test_returns_failure_on_exception(self, mock_meshcore_client, monkeypatch): """Returns failure on general exception.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) async def cmd(): raise RuntimeError("Connection lost") success, _, _, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is False assert error == "Connection lost" class TestRunCommandEventTypeParsing: """Tests for event type name extraction.""" @pytest.mark.asyncio async def test_extracts_type_name_attribute(self, mock_meshcore_client, monkeypatch): """Extracts event type from .type.name attribute.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) event = make_mock_event("CUSTOM_EVENT", {}) async def cmd(): return event success, event_type, payload, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is True assert event_type == "CUSTOM_EVENT" assert payload == {} assert error is None @pytest.mark.asyncio async def test_falls_back_to_str_type(self, mock_meshcore_client, monkeypatch): """Falls back to str(type) when no .name.""" monkeypatch.setattr("meshmon.meshcore_client.MESHCORE_AVAILABLE", True) event = MagicMock() event.type = "STRING_TYPE" event.payload = {} async def cmd(): return event success, event_type, payload, error = await run_command( mock_meshcore_client, cmd(), "test" ) assert success is True assert event_type == "STRING_TYPE" assert payload == {} assert error is None