Fix tests

This commit is contained in:
Teemu Rytilahti 2024-02-15 15:12:51 +01:00
parent 20ec2f5c90
commit 3e25c79f39
4 changed files with 106 additions and 5 deletions

View File

@ -1117,7 +1117,7 @@ async def feature(dev, name: str, value):
If both *name* and *value* are set, the described setting is changed. If both *name* and *value* are set, the described setting is changed.
""" """
if not name: if not name:
echo("[bold]== Feature ==[/bold]") echo("[bold]== Features ==[/bold]")
for name, feat in dev.features.items(): for name, feat in dev.features.items():
echo(f"{feat.name} ({name}): {feat.value}") echo(f"{feat.name} ({name}): {feat.value}")
return return

View File

@ -36,6 +36,11 @@ async def test_update_called_by_cli(dev, mocker):
"""Test that device update is called on main.""" """Test that device update is called on main."""
runner = CliRunner() runner = CliRunner()
update = mocker.patch.object(dev, "update") update = mocker.patch.object(dev, "update")
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
mocker.patch("kasa.discover.Discover.discover_single", return_value=dev) mocker.patch("kasa.discover.Discover.discover_single", return_value=dev)
res = await runner.invoke( res = await runner.invoke(
@ -48,6 +53,7 @@ async def test_update_called_by_cli(dev, mocker):
"--password", "--password",
"bar", "bar",
], ],
catch_exceptions=False,
) )
assert res.exit_code == 0 assert res.exit_code == 0
update.assert_called() update.assert_called()
@ -291,6 +297,10 @@ async def test_brightness(dev):
async def test_json_output(dev: Device, mocker): async def test_json_output(dev: Device, mocker):
"""Test that the json output produces correct output.""" """Test that the json output produces correct output."""
mocker.patch("kasa.Discover.discover", return_value={"127.0.0.1": dev}) mocker.patch("kasa.Discover.discover", return_value={"127.0.0.1": dev})
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
runner = CliRunner() runner = CliRunner()
res = await runner.invoke(cli, ["--json", "state"], obj=dev) res = await runner.invoke(cli, ["--json", "state"], obj=dev)
assert res.exit_code == 0 assert res.exit_code == 0
@ -342,6 +352,10 @@ async def test_without_device_type(dev, mocker):
"""Test connecting without the device type.""" """Test connecting without the device type."""
runner = CliRunner() runner = CliRunner()
mocker.patch("kasa.discover.Discover.discover_single", return_value=dev) mocker.patch("kasa.discover.Discover.discover_single", return_value=dev)
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
res = await runner.invoke( res = await runner.invoke(
cli, cli,
[ [
@ -398,6 +412,10 @@ async def test_duplicate_target_device():
async def test_discover(discovery_mock, mocker): async def test_discover(discovery_mock, mocker):
"""Test discovery output.""" """Test discovery output."""
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
runner = CliRunner() runner = CliRunner()
res = await runner.invoke( res = await runner.invoke(
cli, cli,
@ -417,6 +435,10 @@ async def test_discover(discovery_mock, mocker):
async def test_discover_host(discovery_mock, mocker): async def test_discover_host(discovery_mock, mocker):
"""Test discovery output.""" """Test discovery output."""
# These will mock the features to avoid accessing non-existing
mocker.patch("kasa.device.Device.features", return_value={})
mocker.patch("kasa.iot.iotdevice.IotDevice.features", return_value={})
runner = CliRunner() runner = CliRunner()
res = await runner.invoke( res = await runner.invoke(
cli, cli,

View File

@ -0,0 +1,79 @@
import pytest
from kasa import Feature, FeatureType
@pytest.fixture
def dummy_feature() -> Feature:
# create_autospec for device slows tests way too much, so we use a dummy here
class DummyDevice:
pass
feat = Feature(
device=DummyDevice(), # type: ignore[arg-type]
name="dummy_feature",
attribute_getter="dummygetter",
attribute_setter="dummysetter",
container=None,
icon="mdi:dummy",
type=FeatureType.BinarySensor,
)
return feat
def test_feature_api(dummy_feature: Feature):
"""Test all properties of a dummy feature."""
assert dummy_feature.device is not None
assert dummy_feature.name == "dummy_feature"
assert dummy_feature.attribute_getter == "dummygetter"
assert dummy_feature.attribute_setter == "dummysetter"
assert dummy_feature.container is None
assert dummy_feature.icon == "mdi:dummy"
assert dummy_feature.type == FeatureType.BinarySensor
def test_feature_value(dummy_feature: Feature):
"""Verify that property gets accessed on *value* access."""
dummy_feature.attribute_getter = "test_prop"
dummy_feature.device.test_prop = "dummy" # type: ignore[attr-defined]
assert dummy_feature.value == "dummy"
def test_feature_value_container(mocker, dummy_feature: Feature):
"""Test that container's attribute is accessed when expected."""
class DummyContainer:
@property
def test_prop(self):
return "dummy"
dummy_feature.container = DummyContainer()
dummy_feature.attribute_getter = "test_prop"
mock_dev_prop = mocker.patch.object(
dummy_feature, "test_prop", new_callable=mocker.PropertyMock, create=True
)
assert dummy_feature.value == "dummy"
mock_dev_prop.assert_not_called()
def test_feature_value_callable(dev, dummy_feature: Feature):
"""Verify that callables work as *attribute_getter*."""
dummy_feature.attribute_getter = lambda x: "dummy value"
assert dummy_feature.value == "dummy value"
async def test_feature_setter(dev, mocker, dummy_feature: Feature):
"""Verify that *set_value* calls the defined method."""
mock_set_dummy = mocker.patch.object(dummy_feature.device, "set_dummy", create=True)
dummy_feature.attribute_setter = "set_dummy"
await dummy_feature.set_value("dummy value")
mock_set_dummy.assert_called_with("dummy value")
async def test_feature_setter_read_only(dummy_feature):
"""Verify that read-only feature raises an exception when trying to change it."""
dummy_feature.attribute_setter = None
with pytest.raises(ValueError):
await dummy_feature.set_value("value for read only feature")

View File

@ -67,7 +67,7 @@ async def test_invalid_connection(dev):
async def test_initial_update_emeter(dev, mocker): async def test_initial_update_emeter(dev, mocker):
"""Test that the initial update performs second query if emeter is available.""" """Test that the initial update performs second query if emeter is available."""
dev._last_update = None dev._last_update = None
dev._features = set() dev._legacy_features = set()
spy = mocker.spy(dev.protocol, "query") spy = mocker.spy(dev.protocol, "query")
await dev.update() await dev.update()
# Devices with small buffers may require 3 queries # Devices with small buffers may require 3 queries
@ -79,7 +79,7 @@ async def test_initial_update_emeter(dev, mocker):
async def test_initial_update_no_emeter(dev, mocker): async def test_initial_update_no_emeter(dev, mocker):
"""Test that the initial update performs second query if emeter is available.""" """Test that the initial update performs second query if emeter is available."""
dev._last_update = None dev._last_update = None
dev._features = set() dev._legacy_features = set()
spy = mocker.spy(dev.protocol, "query") spy = mocker.spy(dev.protocol, "query")
await dev.update() await dev.update()
# 2 calls are necessary as some devices crash on unexpected modules # 2 calls are necessary as some devices crash on unexpected modules
@ -218,9 +218,9 @@ async def test_features(dev):
"""Make sure features is always accessible.""" """Make sure features is always accessible."""
sysinfo = dev._last_update["system"]["get_sysinfo"] sysinfo = dev._last_update["system"]["get_sysinfo"]
if "feature" in sysinfo: if "feature" in sysinfo:
assert dev.features == set(sysinfo["feature"].split(":")) assert dev._legacy_features == set(sysinfo["feature"].split(":"))
else: else:
assert dev.features == set() assert dev._legacy_features == set()
@device_iot @device_iot