Skip to content

Commit 01b3872

Browse files
authored
Merge pull request #68 from plugwise/more_testing
Achieve 100% Smile test coverage
2 parents 37683e0 + 977633e commit 01b3872

File tree

3 files changed

+76
-55
lines changed

3 files changed

+76
-55
lines changed

plugwise/helper.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,6 @@ def presets(self, loc_id):
428428
rule_ids = self.rule_ids_by_tag(tag, loc_id)
429429
if rule_ids is None:
430430
rule_ids = self.rule_ids_by_name("Thermostat presets", loc_id)
431-
if rule_ids is None:
432-
return presets
433431

434432
for rule_id in rule_ids:
435433
directives = self._domain_objects.find(f'rule[@id="{rule_id}"]/directives')
@@ -507,7 +505,7 @@ async def update_domain_objects(self):
507505
msg = notification.find("message").text
508506
self.notifications.update({msg_id: {msg_type: msg}})
509507
_LOGGER.debug("Plugwise notifications: %s", self.notifications)
510-
except AttributeError:
508+
except AttributeError: # pragma: no cover
511509
_LOGGER.info(
512510
"Plugwise notification present but unable to process, manually investigate: %s",
513511
url,

plugwise/smile.py

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,7 @@
2323
SYSTEM,
2424
THERMOSTAT_CLASSES,
2525
)
26-
from .exceptions import (
27-
ConnectionFailedError,
28-
DeviceSetupError,
29-
InvalidXMLError,
30-
UnsupportedDeviceError,
31-
XMLDataMissingError,
32-
)
26+
from .exceptions import ConnectionFailedError, InvalidXMLError, UnsupportedDeviceError
3327
from .helper import SmileHelper
3428

3529
_LOGGER = logging.getLogger(__name__)
@@ -95,8 +89,6 @@ async def connect(self):
9589
)
9690
raise ConnectionFailedError
9791

98-
# TODO create this as another function NOT part of connect!
99-
# just using request to parse the data
10092
gateway = result.find(".//gateway")
10193

10294
model = version = None
@@ -169,11 +161,7 @@ async def connect(self):
169161
self._smile_legacy = SMILES[target_smile]["legacy"]
170162

171163
# Update all endpoints on first connect
172-
try:
173-
await self.full_update_device()
174-
except XMLDataMissingError:
175-
_LOGGER.error("Critical information not returned from device")
176-
raise DeviceSetupError
164+
await self.full_update_device()
177165

178166
return True
179167

tests/test_smile.py

Lines changed: 73 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# String generation
1212
import random
1313
import string
14+
from unittest.mock import patch
1415

1516
# Testing
1617
import aiohttp
@@ -54,6 +55,7 @@ async def setup_app(
5455
timeout=False,
5556
raise_timeout=False,
5657
fail_auth=False,
58+
stretch=False,
5759
):
5860
"""Create mock webserver for Smile to interface with."""
5961
app = aiohttp.web.Application()
@@ -86,12 +88,17 @@ async def setup_app(
8688
"DELETE", "/core/notifications{tail:.*}", self.smile_del_notification
8789
)
8890
app.router.add_route("PUT", "/core/rules{tail:.*}", self.smile_set_schedule)
89-
app.router.add_route(
90-
"PUT", "/core/appliances{tail:.*}", self.smile_set_relay
91-
)
9291
app.router.add_route(
9392
"DELETE", "/core/notifications{tail:.*}", self.smile_del_notification
9493
)
94+
if not stretch:
95+
app.router.add_route(
96+
"PUT", "/core/appliances{tail:.*}", self.smile_set_relay
97+
)
98+
else:
99+
app.router.add_route(
100+
"PUT", "/core/appliances{tail:.*}", self.smile_set_relay_stretch
101+
)
95102
else:
96103
app.router.add_route("PUT", "/core/locations{tail:.*}", self.smile_timeout)
97104
app.router.add_route("PUT", "/core/rules{tail:.*}", self.smile_timeout)
@@ -176,6 +183,11 @@ async def smile_set_relay(self, request):
176183
text = "<xml />"
177184
raise aiohttp.web.HTTPAccepted(text=text)
178185

186+
async def smile_set_relay_stretch(self, request):
187+
"""Render generic API calling endpoint."""
188+
text = "<xml />"
189+
raise aiohttp.web.HTTPOk(text=text)
190+
179191
async def smile_del_notification(self, request):
180192
"""Render generic API calling endpoint."""
181193
text = "<xml />"
@@ -194,13 +206,18 @@ async def smile_fail_auth(self, request):
194206
raise aiohttp.web.HTTPUnauthorized()
195207

196208
async def connect(
197-
self, broken=False, timeout=False, raise_timeout=False, fail_auth=False
209+
self,
210+
broken=False,
211+
timeout=False,
212+
raise_timeout=False,
213+
fail_auth=False,
214+
stretch=False,
198215
):
199216
"""Connect to a smile environment and perform basic asserts."""
200217
port = aiohttp.test_utils.unused_port()
201218

