-
-
Notifications
You must be signed in to change notification settings - Fork 273
Expand file tree
/
Copy pathtest_common_modules.py
More file actions
401 lines (320 loc) · 12.6 KB
/
test_common_modules.py
File metadata and controls
401 lines (320 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
from datetime import datetime
from zoneinfo import ZoneInfo
import pytest
from pytest_mock import MockerFixture
from kasa import Device, LightState, Module, ThermostatState
from .device_fixtures import (
bulb_iot,
bulb_smart,
dimmable_iot,
dimmer_iot,
get_parent_and_child_modules,
lightstrip_iot,
parametrize,
parametrize_combine,
plug_iot,
variable_temp_iot,
)
led_smart = parametrize(
"has led smart", component_filter="led", protocol_filter={"SMART"}
)
led = parametrize_combine([led_smart, plug_iot])
light_effect_smart = parametrize(
"has light effect smart", component_filter="light_effect", protocol_filter={"SMART"}
)
light_strip_effect_smart = parametrize(
"has light strip effect smart",
component_filter="light_strip_lighting_effect",
protocol_filter={"SMART"},
)
light_effect = parametrize_combine(
[light_effect_smart, light_strip_effect_smart, lightstrip_iot]
)
dimmable_smart = parametrize(
"dimmable smart", component_filter="brightness", protocol_filter={"SMART"}
)
dimmable = parametrize_combine([dimmable_smart, dimmer_iot, dimmable_iot])
variable_temp_smart = parametrize(
"variable temp smart",
component_filter="color_temperature",
protocol_filter={"SMART"},
)
variable_temp = parametrize_combine([variable_temp_iot, variable_temp_smart])
light_preset_smart = parametrize(
"has light preset smart", component_filter="preset", protocol_filter={"SMART"}
)
light_preset = parametrize_combine([light_preset_smart, bulb_iot])
light = parametrize_combine([bulb_smart, bulb_iot, dimmable])
temp_control_smart = parametrize(
"has temp control smart",
component_filter="temp_control",
protocol_filter={"SMART.CHILD"},
)
@led
async def test_led_module(dev: Device, mocker: MockerFixture):
"""Test fan speed feature."""
led_module = dev.modules.get(Module.Led)
assert led_module
feat = dev.features["led"]
call = mocker.spy(led_module, "call")
await led_module.set_led(True)
assert call.call_count == 1
await dev.update()
assert led_module.led is True
assert feat.value is True
await led_module.set_led(False)
assert call.call_count == 2
await dev.update()
assert led_module.led is False
assert feat.value is False
await feat.set_value(True)
assert call.call_count == 3
await dev.update()
assert feat.value is True
assert led_module.led is True
@light_effect
async def test_light_effect_module(dev: Device, mocker: MockerFixture):
"""Test fan speed feature."""
light_effect_module = dev.modules[Module.LightEffect]
assert light_effect_module
feat = dev.features["light_effect"]
call = mocker.spy(dev, "_query_helper")
effect_list = light_effect_module.effect_list
assert "Off" in effect_list
assert effect_list.index("Off") == 0
assert len(effect_list) > 1
assert effect_list == feat.choices
assert light_effect_module.has_custom_effects is not None
await light_effect_module.set_effect("Off")
call.assert_called()
await dev.update()
assert light_effect_module.effect == "Off"
assert feat.value == "Off"
call.reset_mock()
second_effect = effect_list[1]
await light_effect_module.set_effect(second_effect)
call.assert_called()
await dev.update()
assert light_effect_module.effect == second_effect
assert feat.value == second_effect
call.reset_mock()
last_effect = effect_list[len(effect_list) - 1]
await light_effect_module.set_effect(last_effect)
call.assert_called()
await dev.update()
assert light_effect_module.effect == last_effect
assert feat.value == last_effect
call.reset_mock()
# Test feature set
await feat.set_value(second_effect)
call.assert_called()
await dev.update()
assert light_effect_module.effect == second_effect
assert feat.value == second_effect
call.reset_mock()
with pytest.raises(ValueError, match="The effect foobar is not a built in effect."):
await light_effect_module.set_effect("foobar")
call.assert_not_called()
@light_effect
async def test_light_effect_brightness(dev: Device, mocker: MockerFixture):
"""Test that light module uses light_effect for brightness when active."""
light_module = dev.modules[Module.Light]
light_effect = dev.modules[Module.LightEffect]
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
await light_module.set_brightness(50)
await dev.update()
assert light_effect.effect == light_effect.LIGHT_EFFECTS_OFF
assert light_module.brightness == 50
await light_effect.set_effect(light_effect.effect_list[1])
await dev.update()
# assert light_module.brightness == 100
await light_module.set_brightness(75)
await dev.update()
assert light_module.brightness == 75
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
await dev.update()
assert light_module.brightness == 50
@dimmable
async def test_light_brightness(dev: Device):
"""Test brightness setter and getter."""
assert isinstance(dev, Device)
light = next(get_parent_and_child_modules(dev, Module.Light))
assert light
# Test getting the value
feature = light._device.features["brightness"]
assert feature.minimum_value == 0
assert feature.maximum_value == 100
await light.set_brightness(10)
await dev.update()
assert light.brightness == 10
with pytest.raises(ValueError, match="Invalid brightness value: "):
await light.set_brightness(feature.minimum_value - 10)
with pytest.raises(ValueError, match="Invalid brightness value: "):
await light.set_brightness(feature.maximum_value + 10)
@variable_temp
async def test_light_color_temp(dev: Device):
"""Test color temp setter and getter."""
assert isinstance(dev, Device)
light = next(get_parent_and_child_modules(dev, Module.Light))
assert light
if not light.is_variable_color_temp:
pytest.skip(
"Some smart light strips have color_temperature"
" component but min and max are the same"
)
# Test getting the value
feature = light._device.features["color_temperature"]
assert isinstance(feature.minimum_value, int)
assert isinstance(feature.maximum_value, int)
await light.set_color_temp(feature.minimum_value + 10)
await dev.update()
assert light.color_temp == feature.minimum_value + 10
# Test setting brightness with color temp
await light.set_brightness(50)
await dev.update()
assert light.brightness == 50
await light.set_color_temp(feature.minimum_value + 20, brightness=60)
await dev.update()
assert light.color_temp == feature.minimum_value + 20
assert light.brightness == 60
with pytest.raises(ValueError, match=r"Temperature should be between \d+ and \d+"):
await light.set_color_temp(feature.minimum_value - 10)
with pytest.raises(ValueError, match=r"Temperature should be between \d+ and \d+"):
await light.set_color_temp(feature.maximum_value + 10)
@light
async def test_light_set_state(dev: Device):
"""Test brightness setter and getter."""
assert isinstance(dev, Device)
light = next(get_parent_and_child_modules(dev, Module.Light))
assert light
# For fixtures that have a light effect active switch off
if light_effect := light._device.modules.get(Module.LightEffect):
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
await light.set_state(LightState(light_on=False))
await dev.update()
assert light.state.light_on is False
await light.set_state(LightState(light_on=True))
await dev.update()
assert light.state.light_on is True
await light.set_state(LightState(brightness=0))
await dev.update()
assert light.state.light_on is False
await light.set_state(LightState(brightness=50))
await dev.update()
assert light.state.light_on is True
@light_preset
async def test_light_preset_module(dev: Device, mocker: MockerFixture):
"""Test light preset module."""
preset_mod = next(get_parent_and_child_modules(dev, Module.LightPreset))
assert preset_mod
light_mod = next(get_parent_and_child_modules(dev, Module.Light))
assert light_mod
feat = preset_mod._device.features["light_preset"]
preset_list = preset_mod.preset_list
assert "Not set" in preset_list
assert preset_list.index("Not set") == 0
assert preset_list == feat.choices
assert preset_mod.has_save_preset is True
await light_mod.set_brightness(33) # Value that should not be a preset
await dev.update()
assert preset_mod.preset == "Not set"
assert feat.value == "Not set"
if len(preset_list) == 1:
return
call = mocker.spy(light_mod, "set_state")
second_preset = preset_list[1]
await preset_mod.set_preset(second_preset)
assert call.call_count == 1
await dev.update()
assert preset_mod.preset == second_preset
assert feat.value == second_preset
last_preset = preset_list[len(preset_list) - 1]
await preset_mod.set_preset(last_preset)
assert call.call_count == 2
await dev.update()
assert preset_mod.preset == last_preset
assert feat.value == last_preset
# Test feature set
await feat.set_value(second_preset)
assert call.call_count == 3
await dev.update()
assert preset_mod.preset == second_preset
assert feat.value == second_preset
with pytest.raises(ValueError, match="foobar is not a valid preset"):
await preset_mod.set_preset("foobar")
assert call.call_count == 3
@light_preset
async def test_light_preset_save(dev: Device, mocker: MockerFixture):
"""Test saving a new preset value."""
preset_mod = next(get_parent_and_child_modules(dev, Module.LightPreset))
assert preset_mod
preset_list = preset_mod.preset_list
if len(preset_list) == 1:
return
second_preset = preset_list[1]
if preset_mod.preset_states_list[0].hue is None:
new_preset = LightState(brightness=52)
else:
new_preset = LightState(brightness=52, color_temp=3000, hue=20, saturation=30)
await preset_mod.save_preset(second_preset, new_preset)
await dev.update()
new_preset_state = preset_mod.preset_states_list[0]
assert new_preset_state.brightness == new_preset.brightness
assert new_preset_state.hue == new_preset.hue
assert new_preset_state.saturation == new_preset.saturation
assert new_preset_state.color_temp == new_preset.color_temp
@temp_control_smart
async def test_thermostat(dev: Device, mocker: MockerFixture):
"""Test saving a new preset value."""
therm_mod = next(get_parent_and_child_modules(dev, Module.Thermostat))
assert therm_mod
await therm_mod.set_state(False)
await dev.update()
assert therm_mod.state is False
assert therm_mod.mode is ThermostatState.Off
await therm_mod.set_target_temperature(10)
await dev.update()
assert therm_mod.state is True
assert therm_mod.mode is ThermostatState.Heating
assert therm_mod.target_temperature == 10
target_temperature_feature = therm_mod.get_feature(therm_mod.set_target_temperature)
temp_control = dev.modules.get(Module.TemperatureControl)
assert temp_control
allowed_range = temp_control.allowed_temperature_range
assert target_temperature_feature.minimum_value == allowed_range[0]
assert target_temperature_feature.maximum_value == allowed_range[1]
await therm_mod.set_temperature_unit("celsius")
await dev.update()
assert therm_mod.temperature_unit == "celsius"
await therm_mod.set_temperature_unit("fahrenheit")
await dev.update()
assert therm_mod.temperature_unit == "fahrenheit"
async def test_set_time(dev: Device):
"""Test setting the device time."""
time_mod = dev.modules[Module.Time]
original_time = time_mod.time
original_timezone = time_mod.timezone
test_time = datetime.fromisoformat("2021-01-09 12:00:00+00:00")
test_time = test_time.astimezone(original_timezone)
try:
assert time_mod.time != test_time
await time_mod.set_time(test_time)
await dev.update()
assert time_mod.time == test_time
if (
isinstance(original_timezone, ZoneInfo)
and original_timezone.key != "Europe/Berlin"
):
test_zonezone = ZoneInfo("Europe/Berlin")
else:
test_zonezone = ZoneInfo("Europe/London")
# Just update the timezone
new_time = time_mod.time.astimezone(test_zonezone)
await time_mod.set_time(new_time)
await dev.update()
assert time_mod.time == new_time
finally:
# Reset back to the original
await time_mod.set_time(original_time)
await dev.update()
assert time_mod.time == original_time