mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-12-01 05:38:33 +00:00
Merge remote-tracking branch 'upstream/master' into feat/light_module_feats
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
import copy
|
||||
import logging
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from kasa import Module, SmartDevice
|
||||
from kasa import DeviceError, Module
|
||||
from kasa.exceptions import SmartErrorCode
|
||||
from kasa.interfaces.energy import Energy
|
||||
from kasa.smart import SmartDevice
|
||||
from kasa.smart.modules import Energy as SmartEnergyModule
|
||||
from tests.conftest import has_emeter_smart
|
||||
|
||||
@@ -19,3 +26,84 @@ async def test_supported(dev: SmartDevice):
|
||||
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is False
|
||||
else:
|
||||
assert energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT) is True
|
||||
|
||||
|
||||
@has_emeter_smart
|
||||
async def test_get_energy_usage_error(
|
||||
dev: SmartDevice, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test errors on get_energy_usage."""
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
energy_module = dev.modules.get(Module.Energy)
|
||||
if not energy_module:
|
||||
pytest.skip(f"Energy module not supported for {dev}.")
|
||||
|
||||
version = dev._components["energy_monitoring"]
|
||||
|
||||
expected_raise = does_not_raise() if version > 1 else pytest.raises(DeviceError)
|
||||
if version > 1:
|
||||
expected = "get_energy_usage"
|
||||
expected_current_consumption = 2.002
|
||||
else:
|
||||
expected = "current_power"
|
||||
expected_current_consumption = None
|
||||
|
||||
assert expected in energy_module.data
|
||||
assert energy_module.current_consumption is not None
|
||||
assert energy_module.consumption_today is not None
|
||||
assert energy_module.consumption_this_month is not None
|
||||
|
||||
last_update = copy.deepcopy(dev._last_update)
|
||||
resp = copy.deepcopy(last_update)
|
||||
|
||||
if ed := resp.get("get_emeter_data"):
|
||||
ed["power_mw"] = 2002
|
||||
if cp := resp.get("get_current_power"):
|
||||
cp["current_power"] = 2.002
|
||||
resp["get_energy_usage"] = SmartErrorCode.JSON_DECODE_FAIL_ERROR
|
||||
|
||||
# version 1 only has get_energy_usage so module should raise an error if
|
||||
# version 1 and get_energy_usage is in error
|
||||
with patch.object(dev.protocol, "query", return_value=resp):
|
||||
await dev.update()
|
||||
|
||||
with expected_raise:
|
||||
assert "get_energy_usage" not in energy_module.data
|
||||
|
||||
assert energy_module.current_consumption == expected_current_consumption
|
||||
assert energy_module.consumption_today is None
|
||||
assert energy_module.consumption_this_month is None
|
||||
|
||||
msg = (
|
||||
f"Removed key get_energy_usage from response for device {dev.host}"
|
||||
" as it returned error: JSON_DECODE_FAIL_ERROR"
|
||||
)
|
||||
if version > 1:
|
||||
assert msg in caplog.text
|
||||
|
||||
# Now test with no get_emeter_data
|
||||
# This may not be valid scenario but we have a fallback to get_current_power
|
||||
# just in case that should be tested.
|
||||
caplog.clear()
|
||||
resp = copy.deepcopy(last_update)
|
||||
|
||||
if cp := resp.get("get_current_power"):
|
||||
cp["current_power"] = 2.002
|
||||
resp["get_energy_usage"] = SmartErrorCode.JSON_DECODE_FAIL_ERROR
|
||||
|
||||
# Remove get_emeter_data from the response and from the device which will
|
||||
# remember it otherwise.
|
||||
resp.pop("get_emeter_data", None)
|
||||
dev._last_update.pop("get_emeter_data", None)
|
||||
|
||||
with patch.object(dev.protocol, "query", return_value=resp):
|
||||
await dev.update()
|
||||
|
||||
with expected_raise:
|
||||
assert "get_energy_usage" not in energy_module.data
|
||||
|
||||
assert energy_module.current_consumption == expected_current_consumption
|
||||
|
||||
# message should only be logged once
|
||||
assert msg not in caplog.text
|
||||
|
||||
16
tests/smart/modules/test_homekit.py
Normal file
16
tests/smart/modules/test_homekit.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
|
||||
from ...device_fixtures import parametrize
|
||||
|
||||
homekit = parametrize(
|
||||
"has homekit", component_filter="homekit", protocol_filter={"SMART"}
|
||||
)
|
||||
|
||||
|
||||
@homekit
|
||||
async def test_info(dev: SmartDevice):
|
||||
"""Test homekit info."""
|
||||
homekit = dev.modules.get(Module.HomeKit)
|
||||
assert homekit
|
||||
assert homekit.info
|
||||
20
tests/smart/modules/test_matter.py
Normal file
20
tests/smart/modules/test_matter.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from kasa import Module
|
||||
from kasa.smart import SmartDevice
|
||||
|
||||
from ...device_fixtures import parametrize
|
||||
|
||||
matter = parametrize(
|
||||
"has matter", component_filter="matter", protocol_filter={"SMART", "SMARTCAM"}
|
||||
)
|
||||
|
||||
|
||||
@matter
|
||||
async def test_info(dev: SmartDevice):
|
||||
"""Test matter info."""
|
||||
matter = dev.modules.get(Module.Matter)
|
||||
assert matter
|
||||
assert matter.info
|
||||
setup_code = dev.features.get("matter_setup_code")
|
||||
assert setup_code
|
||||
setup_payload = dev.features.get("matter_setup_payload")
|
||||
assert setup_payload
|
||||
@@ -62,11 +62,14 @@ async def test_device_type_no_update(discovery_mock, caplog: pytest.LogCaptureFi
|
||||
assert repr(dev) == f"<DeviceType.Unknown at {DISCOVERY_MOCK_IP} - update() needed>"
|
||||
|
||||
discovery_result = copy.deepcopy(discovery_mock.discovery_data["result"])
|
||||
|
||||
disco_model = discovery_result["device_model"]
|
||||
short_model, _, _ = disco_model.partition("(")
|
||||
dev.update_from_discover_info(discovery_result)
|
||||
assert dev.device_type is DeviceType.Unknown
|
||||
assert (
|
||||
repr(dev)
|
||||
== f"<DeviceType.Unknown at {DISCOVERY_MOCK_IP} - None (None) - update() needed>"
|
||||
== f"<DeviceType.Unknown at {DISCOVERY_MOCK_IP} - None ({short_model}) - update() needed>"
|
||||
)
|
||||
discovery_result["device_type"] = "SMART.FOOBAR"
|
||||
dev.update_from_discover_info(discovery_result)
|
||||
@@ -74,7 +77,7 @@ async def test_device_type_no_update(discovery_mock, caplog: pytest.LogCaptureFi
|
||||
assert dev.device_type is DeviceType.Plug
|
||||
assert (
|
||||
repr(dev)
|
||||
== f"<DeviceType.Plug at {DISCOVERY_MOCK_IP} - None (None) - update() needed>"
|
||||
== f"<DeviceType.Plug at {DISCOVERY_MOCK_IP} - None ({short_model}) - update() needed>"
|
||||
)
|
||||
assert "Unknown device type, falling back to plug" in caplog.text
|
||||
|
||||
@@ -352,7 +355,7 @@ async def test_update_module_query_errors(
|
||||
if mod.name == "Energy":
|
||||
emod = cast(Energy, mod)
|
||||
with pytest.raises(KasaException, match="Module update error"):
|
||||
assert emod.current_consumption is not None
|
||||
assert emod.status is not None
|
||||
else:
|
||||
assert mod.disabled is False
|
||||
assert mod._error_count == 0
|
||||
@@ -360,7 +363,7 @@ async def test_update_module_query_errors(
|
||||
# Test one of the raise_if_update_error doesn't raise
|
||||
if mod.name == "Energy":
|
||||
emod = cast(Energy, mod)
|
||||
assert emod.current_consumption is not None
|
||||
assert emod.status is not None
|
||||
|
||||
|
||||
async def test_get_modules():
|
||||
@@ -469,7 +472,9 @@ async def test_smartdevice_cloud_connection(dev: SmartDevice, mocker: MockerFixt
|
||||
async def test_smart_temp_range(dev: Device):
|
||||
light = dev.modules.get(Module.Light)
|
||||
assert light
|
||||
assert light.valid_temperature_range
|
||||
color_temp_feat = light.get_feature("color_temp")
|
||||
assert color_temp_feat
|
||||
assert color_temp_feat.range
|
||||
|
||||
|
||||
@device_smart
|
||||
@@ -528,3 +533,16 @@ async def test_initialize_modules_required_component(
|
||||
|
||||
assert "AvailableComponent" in dev.modules
|
||||
assert "NonExistingComponent" not in dev.modules
|
||||
|
||||
|
||||
async def test_smartmodule_query():
|
||||
"""Test that a module that doesn't set QUERY_GETTER_NAME has empty query."""
|
||||
|
||||
class DummyModule(SmartModule):
|
||||
pass
|
||||
|
||||
dummy_device = await get_device_for_fixture_protocol(
|
||||
"KS240(US)_1.0_1.0.5.json", "SMART"
|
||||
)
|
||||
mod = DummyModule(dummy_device, "dummy")
|
||||
assert mod.query() == {}
|
||||
|
||||
Reference in New Issue
Block a user