"""DeviceManager with discovery mocked.""" from __future__ import annotations from datetime import UTC, datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest from shelly_manager.core.device_manager import DeviceManager, shelly_from_probe from shelly_manager.core.models import DiscoveredEndpoint, ShellyDevice @pytest.mark.asyncio async def test_shelly_from_probe_gen2() -> None: d = shelly_from_probe( "10.0.0.2", 2, {"gen": 2, "mac": "AABBCCDDEEFF", "model": "Plus1", "fw_id": "x", "id": "shellyplus1-abc"}, ) assert d.generation == 2 assert d.ip == "10.0.0.2" @pytest.mark.asyncio async def test_discover_all_persists_after_mdns_probe_and_enrich( app_config_sqlite, ) -> None: dm = DeviceManager(app_config_sqlite) enriched = ShellyDevice( id="AABBCCDDEEFF", name="enriched", mac="AA:BB:CC:DD:EE:FF", ip="10.0.0.1", generation=2, model="Plus1", firmware="1", online=True, last_seen=datetime.now(UTC), capabilities=["switch"], status={"ok": True}, settings={"sys": {}}, ) ep = DiscoveredEndpoint(ip="10.0.0.1", port=80, source="mdns_http", txt={}) probe_payload = { "gen": 2, "mac": "AA:BB:CC:DD:EE:FF", "model": "Plus1", "fw_id": "1", "id": "shellyplus1-abc", } fake_rt = MagicMock() fake_rt.__aenter__ = AsyncMock(return_value=fake_rt) fake_rt.__aexit__ = AsyncMock(return_value=None) with ( patch("shelly_manager.core.device_manager.browse_mdns", new_callable=AsyncMock) as mdns, patch("shelly_manager.core.device_manager.probe_ip", new_callable=AsyncMock) as probe, patch( "shelly_manager.core.device_manager.fetch_device_snapshot", new_callable=AsyncMock, ) as enrich, patch("shelly_manager.core.device_manager.ShellyRuntime", return_value=fake_rt), ): mdns.return_value = [ep] probe.return_value = (2, probe_payload) enrich.return_value = enriched sess = MagicMock() await dm.discover_all(session=sess) stored = await dm.storage.get_device("AABBCCDDEEFF") assert stored is not None assert stored.name == "enriched" assert stored.status.get("ok") is True @pytest.mark.asyncio async def test_add_device_by_ip_invalid(app_config_sqlite) -> None: dm = DeviceManager(app_config_sqlite) dev, err = await dm.add_device_by_ip("not-an-ip") assert dev is None assert err is not None assert "Invalid" in err @pytest.mark.asyncio async def test_add_device_by_ip_no_shelly_json(app_config_sqlite) -> None: dm = DeviceManager(app_config_sqlite) with patch("shelly_manager.core.device_manager.fetch_shelly_json", new_callable=AsyncMock) as fj: fj.return_value = None sess = MagicMock() dev, err = await dm.add_device_by_ip("192.168.1.1", session=sess) assert dev is None assert err is not None @pytest.mark.asyncio async def test_add_device_by_ip_persists_after_enrich(app_config_sqlite) -> None: dm = DeviceManager(app_config_sqlite) enriched = ShellyDevice( id="AABBCCDDEEFF", name="enriched", mac="AA:BB:CC:DD:EE:FF", ip="10.0.0.5", generation=2, model="Plus1", firmware="1", online=True, last_seen=datetime.now(UTC), capabilities=["switch"], status={"ok": True}, settings={"sys": {}}, ) probe_payload = { "gen": 2, "mac": "AA:BB:CC:DD:EE:FF", "model": "Plus1", "fw_id": "1", "id": "shellyplus1-abc", } fake_rt = MagicMock() fake_rt.__aenter__ = AsyncMock(return_value=fake_rt) fake_rt.__aexit__ = AsyncMock(return_value=None) with ( patch("shelly_manager.core.device_manager.fetch_shelly_json", new_callable=AsyncMock) as fj, patch( "shelly_manager.core.device_manager.fetch_device_snapshot", new_callable=AsyncMock, ) as enrich, patch("shelly_manager.core.device_manager.ShellyRuntime", return_value=fake_rt), ): fj.return_value = probe_payload enrich.return_value = enriched sess = MagicMock() dev, err = await dm.add_device_by_ip("10.0.0.5", session=sess) assert err is None assert dev is not None assert dev.name == "enriched" stored = await dm.storage.get_device("AABBCCDDEEFF") assert stored is not None assert stored.status.get("ok") is True