mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-12-07 00:28:22 +00:00
Improve feature setter robustness (#870)
This adds a test to check that all feature.set_value() calls will cause a query, i.e., that there are no self.call()s that are not awaited, and fixes existing code in this context. This also fixes an issue where it was not possible to print out the feature if the value threw an exception.
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import pytest
|
||||
from pytest_mock import MockFixture
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from kasa import Feature
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from kasa import Device, Feature, KasaException
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DummyDevice:
|
||||
@@ -111,7 +116,7 @@ async def test_feature_action(mocker):
|
||||
mock_call_action.assert_called()
|
||||
|
||||
|
||||
async def test_feature_choice_list(dummy_feature, caplog, mocker: MockFixture):
|
||||
async def test_feature_choice_list(dummy_feature, caplog, mocker: MockerFixture):
|
||||
"""Test the choice feature type."""
|
||||
dummy_feature.type = Feature.Type.Choice
|
||||
dummy_feature.choices = ["first", "second"]
|
||||
@@ -138,3 +143,60 @@ async def test_precision_hint(dummy_feature, precision_hint):
|
||||
dummy_feature.attribute_getter = lambda x: dummy_value
|
||||
assert dummy_feature.value == dummy_value
|
||||
assert f"{round(dummy_value, precision_hint)} dummyunit" in repr(dummy_feature)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 11),
|
||||
reason="exceptiongroup requires python3.11+",
|
||||
)
|
||||
async def test_feature_setters(dev: Device, mocker: MockerFixture):
|
||||
"""Test that all feature setters query something."""
|
||||
|
||||
async def _test_feature(feat, query_mock):
|
||||
if feat.attribute_setter is None:
|
||||
return
|
||||
|
||||
expecting_call = True
|
||||
|
||||
if feat.type == Feature.Type.Number:
|
||||
await feat.set_value(feat.minimum_value)
|
||||
elif feat.type == Feature.Type.Switch:
|
||||
await feat.set_value(True)
|
||||
elif feat.type == Feature.Type.Action:
|
||||
await feat.set_value("dummyvalue")
|
||||
elif feat.type == Feature.Type.Choice:
|
||||
await feat.set_value(feat.choices[0])
|
||||
elif feat.type == Feature.Type.Unknown:
|
||||
_LOGGER.warning("Feature '%s' has no type, cannot test the setter", feat)
|
||||
expecting_call = False
|
||||
else:
|
||||
raise NotImplementedError(f"set_value not implemented for {feat.type}")
|
||||
|
||||
if expecting_call:
|
||||
query_mock.assert_called()
|
||||
|
||||
async def _test_features(dev):
|
||||
exceptions = []
|
||||
query = mocker.patch.object(dev.protocol, "query")
|
||||
for feat in dev.features.values():
|
||||
query.reset_mock()
|
||||
try:
|
||||
await _test_feature(feat, query)
|
||||
# we allow our own exceptions to avoid mocking valid responses
|
||||
except KasaException:
|
||||
pass
|
||||
except Exception as ex:
|
||||
ex.add_note(f"Exception when trying to set {feat} on {dev}")
|
||||
exceptions.append(ex)
|
||||
|
||||
return exceptions
|
||||
|
||||
exceptions = await _test_features(dev)
|
||||
|
||||
for child in dev.children:
|
||||
exceptions.extend(await _test_features(child))
|
||||
|
||||
if exceptions:
|
||||
raise ExceptionGroup(
|
||||
"Got exceptions while testing attribute_setters", exceptions
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user