Skip to content

Commit

Permalink
Sleepiq single sleeper crash (#24941)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
qypea authored and pvizeli committed Jul 7, 2019
1 parent adbec5b commit 628e12c
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 18 deletions.
3 changes: 1 addition & 2 deletions 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

Expand Down Expand Up @@ -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"
"""
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/sleepiq/binary_sensor.py
Expand Up @@ -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)


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/sleepiq/manifest.json
Expand Up @@ -3,7 +3,7 @@
"name": "Sleepiq",
"documentation": "https://www.home-assistant.io/components/sleepiq",
"requirements": [
"sleepyq==0.6"
"sleepyq==0.7"
],
"dependencies": [],
"codeowners": []
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/sleepiq/sensor.py
Expand Up @@ -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)


Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/components/sleepiq/test_binary_sensor.py
Expand Up @@ -30,6 +30,7 @@ def setUp(self):
'username': self.username,
'password': self.password,
}
self.DEVICES = []

def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
Expand All @@ -56,3 +57,21 @@ def test_setup(self, mock):
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
14 changes: 9 additions & 5 deletions tests/components/sleepiq/test_init.py
Expand Up @@ -10,21 +10,25 @@
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):
Expand Down Expand Up @@ -61,7 +65,7 @@ def test_setup(self, mock):
@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'))

Expand Down
25 changes: 21 additions & 4 deletions tests/components/sleepiq/test_sensor.py
Expand Up @@ -30,6 +30,7 @@ def setUp(self):
'username': self.username,
'password': self.password,
}
self.DEVICES = []

def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started."""
Expand All @@ -41,10 +42,7 @@ def test_setup(self, mock):
mock_responses(mock)

assert setup_component(self.hass, 'sleepiq', {
'sleepiq': {
'username': '',
'password': '',
}
'sleepiq': self.config
})

sleepiq.setup_platform(self.hass,
Expand All @@ -60,3 +58,22 @@ def test_setup(self, mock):
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
27 changes: 27 additions & 0 deletions 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"
}
]
}
17 changes: 17 additions & 0 deletions 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
}
]
}

0 comments on commit 628e12c

Please sign in to comment.