From 628e12c94448c24bdc5e95dd7482d8ec52c4ec5b Mon Sep 17 00:00:00 2001 From: David Winn Date: Sat, 6 Jul 2019 23:40:02 -0700 Subject: [PATCH] Sleepiq single sleeper crash (#24941) * Update sleepyq to 0.7 Fixes crash when working with a single sleeper. * sleepiq: Handle null side definitions These happen if no sleeper is defined for a side of the bed. Don't create sensors for null sides; they'll crash every time we try to use them. * sleepiq: Fix urls mocked to match sleepyq 0.7 * sleepi: Fix test_sensor.TestSleepIQSensorSetup Sleepyq 0.7 throws on empty strings, so we have to specify them. * sleepiq: Test for ValueError thrown by sleepyq 0.7 * sleepiq: Drop no longer used HTTPError import * sleepiq: Add tests for single sleeper case * sleepiq: Shorten comments to not overflow line length * sleepiq: Use formatted string literals for adding suffixes to test files * sleepiq: Use str.format() for test suffixing --- homeassistant/components/sleepiq/__init__.py | 3 +-- .../components/sleepiq/binary_sensor.py | 5 ++-- .../components/sleepiq/manifest.json | 2 +- homeassistant/components/sleepiq/sensor.py | 5 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/sleepiq/test_binary_sensor.py | 19 +++++++++++++ tests/components/sleepiq/test_init.py | 14 ++++++---- tests/components/sleepiq/test_sensor.py | 25 ++++++++++++++--- tests/fixtures/sleepiq-bed-single.json | 27 +++++++++++++++++++ .../fixtures/sleepiq-familystatus-single.json | 17 ++++++++++++ 11 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 tests/fixtures/sleepiq-bed-single.json create mode 100644 tests/fixtures/sleepiq-familystatus-single.json diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index 97b2d53a033..ac74ea583c5 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,7 +1,6 @@ """Support for SleepIQ from SleepNumber.""" import logging from datetime import timedelta -from requests.exceptions import HTTPError import voluptuous as vol @@ -53,7 +52,7 @@ def setup(hass, config): try: DATA = SleepIQData(client) DATA.update() - except HTTPError: + except ValueError: message = """ SleepIQ failed to login, double check your username and password" """ diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index cad5c9e42f1..b9278fab278 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -12,9 +12,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data.update() dev = list() - for bed_id, _ in data.beds.items(): + for bed_id, bed in data.beds.items(): for side in sleepiq.SIDES: - dev.append(IsInBedBinarySensor(data, bed_id, side)) + if getattr(bed, side) is not None: + dev.append(IsInBedBinarySensor(data, bed_id, side)) add_entities(dev) diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index 339685d32e1..ea16d626af4 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -3,7 +3,7 @@ "name": "Sleepiq", "documentation": "https://www.home-assistant.io/components/sleepiq", "requirements": [ - "sleepyq==0.6" + "sleepyq==0.7" ], "dependencies": [], "codeowners": [] diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index c92c463ea24..502ff2c268a 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -13,9 +13,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data.update() dev = list() - for bed_id, _ in data.beds.items(): + for bed_id, bed in data.beds.items(): for side in sleepiq.SIDES: - dev.append(SleepNumberSensor(data, bed_id, side)) + if getattr(bed, side) is not None: + dev.append(SleepNumberSensor(data, bed_id, side)) add_entities(dev) diff --git a/requirements_all.txt b/requirements_all.txt index 96aa7fc9973..d3eccacfd70 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1671,7 +1671,7 @@ skybellpy==0.4.0 slacker==0.13.0 # homeassistant.components.sleepiq -sleepyq==0.6 +sleepyq==0.7 # homeassistant.components.xmpp slixmpp==1.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c44bdf7c17..4c7ef489823 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -332,7 +332,7 @@ rxv==0.6.0 simplisafe-python==3.4.2 # homeassistant.components.sleepiq -sleepyq==0.6 +sleepyq==0.7 # homeassistant.components.smhi smhi-pkg==1.0.10 diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index 66748f1379c..3b4cd753e69 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -30,6 +30,7 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase): 'username': self.username, 'password': self.password, } + self.DEVICES = [] def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" @@ -56,3 +57,21 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase): right_side = self.DEVICES[0] assert 'SleepNumber ILE Test2 Is In Bed' == right_side.name assert 'off' == right_side.state + + @requests_mock.Mocker() + def test_setup_single(self, mock): + """Test for successfully setting up the SleepIQ platform.""" + mock_responses(mock, single=True) + + setup_component(self.hass, 'sleepiq', { + 'sleepiq': self.config}) + + sleepiq.setup_platform(self.hass, + self.config, + self.add_entities, + MagicMock()) + assert 1 == len(self.DEVICES) + + right_side = self.DEVICES[0] + assert 'SleepNumber ILE Test1 Is In Bed' == right_side.name + assert 'on' == right_side.state diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index d3235cbd8b9..7958da8827a 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -10,21 +10,25 @@ import homeassistant.components.sleepiq as sleepiq from tests.common import load_fixture, get_test_home_assistant -def mock_responses(mock): +def mock_responses(mock, single=False): """Mock responses for SleepIQ.""" - base_url = 'https://api.sleepiq.sleepnumber.com/rest/' + base_url = 'https://prod-api.sleepiq.sleepnumber.com/rest/' + if single: + suffix = '-single' + else: + suffix = '' mock.put( base_url + 'login', text=load_fixture('sleepiq-login.json')) mock.get( base_url + 'bed?_k=0987', - text=load_fixture('sleepiq-bed.json')) + text=load_fixture('sleepiq-bed{}.json'.format(suffix))) mock.get( base_url + 'sleeper?_k=0987', text=load_fixture('sleepiq-sleeper.json')) mock.get( base_url + 'bed/familyStatus?_k=0987', - text=load_fixture('sleepiq-familystatus.json')) + text=load_fixture('sleepiq-familystatus{}.json'.format(suffix))) class TestSleepIQ(unittest.TestCase): @@ -61,7 +65,7 @@ class TestSleepIQ(unittest.TestCase): @requests_mock.Mocker() def test_setup_login_failed(self, mock): """Test the setup if a bad username or password is given.""" - mock.put('https://api.sleepiq.sleepnumber.com/rest/login', + mock.put('https://prod-api.sleepiq.sleepnumber.com/rest/login', status_code=401, json=load_fixture('sleepiq-login-failed.json')) diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index 8b5c039011f..d692f054e2f 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -30,6 +30,7 @@ class TestSleepIQSensorSetup(unittest.TestCase): 'username': self.username, 'password': self.password, } + self.DEVICES = [] def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" @@ -41,10 +42,7 @@ class TestSleepIQSensorSetup(unittest.TestCase): mock_responses(mock) assert setup_component(self.hass, 'sleepiq', { - 'sleepiq': { - 'username': '', - 'password': '', - } + 'sleepiq': self.config }) sleepiq.setup_platform(self.hass, @@ -60,3 +58,22 @@ class TestSleepIQSensorSetup(unittest.TestCase): right_side = self.DEVICES[0] assert 'SleepNumber ILE Test2 SleepNumber' == right_side.name assert 80 == right_side.state + + @requests_mock.Mocker() + def test_setup_sigle(self, mock): + """Test for successfully setting up the SleepIQ platform.""" + mock_responses(mock, single=True) + + assert setup_component(self.hass, 'sleepiq', { + 'sleepiq': self.config + }) + + sleepiq.setup_platform(self.hass, + self.config, + self.add_entities, + MagicMock()) + assert 1 == len(self.DEVICES) + + right_side = self.DEVICES[0] + assert 'SleepNumber ILE Test1 SleepNumber' == right_side.name + assert 40 == right_side.state diff --git a/tests/fixtures/sleepiq-bed-single.json b/tests/fixtures/sleepiq-bed-single.json new file mode 100644 index 00000000000..512f36c0e6a --- /dev/null +++ b/tests/fixtures/sleepiq-bed-single.json @@ -0,0 +1,27 @@ +{ + "beds" : [ + { + "dualSleep" : false, + "base" : "FlexFit", + "sku" : "AILE", + "model" : "ILE", + "size" : "KING", + "isKidsBed" : false, + "sleeperRightId" : "-80", + "accountId" : "-32", + "bedId" : "-31", + "registrationDate" : "2016-07-22T14:00:58Z", + "serial" : null, + "reference" : "95000794555-1", + "macAddress" : "CD13A384BA51", + "version" : null, + "purchaseDate" : "2016-06-22T00:00:00Z", + "sleeperLeftId" : "0", + "zipcode" : "12345", + "returnRequestStatus" : 0, + "name" : "ILE", + "status" : 1, + "timezone" : "US/Eastern" + } + ] +} diff --git a/tests/fixtures/sleepiq-familystatus-single.json b/tests/fixtures/sleepiq-familystatus-single.json new file mode 100644 index 00000000000..08c9569c4dc --- /dev/null +++ b/tests/fixtures/sleepiq-familystatus-single.json @@ -0,0 +1,17 @@ +{ + "beds" : [ + { + "bedId" : "-31", + "rightSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "isInBed" : true, + "sleepNumber" : 40, + "alertDetailedMessage" : "No Alert", + "pressure" : -16 + }, + "status" : 1, + "leftSide" : null + } + ] +}