mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Add flake8-pytest-style (PT) for ruff (#1105)
This will catch common issues with pytest code. * Use `match` when using `pytest.raises()` for base exception types like `TypeError` or `ValueError` * Use tuples for `parametrize()` * Enforces `pytest.raises()` to contain simple statements, using `noqa` to skip this on some cases for now. * Fixes incorrect exception type (valueerror instead of typeerror) for iotdimmer. * Adds check valid types for `iotbulb.set_hsv` and `color` smart module. * Consolidate exception messages for common interface modules.
This commit is contained in:
parent
3e43781bb2
commit
6a86ffbbba
@ -388,10 +388,14 @@ class IotBulb(IotDevice):
|
|||||||
if not self._is_color:
|
if not self._is_color:
|
||||||
raise KasaException("Bulb does not support color.")
|
raise KasaException("Bulb does not support color.")
|
||||||
|
|
||||||
if not isinstance(hue, int) or not (0 <= hue <= 360):
|
if not isinstance(hue, int):
|
||||||
|
raise TypeError("Hue must be an integer.")
|
||||||
|
if not (0 <= hue <= 360):
|
||||||
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
|
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
|
||||||
|
|
||||||
if not isinstance(saturation, int) or not (0 <= saturation <= 100):
|
if not isinstance(saturation, int):
|
||||||
|
raise TypeError("Saturation must be an integer.")
|
||||||
|
if not (0 <= saturation <= 100):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid saturation value: {saturation} (valid range: 0-100%)"
|
f"Invalid saturation value: {saturation} (valid range: 0-100%)"
|
||||||
)
|
)
|
||||||
@ -445,7 +449,9 @@ class IotBulb(IotDevice):
|
|||||||
return await self._set_light_state(light_state, transition=transition)
|
return await self._set_light_state(light_state, transition=transition)
|
||||||
|
|
||||||
def _raise_for_invalid_brightness(self, value):
|
def _raise_for_invalid_brightness(self, value):
|
||||||
if not isinstance(value, int) or not (0 <= value <= 100):
|
if not isinstance(value, int):
|
||||||
|
raise TypeError("Brightness must be an integer")
|
||||||
|
if not (0 <= value <= 100):
|
||||||
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")
|
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")
|
||||||
|
|
||||||
@property # type: ignore
|
@property # type: ignore
|
||||||
|
@ -118,7 +118,9 @@ class IotDimmer(IotPlug):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not 0 <= brightness <= 100:
|
if not 0 <= brightness <= 100:
|
||||||
raise ValueError("Brightness value %s is not valid." % brightness)
|
raise ValueError(
|
||||||
|
f"Invalid brightness value: {brightness} (valid range: 0-100%)"
|
||||||
|
)
|
||||||
|
|
||||||
# Dimmers do not support a brightness of 0, but bulbs do.
|
# Dimmers do not support a brightness of 0, but bulbs do.
|
||||||
# Coerce 0 to 1 to maintain the same interface between dimmers and bulbs.
|
# Coerce 0 to 1 to maintain the same interface between dimmers and bulbs.
|
||||||
@ -161,20 +163,18 @@ class IotDimmer(IotPlug):
|
|||||||
A brightness value of 0 will turn off the dimmer.
|
A brightness value of 0 will turn off the dimmer.
|
||||||
"""
|
"""
|
||||||
if not isinstance(brightness, int):
|
if not isinstance(brightness, int):
|
||||||
raise ValueError(
|
raise TypeError(f"Brightness must be an integer, not {type(brightness)}.")
|
||||||
"Brightness must be integer, " "not of %s.", type(brightness)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not 0 <= brightness <= 100:
|
if not 0 <= brightness <= 100:
|
||||||
raise ValueError("Brightness value %s is not valid." % brightness)
|
raise ValueError(
|
||||||
|
f"Invalid brightness value: {brightness} (valid range: 0-100%)"
|
||||||
|
)
|
||||||
|
|
||||||
# If zero set to 1 millisecond
|
# If zero set to 1 millisecond
|
||||||
if transition == 0:
|
if transition == 0:
|
||||||
transition = 1
|
transition = 1
|
||||||
if not isinstance(transition, int):
|
if not isinstance(transition, int):
|
||||||
raise ValueError(
|
raise TypeError(f"Transition must be integer, not of {type(transition)}.")
|
||||||
"Transition must be integer, " "not of %s.", type(transition)
|
|
||||||
)
|
|
||||||
if transition <= 0:
|
if transition <= 0:
|
||||||
raise ValueError("Transition value %s is not valid." % transition)
|
raise ValueError("Transition value %s is not valid." % transition)
|
||||||
|
|
||||||
|
@ -51,10 +51,12 @@ class Color(SmartModule):
|
|||||||
|
|
||||||
return HSV(hue=h, saturation=s, value=v)
|
return HSV(hue=h, saturation=s, value=v)
|
||||||
|
|
||||||
def _raise_for_invalid_brightness(self, value: int):
|
def _raise_for_invalid_brightness(self, value):
|
||||||
"""Raise error on invalid brightness value."""
|
"""Raise error on invalid brightness value."""
|
||||||
if not isinstance(value, int) or not (1 <= value <= 100):
|
if not isinstance(value, int):
|
||||||
raise ValueError(f"Invalid brightness value: {value} (valid range: 1-100%)")
|
raise TypeError("Brightness must be an integer")
|
||||||
|
if not (0 <= value <= 100):
|
||||||
|
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")
|
||||||
|
|
||||||
async def set_hsv(
|
async def set_hsv(
|
||||||
self,
|
self,
|
||||||
@ -73,10 +75,14 @@ class Color(SmartModule):
|
|||||||
:param int value: value in percentage [0, 100]
|
:param int value: value in percentage [0, 100]
|
||||||
:param int transition: transition in milliseconds.
|
:param int transition: transition in milliseconds.
|
||||||
"""
|
"""
|
||||||
if not isinstance(hue, int) or not (0 <= hue <= 360):
|
if not isinstance(hue, int):
|
||||||
|
raise TypeError("Hue must be an integer")
|
||||||
|
if not (0 <= hue <= 360):
|
||||||
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
|
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
|
||||||
|
|
||||||
if not isinstance(saturation, int) or not (0 <= saturation <= 100):
|
if not isinstance(saturation, int):
|
||||||
|
raise TypeError("Saturation must be an integer")
|
||||||
|
if not (0 <= saturation <= 100):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid saturation value: {saturation} (valid range: 0-100%)"
|
f"Invalid saturation value: {saturation} (valid range: 0-100%)"
|
||||||
)
|
)
|
||||||
|
@ -90,7 +90,7 @@ class LightEffect(SmartModule, SmartLightEffect):
|
|||||||
"""
|
"""
|
||||||
if effect != self.LIGHT_EFFECTS_OFF and effect not in self._scenes_names_to_id:
|
if effect != self.LIGHT_EFFECTS_OFF and effect not in self._scenes_names_to_id:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Cannot set light effect to {effect}, possible values "
|
f"The effect {effect} is not a built in effect. Possible values "
|
||||||
f"are: {self.LIGHT_EFFECTS_OFF} "
|
f"are: {self.LIGHT_EFFECTS_OFF} "
|
||||||
f"{' '.join(self._scenes_names_to_id.keys())}"
|
f"{' '.join(self._scenes_names_to_id.keys())}"
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ async def handle_turn_on(dev, turn_on):
|
|||||||
await dev.turn_off()
|
await dev.turn_off()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def dummy_protocol():
|
def dummy_protocol():
|
||||||
"""Return a smart protocol instance with a mocking-ready dummy transport."""
|
"""Return a smart protocol instance with a mocking-ready dummy transport."""
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ new_discovery = parametrize_discovery(
|
|||||||
async def discovery_mock(request, mocker):
|
async def discovery_mock(request, mocker):
|
||||||
"""Mock discovery and patch protocol queries to use Fake protocols."""
|
"""Mock discovery and patch protocol queries to use Fake protocols."""
|
||||||
fixture_info: FixtureInfo = request.param
|
fixture_info: FixtureInfo = request.param
|
||||||
yield patch_discovery({DISCOVERY_MOCK_IP: fixture_info}, mocker)
|
return patch_discovery({DISCOVERY_MOCK_IP: fixture_info}, mocker)
|
||||||
|
|
||||||
|
|
||||||
def create_discovery_mock(ip: str, fixture_data: dict):
|
def create_discovery_mock(ip: str, fixture_data: dict):
|
||||||
@ -253,4 +253,4 @@ def unsupported_device_info(request, mocker):
|
|||||||
|
|
||||||
mocker.patch("kasa.discover._DiscoverProtocol.do_discover", mock_discover)
|
mocker.patch("kasa.discover._DiscoverProtocol.do_discover", mock_discover)
|
||||||
|
|
||||||
yield discovery_data
|
return discovery_data
|
||||||
|
@ -18,17 +18,18 @@ async def test_brightness_component(dev: SmartDevice):
|
|||||||
# Test getting the value
|
# Test getting the value
|
||||||
feature = brightness._device.features["brightness"]
|
feature = brightness._device.features["brightness"]
|
||||||
assert isinstance(feature.value, int)
|
assert isinstance(feature.value, int)
|
||||||
assert feature.value > 1 and feature.value <= 100
|
assert feature.value > 1
|
||||||
|
assert feature.value <= 100
|
||||||
|
|
||||||
# Test setting the value
|
# Test setting the value
|
||||||
await feature.set_value(10)
|
await feature.set_value(10)
|
||||||
await dev.update()
|
await dev.update()
|
||||||
assert feature.value == 10
|
assert feature.value == 10
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="out of range"):
|
||||||
await feature.set_value(feature.minimum_value - 10)
|
await feature.set_value(feature.minimum_value - 10)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="out of range"):
|
||||||
await feature.set_value(feature.maximum_value + 10)
|
await feature.set_value(feature.maximum_value + 10)
|
||||||
|
|
||||||
|
|
||||||
@ -41,15 +42,16 @@ async def test_brightness_dimmable(dev: IotDevice):
|
|||||||
# Test getting the value
|
# Test getting the value
|
||||||
feature = dev.features["brightness"]
|
feature = dev.features["brightness"]
|
||||||
assert isinstance(feature.value, int)
|
assert isinstance(feature.value, int)
|
||||||
assert feature.value > 0 and feature.value <= 100
|
assert feature.value > 0
|
||||||
|
assert feature.value <= 100
|
||||||
|
|
||||||
# Test setting the value
|
# Test setting the value
|
||||||
await feature.set_value(10)
|
await feature.set_value(10)
|
||||||
await dev.update()
|
await dev.update()
|
||||||
assert feature.value == 10
|
assert feature.value == 10
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="out of range"):
|
||||||
await feature.set_value(feature.minimum_value - 10)
|
await feature.set_value(feature.minimum_value - 10)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="out of range"):
|
||||||
await feature.set_value(feature.maximum_value + 10)
|
await feature.set_value(feature.maximum_value + 10)
|
||||||
|
@ -23,8 +23,8 @@ async def test_colortemp_component(dev: SmartDevice):
|
|||||||
await dev.update()
|
await dev.update()
|
||||||
assert feature.value == new_value
|
assert feature.value == new_value
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="out of range"):
|
||||||
await feature.set_value(feature.minimum_value - 10)
|
await feature.set_value(feature.minimum_value - 10)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="out of range"):
|
||||||
await feature.set_value(feature.maximum_value + 10)
|
await feature.set_value(feature.maximum_value + 10)
|
||||||
|
@ -18,7 +18,7 @@ autooff = parametrize(
|
|||||||
|
|
||||||
@autooff
|
@autooff
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, prop_name, type",
|
("feature", "prop_name", "type"),
|
||||||
[
|
[
|
||||||
("auto_off_enabled", "enabled", bool),
|
("auto_off_enabled", "enabled", bool),
|
||||||
("auto_off_minutes", "delay", int),
|
("auto_off_minutes", "delay", int),
|
||||||
|
@ -10,7 +10,7 @@ contact = parametrize(
|
|||||||
|
|
||||||
@contact
|
@contact
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, type",
|
("feature", "type"),
|
||||||
[
|
[
|
||||||
("is_open", bool),
|
("is_open", bool),
|
||||||
],
|
],
|
||||||
|
@ -76,8 +76,8 @@ async def test_fan_module(dev: SmartDevice, mocker: MockerFixture):
|
|||||||
await dev.update()
|
await dev.update()
|
||||||
assert not device.is_on
|
assert not device.is_on
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid level"):
|
||||||
await fan.set_fan_speed_level(-1)
|
await fan.set_fan_speed_level(-1)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid level"):
|
||||||
await fan.set_fan_speed_level(5)
|
await fan.set_fan_speed_level(5)
|
||||||
|
@ -19,11 +19,10 @@ firmware = parametrize(
|
|||||||
|
|
||||||
@firmware
|
@firmware
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, prop_name, type, required_version",
|
("feature", "prop_name", "type", "required_version"),
|
||||||
[
|
[
|
||||||
("auto_update_enabled", "auto_update_enabled", bool, 2),
|
("auto_update_enabled", "auto_update_enabled", bool, 2),
|
||||||
("update_available", "update_available", bool, 1),
|
("update_available", "update_available", bool, 1),
|
||||||
("update_available", "update_available", bool, 1),
|
|
||||||
("current_firmware_version", "current_firmware", str, 1),
|
("current_firmware_version", "current_firmware", str, 1),
|
||||||
("available_firmware_version", "latest_firmware", str, 1),
|
("available_firmware_version", "latest_firmware", str, 1),
|
||||||
],
|
],
|
||||||
|
@ -10,7 +10,7 @@ humidity = parametrize(
|
|||||||
|
|
||||||
@humidity
|
@humidity
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, type",
|
("feature", "type"),
|
||||||
[
|
[
|
||||||
("humidity", int),
|
("humidity", int),
|
||||||
("humidity_warning", bool),
|
("humidity_warning", bool),
|
||||||
|
@ -37,7 +37,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
|
|||||||
assert light_effect.effect == effect
|
assert light_effect.effect == effect
|
||||||
assert feature.value == effect
|
assert feature.value == effect
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="The effect foobar is not a built in effect"):
|
||||||
await light_effect.set_effect("foobar")
|
await light_effect.set_effect("foobar")
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ async def test_light_strip_effect(dev: Device, mocker: MockerFixture):
|
|||||||
assert light_effect.effect == effect
|
assert light_effect.effect == effect
|
||||||
assert feature.value == effect
|
assert feature.value == effect
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="The effect foobar is not a built in effect"):
|
||||||
await light_effect.set_effect("foobar")
|
await light_effect.set_effect("foobar")
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ motion = parametrize(
|
|||||||
|
|
||||||
@motion
|
@motion
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, type",
|
("feature", "type"),
|
||||||
[
|
[
|
||||||
("motion_detected", bool),
|
("motion_detected", bool),
|
||||||
],
|
],
|
||||||
|
@ -16,7 +16,7 @@ temperature_warning = parametrize(
|
|||||||
|
|
||||||
@temperature
|
@temperature
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, type",
|
("feature", "type"),
|
||||||
[
|
[
|
||||||
("temperature", float),
|
("temperature", float),
|
||||||
("temperature_unit", str),
|
("temperature_unit", str),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ temperature = parametrize(
|
|||||||
|
|
||||||
@thermostats_smart
|
@thermostats_smart
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, type",
|
("feature", "type"),
|
||||||
[
|
[
|
||||||
("target_temperature", float),
|
("target_temperature", float),
|
||||||
("temperature_offset", int),
|
("temperature_offset", int),
|
||||||
@ -59,10 +60,14 @@ async def test_set_temperature_invalid_values(dev):
|
|||||||
"""Test that out-of-bounds temperature values raise errors."""
|
"""Test that out-of-bounds temperature values raise errors."""
|
||||||
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
|
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match="Invalid target temperature -1, must be in range"
|
||||||
|
):
|
||||||
await temp_module.set_target_temperature(-1)
|
await temp_module.set_target_temperature(-1)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match="Invalid target temperature 100, must be in range"
|
||||||
|
):
|
||||||
await temp_module.set_target_temperature(100)
|
await temp_module.set_target_temperature(100)
|
||||||
|
|
||||||
|
|
||||||
@ -70,10 +75,14 @@ async def test_set_temperature_invalid_values(dev):
|
|||||||
async def test_temperature_offset(dev):
|
async def test_temperature_offset(dev):
|
||||||
"""Test the temperature offset API."""
|
"""Test the temperature offset API."""
|
||||||
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
|
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match=re.escape("Temperature offset must be [-10, 10]")
|
||||||
|
):
|
||||||
await temp_module.set_temperature_offset(100)
|
await temp_module.set_temperature_offset(100)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match=re.escape("Temperature offset must be [-10, 10]")
|
||||||
|
):
|
||||||
await temp_module.set_temperature_offset(-100)
|
await temp_module.set_temperature_offset(-100)
|
||||||
|
|
||||||
await temp_module.set_temperature_offset(5)
|
await temp_module.set_temperature_offset(5)
|
||||||
@ -83,7 +92,7 @@ async def test_temperature_offset(dev):
|
|||||||
|
|
||||||
@thermostats_smart
|
@thermostats_smart
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mode, states, frost_protection",
|
("mode", "states", "frost_protection"),
|
||||||
[
|
[
|
||||||
pytest.param(ThermostatState.Idle, [], False, id="idle has empty"),
|
pytest.param(ThermostatState.Idle, [], False, id="idle has empty"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
@ -114,7 +123,7 @@ async def test_thermostat_mode(dev, mode, states, frost_protection):
|
|||||||
|
|
||||||
@thermostats_smart
|
@thermostats_smart
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mode, states, msg",
|
("mode", "states", "msg"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
ThermostatState.Heating,
|
ThermostatState.Heating,
|
||||||
|
@ -12,7 +12,7 @@ waterleak = parametrize(
|
|||||||
|
|
||||||
@waterleak
|
@waterleak
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"feature, prop_name, type",
|
("feature", "prop_name", "type"),
|
||||||
[
|
[
|
||||||
("water_alert", "alert", int),
|
("water_alert", "alert", int),
|
||||||
("water_leak", "status", Enum),
|
("water_leak", "status", Enum),
|
||||||
|
@ -100,7 +100,7 @@ async def test_login(mocker, status_code, error_code, inner_error_code, expectat
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"inner_error_codes, expectation, call_count",
|
("inner_error_codes", "expectation", "call_count"),
|
||||||
[
|
[
|
||||||
([SmartErrorCode.LOGIN_ERROR, 0, 0, 0], does_not_raise(), 4),
|
([SmartErrorCode.LOGIN_ERROR, 0, 0, 0], does_not_raise(), 4),
|
||||||
(
|
(
|
||||||
@ -298,7 +298,7 @@ async def test_unknown_errors(mocker, error_code):
|
|||||||
"requestID": 1,
|
"requestID": 1,
|
||||||
"terminal_uuid": "foobar",
|
"terminal_uuid": "foobar",
|
||||||
}
|
}
|
||||||
with pytest.raises(KasaException):
|
with pytest.raises(KasaException): # noqa: PT012
|
||||||
res = await transport.send(json_dumps(request))
|
res = await transport.send(json_dumps(request))
|
||||||
assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR
|
assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ async def test_port_override():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_delay_required, should_error, should_succeed",
|
("device_delay_required", "should_error", "should_succeed"),
|
||||||
[
|
[
|
||||||
pytest.param(0, False, True, id="No error"),
|
pytest.param(0, False, True, id="No error"),
|
||||||
pytest.param(0.125, True, True, id="Error then succeed"),
|
pytest.param(0.125, True, True, id="Error then succeed"),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from voluptuous import (
|
from voluptuous import (
|
||||||
All,
|
All,
|
||||||
@ -51,10 +53,8 @@ async def test_state_attributes(dev: Device):
|
|||||||
|
|
||||||
@bulb_iot
|
@bulb_iot
|
||||||
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
|
async def test_light_state_without_update(dev: IotBulb, monkeypatch):
|
||||||
|
monkeypatch.setitem(dev._last_update["system"]["get_sysinfo"], "light_state", None)
|
||||||
with pytest.raises(KasaException):
|
with pytest.raises(KasaException):
|
||||||
monkeypatch.setitem(
|
|
||||||
dev._last_update["system"]["get_sysinfo"], "light_state", None
|
|
||||||
)
|
|
||||||
print(dev.light_state)
|
print(dev.light_state)
|
||||||
|
|
||||||
|
|
||||||
@ -114,23 +114,72 @@ async def test_light_set_state(dev: IotBulb, mocker):
|
|||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
@turn_on
|
@turn_on
|
||||||
async def test_invalid_hsv(dev: Device, turn_on):
|
@pytest.mark.parametrize(
|
||||||
|
("hue", "sat", "brightness", "exception_cls", "error"),
|
||||||
|
[
|
||||||
|
pytest.param(-1, 0, 0, ValueError, "Invalid hue", id="hue out of range"),
|
||||||
|
pytest.param(361, 0, 0, ValueError, "Invalid hue", id="hue out of range"),
|
||||||
|
pytest.param(
|
||||||
|
0.5, 0, 0, TypeError, "Hue must be an integer", id="hue invalid type"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"foo", 0, 0, TypeError, "Hue must be an integer", id="hue invalid type"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0, -1, 0, ValueError, "Invalid saturation", id="saturation out of range"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0, 101, 0, ValueError, "Invalid saturation", id="saturation out of range"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0,
|
||||||
|
0.5,
|
||||||
|
0,
|
||||||
|
TypeError,
|
||||||
|
"Saturation must be an integer",
|
||||||
|
id="saturation invalid type",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0,
|
||||||
|
"foo",
|
||||||
|
0,
|
||||||
|
TypeError,
|
||||||
|
"Saturation must be an integer",
|
||||||
|
id="saturation invalid type",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0, 0, -1, ValueError, "Invalid brightness", id="brightness out of range"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0, 0, 101, ValueError, "Invalid brightness", id="brightness out of range"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.5,
|
||||||
|
TypeError,
|
||||||
|
"Brightness must be an integer",
|
||||||
|
id="brightness invalid type",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"foo",
|
||||||
|
TypeError,
|
||||||
|
"Brightness must be an integer",
|
||||||
|
id="brightness invalid type",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_invalid_hsv(
|
||||||
|
dev: Device, turn_on, hue, sat, brightness, exception_cls, error
|
||||||
|
):
|
||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
await handle_turn_on(dev, turn_on)
|
await handle_turn_on(dev, turn_on)
|
||||||
assert light.is_color
|
assert light.is_color
|
||||||
|
with pytest.raises(exception_cls, match=error):
|
||||||
for invalid_hue in [-1, 361, 0.5]:
|
await light.set_hsv(hue, sat, brightness)
|
||||||
with pytest.raises(ValueError):
|
|
||||||
await light.set_hsv(invalid_hue, 0, 0) # type: ignore[arg-type]
|
|
||||||
|
|
||||||
for invalid_saturation in [-1, 101, 0.5]:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
await light.set_hsv(0, invalid_saturation, 0) # type: ignore[arg-type]
|
|
||||||
|
|
||||||
for invalid_brightness in [-1, 101, 0.5]:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
await light.set_hsv(0, 0, invalid_brightness) # type: ignore[arg-type]
|
|
||||||
|
|
||||||
|
|
||||||
@color_bulb
|
@color_bulb
|
||||||
@ -201,9 +250,13 @@ async def test_smart_temp_range(dev: Device):
|
|||||||
async def test_out_of_range_temperature(dev: Device):
|
async def test_out_of_range_temperature(dev: Device):
|
||||||
light = dev.modules.get(Module.Light)
|
light = dev.modules.get(Module.Light)
|
||||||
assert light
|
assert light
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match="Temperature should be between \d+ and \d+, was 1000"
|
||||||
|
):
|
||||||
await light.set_color_temp(1000)
|
await light.set_color_temp(1000)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match="Temperature should be between \d+ and \d+, was 10000"
|
||||||
|
):
|
||||||
await light.set_color_temp(10000)
|
await light.set_color_temp(10000)
|
||||||
|
|
||||||
|
|
||||||
@ -236,7 +289,7 @@ async def test_dimmable_brightness(dev: IotBulb, turn_on):
|
|||||||
await dev.update()
|
await dev.update()
|
||||||
assert dev.brightness == 10
|
assert dev.brightness == 10
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(TypeError, match="Brightness must be an integer"):
|
||||||
await dev.set_brightness("foo") # type: ignore[arg-type]
|
await dev.set_brightness("foo") # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
@ -264,10 +317,16 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
|
|||||||
async def test_invalid_brightness(dev: IotBulb):
|
async def test_invalid_brightness(dev: IotBulb):
|
||||||
assert dev._is_dimmable
|
assert dev._is_dimmable
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"),
|
||||||
|
):
|
||||||
await dev.set_brightness(110)
|
await dev.set_brightness(110)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"),
|
||||||
|
):
|
||||||
await dev.set_brightness(-100)
|
await dev.set_brightness(-100)
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,9 +128,9 @@ async def test_light_effect_module(dev: Device, mocker: MockerFixture):
|
|||||||
assert feat.value == second_effect
|
assert feat.value == second_effect
|
||||||
call.reset_mock()
|
call.reset_mock()
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="The effect foobar is not a built in effect."):
|
||||||
await light_effect_module.set_effect("foobar")
|
await light_effect_module.set_effect("foobar")
|
||||||
call.assert_not_called()
|
call.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@light_effect
|
@light_effect
|
||||||
@ -174,10 +174,10 @@ async def test_light_brightness(dev: Device):
|
|||||||
await dev.update()
|
await dev.update()
|
||||||
assert light.brightness == 10
|
assert light.brightness == 10
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid brightness value: "):
|
||||||
await light.set_brightness(feature.minimum_value - 10)
|
await light.set_brightness(feature.minimum_value - 10)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid brightness value: "):
|
||||||
await light.set_brightness(feature.maximum_value + 10)
|
await light.set_brightness(feature.maximum_value + 10)
|
||||||
|
|
||||||
|
|
||||||
@ -213,10 +213,10 @@ async def test_light_color_temp(dev: Device):
|
|||||||
assert light.color_temp == feature.minimum_value + 20
|
assert light.color_temp == feature.minimum_value + 20
|
||||||
assert light.brightness == 60
|
assert light.brightness == 60
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Temperature should be between \d+ and \d+"):
|
||||||
await light.set_color_temp(feature.minimum_value - 10)
|
await light.set_color_temp(feature.minimum_value - 10)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Temperature should be between \d+ and \d+"):
|
||||||
await light.set_color_temp(feature.maximum_value + 10)
|
await light.set_color_temp(feature.maximum_value + 10)
|
||||||
|
|
||||||
|
|
||||||
@ -293,9 +293,9 @@ async def test_light_preset_module(dev: Device, mocker: MockerFixture):
|
|||||||
assert preset_mod.preset == second_preset
|
assert preset_mod.preset == second_preset
|
||||||
assert feat.value == second_preset
|
assert feat.value == second_preset
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="foobar is not a valid preset"):
|
||||||
await preset_mod.set_preset("foobar")
|
await preset_mod.set_preset("foobar")
|
||||||
assert call.call_count == 3
|
assert call.call_count == 3
|
||||||
|
|
||||||
|
|
||||||
@light_preset
|
@light_preset
|
||||||
@ -315,9 +315,7 @@ async def test_light_preset_save(dev: Device, mocker: MockerFixture):
|
|||||||
await preset_mod.save_preset(second_preset, new_preset)
|
await preset_mod.save_preset(second_preset, new_preset)
|
||||||
await dev.update()
|
await dev.update()
|
||||||
new_preset_state = preset_mod.preset_states_list[0]
|
new_preset_state = preset_mod.preset_states_list[0]
|
||||||
assert (
|
assert new_preset_state.brightness == new_preset.brightness
|
||||||
new_preset_state.brightness == new_preset.brightness
|
assert new_preset_state.hue == new_preset.hue
|
||||||
and new_preset_state.hue == new_preset.hue
|
assert new_preset_state.saturation == new_preset.saturation
|
||||||
and new_preset_state.saturation == new_preset.saturation
|
assert new_preset_state.color_temp == new_preset.color_temp
|
||||||
and new_preset_state.color_temp == new_preset.color_temp
|
|
||||||
)
|
|
||||||
|
@ -103,7 +103,7 @@ async def test_create_thin_wrapper():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_class, use_class", kasa.deprecated_smart_devices.items()
|
("device_class", "use_class"), kasa.deprecated_smart_devices.items()
|
||||||
)
|
)
|
||||||
def test_deprecated_devices(device_class, use_class):
|
def test_deprecated_devices(device_class, use_class):
|
||||||
package_name = ".".join(use_class.__module__.split(".")[:-1])
|
package_name = ".".join(use_class.__module__.split(".")[:-1])
|
||||||
@ -117,7 +117,9 @@ def test_deprecated_devices(device_class, use_class):
|
|||||||
getattr(module, use_class.__name__)
|
getattr(module, use_class.__name__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("deprecated_class, use_class", kasa.deprecated_classes.items())
|
@pytest.mark.parametrize(
|
||||||
|
("deprecated_class", "use_class"), kasa.deprecated_classes.items()
|
||||||
|
)
|
||||||
def test_deprecated_classes(deprecated_class, use_class):
|
def test_deprecated_classes(deprecated_class, use_class):
|
||||||
msg = f"{deprecated_class} is deprecated, use {use_class.__name__} instead"
|
msg = f"{deprecated_class} is deprecated, use {use_class.__name__} instead"
|
||||||
with pytest.deprecated_call(match=msg):
|
with pytest.deprecated_call(match=msg):
|
||||||
|
@ -46,13 +46,23 @@ async def test_set_brightness_transition(dev, turn_on, mocker):
|
|||||||
|
|
||||||
@dimmer_iot
|
@dimmer_iot
|
||||||
async def test_set_brightness_invalid(dev):
|
async def test_set_brightness_invalid(dev):
|
||||||
for invalid_brightness in [-1, 101, 0.5]:
|
for invalid_brightness in [-1, 101]:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid brightness"):
|
||||||
await dev.set_brightness(invalid_brightness)
|
await dev.set_brightness(invalid_brightness)
|
||||||
|
|
||||||
for invalid_transition in [-1, 0.5]:
|
for invalid_type in [0.5, "foo"]:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(TypeError, match="Brightness must be an integer"):
|
||||||
|
await dev.set_brightness(invalid_type)
|
||||||
|
|
||||||
|
|
||||||
|
@dimmer_iot
|
||||||
|
async def test_set_brightness_invalid_transition(dev):
|
||||||
|
for invalid_transition in [-1]:
|
||||||
|
with pytest.raises(ValueError, match="Transition value .+? is not valid."):
|
||||||
await dev.set_brightness(1, transition=invalid_transition)
|
await dev.set_brightness(1, transition=invalid_transition)
|
||||||
|
for invalid_type in [0.5, "foo"]:
|
||||||
|
with pytest.raises(TypeError, match="Transition must be integer"):
|
||||||
|
await dev.set_brightness(1, transition=invalid_type)
|
||||||
|
|
||||||
|
|
||||||
@dimmer_iot
|
@dimmer_iot
|
||||||
@ -128,14 +138,24 @@ async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
|
|||||||
|
|
||||||
|
|
||||||
@dimmer_iot
|
@dimmer_iot
|
||||||
async def test_set_dimmer_transition_invalid(dev):
|
async def test_set_dimmer_transition_invalid_brightness(dev):
|
||||||
for invalid_brightness in [-1, 101, 0.5]:
|
for invalid_brightness in [-1, 101]:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid brightness value: "):
|
||||||
await dev.set_dimmer_transition(invalid_brightness, 1000)
|
await dev.set_dimmer_transition(invalid_brightness, 1000)
|
||||||
|
|
||||||
for invalid_transition in [-1, 0.5]:
|
for invalid_type in [0.5, "foo"]:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(TypeError, match="Transition must be integer"):
|
||||||
await dev.set_dimmer_transition(1, invalid_transition)
|
await dev.set_dimmer_transition(1, invalid_type)
|
||||||
|
|
||||||
|
|
||||||
|
@dimmer_iot
|
||||||
|
async def test_set_dimmer_transition_invalid_transition(dev):
|
||||||
|
for invalid_transition in [-1]:
|
||||||
|
with pytest.raises(ValueError, match="Transition value .+? is not valid."):
|
||||||
|
await dev.set_dimmer_transition(1, transition=invalid_transition)
|
||||||
|
for invalid_type in [0.5, "foo"]:
|
||||||
|
with pytest.raises(TypeError, match="Transition must be integer"):
|
||||||
|
await dev.set_dimmer_transition(1, transition=invalid_type)
|
||||||
|
|
||||||
|
|
||||||
@dimmer_iot
|
@dimmer_iot
|
||||||
|
@ -252,7 +252,7 @@ INVALIDS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("msg, data", INVALIDS)
|
@pytest.mark.parametrize(("msg", "data"), INVALIDS)
|
||||||
async def test_discover_invalid_info(msg, data, mocker):
|
async def test_discover_invalid_info(msg, data, mocker):
|
||||||
"""Make sure that invalid discovery information raises an exception."""
|
"""Make sure that invalid discovery information raises an exception."""
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
@ -304,7 +304,7 @@ async def test_discover_datagram_received(mocker, discovery_data):
|
|||||||
assert dev.host == addr
|
assert dev.host == addr
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("msg, data", INVALIDS)
|
@pytest.mark.parametrize(("msg", "data"), INVALIDS)
|
||||||
async def test_discover_invalid_responses(msg, data, mocker):
|
async def test_discover_invalid_responses(msg, data, mocker):
|
||||||
"""Verify that we don't crash whole discovery if some devices in the network are sending unexpected data."""
|
"""Verify that we don't crash whole discovery if some devices in the network are sending unexpected data."""
|
||||||
proto = _DiscoverProtocol()
|
proto = _DiscoverProtocol()
|
||||||
@ -349,7 +349,7 @@ async def test_discover_single_authentication(discovery_mock, mocker):
|
|||||||
side_effect=AuthenticationError("Failed to authenticate"),
|
side_effect=AuthenticationError("Failed to authenticate"),
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises( # noqa: PT012
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
match="Failed to authenticate",
|
match="Failed to authenticate",
|
||||||
):
|
):
|
||||||
@ -495,7 +495,7 @@ async def test_do_discover_drop_packets(mocker, port, do_not_reply_count):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"port, will_timeout",
|
("port", "will_timeout"),
|
||||||
[(FakeDatagramTransport.GHOST_PORT, True), (20002, False)],
|
[(FakeDatagramTransport.GHOST_PORT, True), (20002, False)],
|
||||||
ids=["unknownport", "unsupporteddevice"],
|
ids=["unknownport", "unsupporteddevice"],
|
||||||
)
|
)
|
||||||
|
@ -61,7 +61,7 @@ async def test_get_emeter_realtime(dev):
|
|||||||
|
|
||||||
|
|
||||||
@has_emeter_iot
|
@has_emeter_iot
|
||||||
@pytest.mark.requires_dummy
|
@pytest.mark.requires_dummy()
|
||||||
async def test_get_emeter_daily(dev):
|
async def test_get_emeter_daily(dev):
|
||||||
assert dev.has_emeter
|
assert dev.has_emeter
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ async def test_get_emeter_daily(dev):
|
|||||||
|
|
||||||
|
|
||||||
@has_emeter_iot
|
@has_emeter_iot
|
||||||
@pytest.mark.requires_dummy
|
@pytest.mark.requires_dummy()
|
||||||
async def test_get_emeter_monthly(dev):
|
async def test_get_emeter_monthly(dev):
|
||||||
assert dev.has_emeter
|
assert dev.has_emeter
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ class DummyDevice:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def dummy_feature() -> Feature:
|
def dummy_feature() -> Feature:
|
||||||
# create_autospec for device slows tests way too much, so we use a dummy here
|
# create_autospec for device slows tests way too much, so we use a dummy here
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ def test_feature_api(dummy_feature: Feature):
|
|||||||
)
|
)
|
||||||
def test_feature_setter_on_sensor(read_only_type):
|
def test_feature_setter_on_sensor(read_only_type):
|
||||||
"""Test that creating a sensor feature with a setter causes an error."""
|
"""Test that creating a sensor feature with a setter causes an error."""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Invalid type for configurable feature"):
|
||||||
Feature(
|
Feature(
|
||||||
device=DummyDevice(), # type: ignore[arg-type]
|
device=DummyDevice(), # type: ignore[arg-type]
|
||||||
id="dummy_error",
|
id="dummy_error",
|
||||||
@ -103,7 +103,7 @@ async def test_feature_setter(dev, mocker, dummy_feature: Feature):
|
|||||||
async def test_feature_setter_read_only(dummy_feature):
|
async def test_feature_setter_read_only(dummy_feature):
|
||||||
"""Verify that read-only feature raises an exception when trying to change it."""
|
"""Verify that read-only feature raises an exception when trying to change it."""
|
||||||
dummy_feature.attribute_setter = None
|
dummy_feature.attribute_setter = None
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Tried to set read-only feature"):
|
||||||
await dummy_feature.set_value("value for read only feature")
|
await dummy_feature.set_value("value for read only feature")
|
||||||
|
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ async def test_feature_choice_list(dummy_feature, caplog, mocker: MockerFixture)
|
|||||||
mock_setter.assert_called_with("first")
|
mock_setter.assert_called_with("first")
|
||||||
mock_setter.reset_mock()
|
mock_setter.reset_mock()
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Unexpected value for dummy_feature: invalid"): # noqa: PT012
|
||||||
await dummy_feature.set_value("invalid")
|
await dummy_feature.set_value("invalid")
|
||||||
assert "Unexpected value" in caplog.text
|
assert "Unexpected value" in caplog.text
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from ..httpclient import HttpClient
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, error_raises, error_message",
|
("error", "error_raises", "error_message"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
aiohttp.ServerDisconnectedError(),
|
aiohttp.ServerDisconnectedError(),
|
||||||
@ -52,7 +52,7 @@ from ..httpclient import HttpClient
|
|||||||
"ServerFingerprintMismatch",
|
"ServerFingerprintMismatch",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("mock_read", (False, True), ids=("post", "read"))
|
@pytest.mark.parametrize("mock_read", [False, True], ids=("post", "read"))
|
||||||
async def test_httpclient_errors(mocker, error, error_raises, error_message, mock_read):
|
async def test_httpclient_errors(mocker, error, error_raises, error_message, mock_read):
|
||||||
class _mock_response:
|
class _mock_response:
|
||||||
def __init__(self, status, error):
|
def __init__(self, status, error):
|
||||||
|
@ -89,7 +89,7 @@ async def test_state_info(dev):
|
|||||||
assert isinstance(dev.state_information, dict)
|
assert isinstance(dev.state_information, dict)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.requires_dummy
|
@pytest.mark.requires_dummy()
|
||||||
@device_iot
|
@device_iot
|
||||||
async def test_invalid_connection(mocker, dev):
|
async def test_invalid_connection(mocker, dev):
|
||||||
with (
|
with (
|
||||||
|
@ -49,7 +49,7 @@ class _mock_response:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, retry_expectation",
|
("error", "retry_expectation"),
|
||||||
[
|
[
|
||||||
(Exception("dummy exception"), False),
|
(Exception("dummy exception"), False),
|
||||||
(aiohttp.ServerTimeoutError("dummy exception"), True),
|
(aiohttp.ServerTimeoutError("dummy exception"), True),
|
||||||
@ -79,7 +79,7 @@ async def test_protocol_retries_via_client_session(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, retry_expectation",
|
("error", "retry_expectation"),
|
||||||
[
|
[
|
||||||
(KasaException("dummy exception"), False),
|
(KasaException("dummy exception"), False),
|
||||||
(_RetryableError("dummy exception"), True),
|
(_RetryableError("dummy exception"), True),
|
||||||
@ -305,7 +305,7 @@ async def test_transport_decrypt_error(mocker, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"device_credentials, expectation",
|
("device_credentials", "expectation"),
|
||||||
[
|
[
|
||||||
(Credentials("foo", "bar"), does_not_raise()),
|
(Credentials("foo", "bar"), does_not_raise()),
|
||||||
(Credentials(), does_not_raise()),
|
(Credentials(), does_not_raise()),
|
||||||
@ -321,7 +321,7 @@ async def test_transport_decrypt_error(mocker, caplog):
|
|||||||
ids=("client", "blank", "kasa_setup", "shouldfail"),
|
ids=("client", "blank", "kasa_setup", "shouldfail"),
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"transport_class, seed_auth_hash_calc",
|
("transport_class", "seed_auth_hash_calc"),
|
||||||
[
|
[
|
||||||
pytest.param(KlapTransport, lambda c, s, a: c + a, id="KLAP"),
|
pytest.param(KlapTransport, lambda c, s, a: c + a, id="KLAP"),
|
||||||
pytest.param(KlapTransportV2, lambda c, s, a: c + s + a, id="KLAPV2"),
|
pytest.param(KlapTransportV2, lambda c, s, a: c + s + a, id="KLAPV2"),
|
||||||
@ -365,7 +365,7 @@ async def test_handshake1(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"transport_class, seed_auth_hash_calc1, seed_auth_hash_calc2",
|
("transport_class", "seed_auth_hash_calc1", "seed_auth_hash_calc2"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
KlapTransport, lambda c, s, a: c + a, lambda c, s, a: s + a, id="KLAP"
|
KlapTransport, lambda c, s, a: c + a, lambda c, s, a: s + a, id="KLAP"
|
||||||
@ -466,7 +466,7 @@ async def test_query(mocker):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"response_status, credentials_match, expectation",
|
("response_status", "credentials_match", "expectation"),
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
(403, 403, 403),
|
(403, 403, 403),
|
||||||
|
@ -22,7 +22,9 @@ async def test_lightstrip_effect(dev: IotLightStrip):
|
|||||||
|
|
||||||
@lightstrip_iot
|
@lightstrip_iot
|
||||||
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(
|
||||||
|
ValueError, match="The effect Not real is not a built in effect"
|
||||||
|
):
|
||||||
await dev.set_effect("Not real")
|
await dev.set_effect("Not real")
|
||||||
|
|
||||||
await dev.set_effect("Candy Cane")
|
await dev.set_effect("Candy Cane")
|
||||||
|
@ -33,7 +33,7 @@ from .fakeprotocol_iot import FakeIotTransport
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class",
|
("protocol_class", "transport_class"),
|
||||||
[
|
[
|
||||||
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
||||||
(IotProtocol, XorTransport),
|
(IotProtocol, XorTransport),
|
||||||
@ -63,7 +63,7 @@ async def test_protocol_retries(mocker, retry_count, protocol_class, transport_c
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class",
|
("protocol_class", "transport_class"),
|
||||||
[
|
[
|
||||||
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
||||||
(IotProtocol, XorTransport),
|
(IotProtocol, XorTransport),
|
||||||
@ -87,7 +87,7 @@ async def test_protocol_no_retry_on_unreachable(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class",
|
("protocol_class", "transport_class"),
|
||||||
[
|
[
|
||||||
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
||||||
(IotProtocol, XorTransport),
|
(IotProtocol, XorTransport),
|
||||||
@ -111,7 +111,7 @@ async def test_protocol_no_retry_connection_refused(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class",
|
("protocol_class", "transport_class"),
|
||||||
[
|
[
|
||||||
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
||||||
(IotProtocol, XorTransport),
|
(IotProtocol, XorTransport),
|
||||||
@ -135,7 +135,7 @@ async def test_protocol_retry_recoverable_error(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class, encryption_class",
|
("protocol_class", "transport_class", "encryption_class"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
_deprecated_TPLinkSmartHomeProtocol,
|
_deprecated_TPLinkSmartHomeProtocol,
|
||||||
@ -185,7 +185,7 @@ async def test_protocol_reconnect(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class, encryption_class",
|
("protocol_class", "transport_class", "encryption_class"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
_deprecated_TPLinkSmartHomeProtocol,
|
_deprecated_TPLinkSmartHomeProtocol,
|
||||||
@ -239,7 +239,7 @@ async def test_protocol_handles_cancellation_during_write(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class, encryption_class",
|
("protocol_class", "transport_class", "encryption_class"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
_deprecated_TPLinkSmartHomeProtocol,
|
_deprecated_TPLinkSmartHomeProtocol,
|
||||||
@ -291,7 +291,7 @@ async def test_protocol_handles_cancellation_during_connection(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class, encryption_class",
|
("protocol_class", "transport_class", "encryption_class"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
_deprecated_TPLinkSmartHomeProtocol,
|
_deprecated_TPLinkSmartHomeProtocol,
|
||||||
@ -338,7 +338,7 @@ async def test_protocol_logging(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class, encryption_class",
|
("protocol_class", "transport_class", "encryption_class"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
_deprecated_TPLinkSmartHomeProtocol,
|
_deprecated_TPLinkSmartHomeProtocol,
|
||||||
@ -494,14 +494,10 @@ def test_protocol_init_signature(class_name_obj):
|
|||||||
params = list(inspect.signature(class_name_obj[1].__init__).parameters.values())
|
params = list(inspect.signature(class_name_obj[1].__init__).parameters.values())
|
||||||
|
|
||||||
assert len(params) == 2
|
assert len(params) == 2
|
||||||
assert (
|
assert params[0].name == "self"
|
||||||
params[0].name == "self"
|
assert params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||||
and params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
assert params[1].name == "transport"
|
||||||
)
|
assert params[1].kind == inspect.Parameter.KEYWORD_ONLY
|
||||||
assert (
|
|
||||||
params[1].name == "transport"
|
|
||||||
and params[1].kind == inspect.Parameter.KEYWORD_ONLY
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -511,13 +507,10 @@ def test_transport_init_signature(class_name_obj):
|
|||||||
params = list(inspect.signature(class_name_obj[1].__init__).parameters.values())
|
params = list(inspect.signature(class_name_obj[1].__init__).parameters.values())
|
||||||
|
|
||||||
assert len(params) == 2
|
assert len(params) == 2
|
||||||
assert (
|
assert params[0].name == "self"
|
||||||
params[0].name == "self"
|
assert params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||||
and params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
assert params[1].name == "config"
|
||||||
)
|
assert params[1].kind == inspect.Parameter.KEYWORD_ONLY
|
||||||
assert (
|
|
||||||
params[1].name == "config" and params[1].kind == inspect.Parameter.KEYWORD_ONLY
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -582,7 +575,7 @@ async def test_transport_credentials_hash(
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"transport_class",
|
"transport_class",
|
||||||
[AesTransport, KlapTransport, KlapTransportV2, XorTransport, XorTransport],
|
[AesTransport, KlapTransport, KlapTransportV2, XorTransport],
|
||||||
)
|
)
|
||||||
async def test_transport_credentials_hash_from_config(mocker, transport_class):
|
async def test_transport_credentials_hash_from_config(mocker, transport_class):
|
||||||
"""Test that credentials_hash provided via config sets correctly."""
|
"""Test that credentials_hash provided via config sets correctly."""
|
||||||
@ -599,7 +592,7 @@ async def test_transport_credentials_hash_from_config(mocker, transport_class):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, retry_expectation",
|
("error", "retry_expectation"),
|
||||||
[
|
[
|
||||||
(ConnectionRefusedError("dummy exception"), False),
|
(ConnectionRefusedError("dummy exception"), False),
|
||||||
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), False),
|
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), False),
|
||||||
@ -609,7 +602,7 @@ async def test_transport_credentials_hash_from_config(mocker, transport_class):
|
|||||||
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
|
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class",
|
("protocol_class", "transport_class"),
|
||||||
[
|
[
|
||||||
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
||||||
(IotProtocol, XorTransport),
|
(IotProtocol, XorTransport),
|
||||||
@ -631,7 +624,7 @@ async def test_protocol_will_retry_on_connect(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"error, retry_expectation",
|
("error", "retry_expectation"),
|
||||||
[
|
[
|
||||||
(ConnectionRefusedError("dummy exception"), True),
|
(ConnectionRefusedError("dummy exception"), True),
|
||||||
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), True),
|
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), True),
|
||||||
@ -641,7 +634,7 @@ async def test_protocol_will_retry_on_connect(
|
|||||||
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
|
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"protocol_class, transport_class",
|
("protocol_class", "transport_class"),
|
||||||
[
|
[
|
||||||
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
|
||||||
(IotProtocol, XorTransport),
|
(IotProtocol, XorTransport),
|
||||||
|
@ -145,7 +145,7 @@ def test_tutorial_examples(readmes_mock):
|
|||||||
assert not res["failed"]
|
assert not res["failed"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
async def readmes_mock(mocker):
|
async def readmes_mock(mocker):
|
||||||
fixture_infos = {
|
fixture_infos = {
|
||||||
"127.0.0.1": get_fixture_info("KP303(UK)_1.0_1.0.3.json", "IOT"), # Strip
|
"127.0.0.1": get_fixture_info("KP303(UK)_1.0_1.0.3.json", "IOT"), # Strip
|
||||||
@ -154,4 +154,4 @@ async def readmes_mock(mocker):
|
|||||||
"127.0.0.4": get_fixture_info("KL430(US)_1.0_1.0.10.json", "IOT"), # Lightstrip
|
"127.0.0.4": get_fixture_info("KL430(US)_1.0_1.0.10.json", "IOT"), # Lightstrip
|
||||||
"127.0.0.5": get_fixture_info("HS220(US)_1.0_1.5.7.json", "IOT"), # Dimmer
|
"127.0.0.5": get_fixture_info("HS220(US)_1.0_1.5.7.json", "IOT"), # Dimmer
|
||||||
}
|
}
|
||||||
yield patch_discovery(fixture_infos, mocker)
|
return patch_discovery(fixture_infos, mocker)
|
||||||
|
@ -65,7 +65,7 @@ async def test_smart_device_unknown_errors(
|
|||||||
dummy_protocol._transport, "send", return_value=mock_response
|
dummy_protocol._transport, "send", return_value=mock_response
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(KasaException):
|
with pytest.raises(KasaException): # noqa: PT012
|
||||||
res = await dummy_protocol.query(DUMMY_QUERY)
|
res = await dummy_protocol.query(DUMMY_QUERY)
|
||||||
assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR
|
assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ def test_usage_convert_stat_data():
|
|||||||
k, v = d.popitem()
|
k, v = d.popitem()
|
||||||
assert isinstance(k, int)
|
assert isinstance(k, int)
|
||||||
assert isinstance(v, int)
|
assert isinstance(v, int)
|
||||||
assert k == 4 and v == 30
|
assert k == 4
|
||||||
|
assert v == 30
|
||||||
|
|
||||||
|
|
||||||
def test_usage_today():
|
def test_usage_today():
|
||||||
|
@ -117,6 +117,7 @@ select = [
|
|||||||
"FA", # flake8-future-annotations
|
"FA", # flake8-future-annotations
|
||||||
"I", # isort
|
"I", # isort
|
||||||
"S", # bandit
|
"S", # bandit
|
||||||
|
"PT", # flake8-pytest-style
|
||||||
"LOG", # flake8-logging
|
"LOG", # flake8-logging
|
||||||
"G", # flake8-logging-format
|
"G", # flake8-logging-format
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user