Files
jonas 71803418e5 Initial commit: Shelly Manager with Textual CLI, Streamlit UI, and comprehensive .gitignore
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.
2026-03-23 21:51:59 +01:00

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