202219
# Happy flow
203-
app = await self.setup_app(broken, timeout, raise_timeout, fail_auth)
220+
app = await self.setup_app(broken, timeout, raise_timeout, fail_auth, stretch)
204221

205222
server = aiohttp.test_utils.TestServer(
206223
app, port=port, scheme="http", host="127.0.0.1"
@@ -271,7 +288,9 @@ async def connect(
271288
raise exception
272289

273290
# Wrap connect for invalid connections
274-
async def connect_wrapper(self, raise_timeout=False, fail_auth=False):
291+
async def connect_wrapper(
292+
self, raise_timeout=False, fail_auth=False, stretch=False
293+
):
275294
"""Wrap connect to try negative testing before positive testing."""
276295

277296
if fail_auth:
@@ -305,7 +324,7 @@ async def connect_wrapper(self, raise_timeout=False, fail_auth=False):
305324
_LOGGER.info(" + successfully passed XML issue handling.")
306325

307326
_LOGGER.info("Connecting to functioning device:")
308-
return await self.connect()
327+
return await self.connect(stretch=stretch)
309328

310329
# Generic disconnect
311330
@pytest.mark.asyncio
@@ -351,6 +370,7 @@ async def device_test(self, smile=pw_smile.Smile, testdata=None):
351370
pp4 = PrettyPrinter(indent=4)
352371
pp8 = PrettyPrinter(indent=8)
353372
_LOGGER.debug("Device list:\n%s", pp4.pformat(device_list))
373+
await smile.update_device()
354374
for dev_id, details in device_list.items():
355375
data = smile.get_device_data(dev_id)
356376
self._write_json("get_device_data/" + dev_id, data)
@@ -388,29 +408,28 @@ async def device_test(self, smile=pw_smile.Smile, testdata=None):
388408

389409
@pytest.mark.asyncio
390410
async def tinker_switch(
391-
self, smile, dev_ids=None, members=None, model=None, unhappy=False
411+
self, smile, dev_id=None, members=None, model=None, unhappy=False
392412
):
393413
"""Turn a Switch on and off to test functionality."""
394414
_LOGGER.info("Asserting modifying settings for switch devices:")
395-
for dev_id in dev_ids:
396-
_LOGGER.info("- Devices (%s):", dev_id)
397-
for new_state in [False, True, False]:
398-
_LOGGER.info("- Switching %s", new_state)
399-
try:
400-
switch_change = await smile.set_switch_state(
401-
dev_id, members, model, new_state
402-
)
403-
assert switch_change
404-
_LOGGER.info(" + worked as intended")
405-
except (
406-
pw_exceptions.ErrorSendingCommandError,
407-
pw_exceptions.ResponseError,
408-
):
409-
if unhappy:
410-
_LOGGER.info(" + failed as expected")
411-
else: # pragma: no cover
412-
_LOGGER.info(" - failed unexpectedly")
413-
raise self.UnexpectedError
415+
_LOGGER.info("- Devices (%s):", dev_id)
416+
for new_state in [False, True, False]:
417+
_LOGGER.info("- Switching %s", new_state)
418+
try:
419+
switch_change = await smile.set_switch_state(
420+
dev_id, members, model, new_state
421+
)
422+
assert switch_change
423+
_LOGGER.info(" + worked as intended")
424+
except (
425+
pw_exceptions.ErrorSendingCommandError,
426+
pw_exceptions.ResponseError,
427+
):
428+
if unhappy:
429+
_LOGGER.info(" + failed as expected")
430+
else: # pragma: no cover
431+
_LOGGER.info(" - failed unexpectedly")
432+
raise self.UnexpectedError
414433

415434
@pytest.mark.asyncio
416435
async def tinker_thermostat(self, smile, loc_id, good_schemas=None, unhappy=False):
@@ -799,7 +818,7 @@ async def test_connect_anna_without_boiler_fw3(self):
799818
"a270735e4ccd45239424badc0578a2b1": {
800819
"outdoor_temperature": 10.8,
801820
},
802-
## Central
821+
# # Central
803822
# "c46b4794d28149699eacf053deedd003": {
804823
# "heating_state": False,
805824
# },
@@ -858,7 +877,7 @@ async def test_connect_anna_without_boiler_fw4(self):
858877
"a270735e4ccd45239424badc0578a2b1": {
859878
"outdoor_temperature": 16.6,
860879
},
861-
## Central
880+
# # Central
862881
# "c46b4794d28149699eacf053deedd003": {
863882
# "heating_state": True,
864883
# },
@@ -948,7 +967,7 @@ async def test_connect_adam_plus_anna(self):
948967
await self.tinker_thermostat(
949968
smile, "009490cc2f674ce6b576863fbb64f867", good_schemas=["Weekschema"]
950969
)
951-
await self.tinker_switch(smile, ["aa6b0002df0a46e1b1eb94beb61eddfe"])
970+
await self.tinker_switch(smile, "aa6b0002df0a46e1b1eb94beb61eddfe")
952971
await smile.close_connection()
953972
await self.disconnect(server, client)
954973

@@ -960,7 +979,7 @@ async def test_connect_adam_plus_anna(self):
960979
unhappy=True,
961980
)
962981
await self.tinker_switch(
963-
smile, ["aa6b0002df0a46e1b1eb94beb61eddfe"], unhappy=True
982+
smile, "aa6b0002df0a46e1b1eb94beb61eddfe", unhappy=True
964983
)
965984
await smile.close_connection()
966985
await self.disconnect(server, client)
@@ -992,11 +1011,11 @@ async def test_connect_adam_plus_anna_new(self):
9921011

9931012
await self.tinker_switch(
9941013
smile,
995-
["b83f9f9758064c0fab4af6578cba4c6d"],
1014+
"b83f9f9758064c0fab4af6578cba4c6d",
9961015
["aa6b0002df0a46e1b1eb94beb61eddfe", "f2be121e4a9345ac83c6e99ed89a98be"],
9971016
)
9981017
await self.tinker_switch(
999-
smile, ["2743216f626f43948deec1f7ab3b3d70"], model="dhw_cm_switch"
1018+
smile, "2743216f626f43948deec1f7ab3b3d70", model="dhw_cm_switch"
10001019
)
10011020

10021021
await smile.close_connection()
@@ -1068,7 +1087,7 @@ async def test_connect_adam_zone_per_device(self):
10681087
await self.tinker_thermostat(
10691088
smile, "82fa13f017d240daa0d0ea1775420f24", good_schemas=["CV Jessie"]
10701089
)
1071-
await self.tinker_switch(smile, ["675416a629f343c495449970e2ca37b5"])
1090+
await self.tinker_switch(smile, "675416a629f343c495449970e2ca37b5")
10721091
await smile.close_connection()
10731092
await self.disconnect(server, client)
10741093

@@ -1162,7 +1181,7 @@ async def test_connect_adam_multiple_devices_per_zone(self):
11621181
await self.tinker_thermostat(
11631182
smile, "82fa13f017d240daa0d0ea1775420f24", good_schemas=["CV Jessie"]
11641183
)
1165-
await self.tinker_switch(smile, ["675416a629f343c495449970e2ca37b5"])
1184+
await self.tinker_switch(smile, "675416a629f343c495449970e2ca37b5")
11661185
await smile.close_connection()
11671186
await self.disconnect(server, client)
11681187

@@ -1413,7 +1432,7 @@ async def test_connect_stretch_v31(self):
14131432
}
14141433

