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.
185 lines
5.0 KiB
Python
185 lines
5.0 KiB
Python
"""Unit tests for models and helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
from shelly_manager.core.discovery import classify_generation, normalize_mac
|
|
from shelly_manager.core.models import (
|
|
DeviceFilter,
|
|
ShellyDevice,
|
|
infer_capabilities,
|
|
preserve_firmware_check_metadata,
|
|
)
|
|
|
|
|
|
def test_classify_generation_gen1() -> None:
|
|
assert classify_generation({"type": "SHSW-21", "mac": "A"}) == 1
|
|
|
|
|
|
def test_classify_generation_gen2() -> None:
|
|
assert classify_generation({"gen": 2, "model": "x"}) == 2
|
|
assert classify_generation({"gen": 3, "model": "x"}) == 3
|
|
|
|
|
|
def test_normalize_mac() -> None:
|
|
assert normalize_mac("aa:bb:cc:dd:ee:ff") == "AABBCCDDEEFF"
|
|
|
|
|
|
def test_device_filter_matches() -> None:
|
|
d = ShellyDevice(
|
|
id="AABB",
|
|
name="Kitchen",
|
|
mac="AA:BB",
|
|
ip="192.168.1.10",
|
|
generation=2,
|
|
model="Plus1",
|
|
firmware="1.0",
|
|
online=True,
|
|
last_seen=datetime.now(UTC),
|
|
capabilities=["switch"],
|
|
status={},
|
|
settings={},
|
|
tags=["t1"],
|
|
)
|
|
assert DeviceFilter(generations=[2]).matches(d)
|
|
assert not DeviceFilter(generations=[1]).matches(d)
|
|
assert DeviceFilter(tags=["t1"]).matches(d)
|
|
assert not DeviceFilter(tags=["missing"]).matches(d)
|
|
assert DeviceFilter(name_contains="kit").matches(d)
|
|
assert DeviceFilter(ip_prefix="192.168.1").matches(d)
|
|
assert DeviceFilter(only_device_ids=["AABB"]).matches(d)
|
|
assert not DeviceFilter(only_device_ids=["OTHER"]).matches(d)
|
|
|
|
|
|
def test_device_filter_settings_truthy() -> None:
|
|
d = ShellyDevice(
|
|
id="AABB",
|
|
name="Kitchen",
|
|
mac="AA:BB",
|
|
ip="192.168.1.10",
|
|
generation=2,
|
|
model="Plus1",
|
|
firmware="1.0",
|
|
online=True,
|
|
last_seen=datetime.now(UTC),
|
|
capabilities=[],
|
|
status={},
|
|
settings={"ble": {"enable": True}},
|
|
tags=[],
|
|
)
|
|
f = DeviceFilter(settings_path="ble.enable", settings_match="truthy")
|
|
assert f.matches(d)
|
|
d2 = d.model_copy(update={"settings": {"ble": {"enable": False}}})
|
|
assert not f.matches(d2)
|
|
|
|
|
|
def test_shelly_device_http_url() -> None:
|
|
d = ShellyDevice(
|
|
id="AABB",
|
|
name="Kitchen",
|
|
mac="AA:BB",
|
|
ip="192.168.1.10",
|
|
generation=2,
|
|
model="Plus1",
|
|
firmware="1.0",
|
|
online=True,
|
|
last_seen=datetime.now(UTC),
|
|
capabilities=[],
|
|
status={},
|
|
settings={},
|
|
tags=[],
|
|
http_port=8080,
|
|
)
|
|
assert d.http_url == "http://192.168.1.10:8080/"
|
|
|
|
|
|
def test_device_filter_capabilities_and_status() -> None:
|
|
d = ShellyDevice(
|
|
id="AABB",
|
|
name="Kitchen",
|
|
mac="AA:BB",
|
|
ip="192.168.1.10",
|
|
generation=2,
|
|
model="Plus1",
|
|
firmware="1.0",
|
|
online=True,
|
|
last_seen=datetime.now(UTC),
|
|
capabilities=["switch"],
|
|
status={"switch:0": {"output": True}},
|
|
settings={},
|
|
tags=[],
|
|
)
|
|
assert DeviceFilter(capabilities_any=["switch"]).matches(d)
|
|
assert not DeviceFilter(capabilities_any=["relay"]).matches(d)
|
|
assert DeviceFilter(status_path="switch:0|output", status_match="truthy").matches(d)
|
|
|
|
|
|
def test_device_filter_settings_falsy() -> None:
|
|
d = ShellyDevice(
|
|
id="AABB",
|
|
name="Kitchen",
|
|
mac="AA:BB",
|
|
ip="192.168.1.10",
|
|
generation=2,
|
|
model="Plus1",
|
|
firmware="1.0",
|
|
online=True,
|
|
last_seen=datetime.now(UTC),
|
|
capabilities=[],
|
|
status={},
|
|
settings={},
|
|
tags=[],
|
|
)
|
|
f = DeviceFilter(settings_path="ble.enable", settings_match="falsy")
|
|
assert f.matches(d)
|
|
|
|
|
|
def test_infer_capabilities_gen1_relay() -> None:
|
|
caps = infer_capabilities(1, {"relays": [{"ison": True}]}, {})
|
|
assert "relay" in caps
|
|
|
|
|
|
def test_infer_capabilities_gen2_switch() -> None:
|
|
caps = infer_capabilities(2, {"switch:0": {"output": True}}, {})
|
|
assert "switch" in caps
|
|
|
|
|
|
def test_preserve_firmware_check_metadata() -> None:
|
|
ts = datetime.now(UTC)
|
|
prev = ShellyDevice(
|
|
id="AABBCCDDEEFF",
|
|
name="A",
|
|
mac="AA:BB:CC:DD:EE:FF",
|
|
ip="192.168.1.1",
|
|
generation=2, # type: ignore[arg-type]
|
|
model="Plus1",
|
|
firmware="1",
|
|
online=True,
|
|
last_seen=ts,
|
|
capabilities=[],
|
|
status={},
|
|
settings={},
|
|
firmware_check_result={"stable": {"version": "1.7.4"}},
|
|
firmware_check_at=ts,
|
|
)
|
|
fresh = ShellyDevice(
|
|
id="AABBCCDDEEFF",
|
|
name="A",
|
|
mac="AA:BB:CC:DD:EE:FF",
|
|
ip="192.168.1.1",
|
|
generation=2, # type: ignore[arg-type]
|
|
model="Plus1",
|
|
firmware="1",
|
|
online=True,
|
|
last_seen=ts,
|
|
capabilities=[],
|
|
status={"sys": {}},
|
|
settings={},
|
|
firmware_check_result=None,
|
|
firmware_check_at=None,
|
|
)
|
|
preserve_firmware_check_metadata(fresh, prev)
|
|
assert fresh.firmware_check_result == prev.firmware_check_result
|
|
assert fresh.firmware_check_at == prev.firmware_check_at
|