71803418e5
Shelly device management app with mDNS/subnet discovery, inventory, configuration, and mass operations for Gen1/Gen2+ devices. Includes .gitignore excluding runtime data (device DB, user config), AI conversation history, build artifacts, and common Python/OS patterns.
148 lines
4.5 KiB
Python
148 lines
4.5 KiB
Python
"""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
|