14151434
self.smile_setup = "stretch_v31"
1416-
server, smile, client = await self.connect_wrapper()
1435+
server, smile, client = await self.connect_wrapper(stretch=True)
14171436
assert smile.smile_hostname == "stretch000000"
14181437

14191438
_LOGGER.info("Basics:")
@@ -1449,7 +1468,7 @@ async def test_connect_stretch_v23(self):
14491468
}
14501469

14511470
self.smile_setup = "stretch_v23"
1452-
server, smile, client = await self.connect_wrapper()
1471+
server, smile, client = await self.connect_wrapper(stretch=True)
14531472
assert smile.smile_hostname == "stretch000000"
14541473

14551474
_LOGGER.info("Basics:")
@@ -1462,7 +1481,12 @@ async def test_connect_stretch_v23(self):
14621481
_LOGGER.info(" # Assert no master thermostat")
14631482
assert smile.single_master_thermostat() is None # it's not a thermostat :)
14641483

1465-
await self.tinker_switch(smile, ["2587a7fcdd7e482dab03fda256076b4b"])
1484+
await self.tinker_switch(smile, "2587a7fcdd7e482dab03fda256076b4b")
1485+
await self.tinker_switch(
1486+
smile,
1487+
"f7b145c8492f4dd7a4de760456fdef3e",
1488+
["407aa1c1099d463c9137a3a9eda787fd"],
1489+
)
14661490

14671491
smile.get_all_devices()
14681492
await self.device_test(smile, testdata)
@@ -1535,6 +1559,17 @@ async def test_connect_fail_firmware(self):
15351559
except pw_exceptions.UnsupportedDeviceError:
15361560
assert True
15371561

1562+
# Test connect for timeout
1563+
@patch("async_timeout.timeout", side_effect=asyncio.exceptions.TimeoutError)
1564+
async def test_connect_timeout(self, timeout_test):
1565+
"""Wrap connect to raise timeout during get."""
1566+
1567+
try:
1568+
await self.connect_wrapper()
1569+
assert False # pragma: no cover
1570+
except pw_exceptions.DeviceTimeoutError:
1571+
assert True
1572+
15381573
class PlugwiseTestError(Exception):
15391574
"""Plugwise test exceptions class."""
15401575

0 commit comments

Comments
 (0)