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
This commit is contained in:
David Winn 2019-07-06 23:40:02 -07:00 committed by Pascal Vizeli
parent adbec5bffc
commit 628e12c944
11 changed files with 103 additions and 18 deletions

View File

@ -1,7 +1,6 @@
"""Support for SleepIQ from SleepNumber.""" """Support for SleepIQ from SleepNumber."""
import logging import logging
from datetime import timedelta from datetime import timedelta
from requests.exceptions import HTTPError
import voluptuous as vol import voluptuous as vol
@ -53,7 +52,7 @@ def setup(hass, config):
try: try:
DATA = SleepIQData(client) DATA = SleepIQData(client)
DATA.update() DATA.update()
except HTTPError: except ValueError:
message = """ message = """
SleepIQ failed to login, double check your username and password" SleepIQ failed to login, double check your username and password"
""" """

View File

@ -12,9 +12,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data.update() data.update()
dev = list() dev = list()
for bed_id, _ in data.beds.items(): for bed_id, bed in data.beds.items():
for side in sleepiq.SIDES: 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) add_entities(dev)

View File

@ -3,7 +3,7 @@
"name": "Sleepiq", "name": "Sleepiq",
"documentation": "https://www.home-assistant.io/components/sleepiq", "documentation": "https://www.home-assistant.io/components/sleepiq",
"requirements": [ "requirements": [
"sleepyq==0.6" "sleepyq==0.7"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [] "codeowners": []

View File

@ -13,9 +13,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data.update() data.update()
dev = list() dev = list()
for bed_id, _ in data.beds.items(): for bed_id, bed in data.beds.items():
for side in sleepiq.SIDES: 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) add_entities(dev)

View File

@ -1671,7 +1671,7 @@ skybellpy==0.4.0
slacker==0.13.0 slacker==0.13.0
# homeassistant.components.sleepiq # homeassistant.components.sleepiq
sleepyq==0.6 sleepyq==0.7
# homeassistant.components.xmpp # homeassistant.components.xmpp
slixmpp==1.4.2 slixmpp==1.4.2

View File

@ -332,7 +332,7 @@ rxv==0.6.0
simplisafe-python==3.4.2 simplisafe-python==3.4.2
# homeassistant.components.sleepiq # homeassistant.components.sleepiq
sleepyq==0.6 sleepyq==0.7
# homeassistant.components.smhi # homeassistant.components.smhi
smhi-pkg==1.0.10 smhi-pkg==1.0.10

View File

@ -30,6 +30,7 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase):
'username': self.username, 'username': self.username,
'password': self.password, 'password': self.password,
} }
self.DEVICES = []
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started.""" """Stop everything that was started."""
@ -56,3 +57,21 @@ class TestSleepIQBinarySensorSetup(unittest.TestCase):
right_side = self.DEVICES[0] right_side = self.DEVICES[0]
assert 'SleepNumber ILE Test2 Is In Bed' == right_side.name assert 'SleepNumber ILE Test2 Is In Bed' == right_side.name
assert 'off' == right_side.state 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

View File

@ -10,21 +10,25 @@ import homeassistant.components.sleepiq as sleepiq
from tests.common import load_fixture, get_test_home_assistant from tests.common import load_fixture, get_test_home_assistant
def mock_responses(mock): def mock_responses(mock, single=False):
"""Mock responses for SleepIQ.""" """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( mock.put(
base_url + 'login', base_url + 'login',
text=load_fixture('sleepiq-login.json')) text=load_fixture('sleepiq-login.json'))
mock.get( mock.get(
base_url + 'bed?_k=0987', base_url + 'bed?_k=0987',
text=load_fixture('sleepiq-bed.json')) text=load_fixture('sleepiq-bed{}.json'.format(suffix)))
mock.get( mock.get(
base_url + 'sleeper?_k=0987', base_url + 'sleeper?_k=0987',
text=load_fixture('sleepiq-sleeper.json')) text=load_fixture('sleepiq-sleeper.json'))
mock.get( mock.get(
base_url + 'bed/familyStatus?_k=0987', base_url + 'bed/familyStatus?_k=0987',
text=load_fixture('sleepiq-familystatus.json')) text=load_fixture('sleepiq-familystatus{}.json'.format(suffix)))
class TestSleepIQ(unittest.TestCase): class TestSleepIQ(unittest.TestCase):
@ -61,7 +65,7 @@ class TestSleepIQ(unittest.TestCase):
@requests_mock.Mocker() @requests_mock.Mocker()
def test_setup_login_failed(self, mock): def test_setup_login_failed(self, mock):
"""Test the setup if a bad username or password is given.""" """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, status_code=401,
json=load_fixture('sleepiq-login-failed.json')) json=load_fixture('sleepiq-login-failed.json'))

View File

@ -30,6 +30,7 @@ class TestSleepIQSensorSetup(unittest.TestCase):
'username': self.username, 'username': self.username,
'password': self.password, 'password': self.password,
} }
self.DEVICES = []
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
"""Stop everything that was started.""" """Stop everything that was started."""
@ -41,10 +42,7 @@ class TestSleepIQSensorSetup(unittest.TestCase):
mock_responses(mock) mock_responses(mock)
assert setup_component(self.hass, 'sleepiq', { assert setup_component(self.hass, 'sleepiq', {
'sleepiq': { 'sleepiq': self.config
'username': '',
'password': '',
}
}) })
sleepiq.setup_platform(self.hass, sleepiq.setup_platform(self.hass,
@ -60,3 +58,22 @@ class TestSleepIQSensorSetup(unittest.TestCase):
right_side = self.DEVICES[0] right_side = self.DEVICES[0]
assert 'SleepNumber ILE Test2 SleepNumber' == right_side.name assert 'SleepNumber ILE Test2 SleepNumber' == right_side.name
assert 80 == right_side.state 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
tests/fixtures/sleepiq-bed-single.json vendored Normal file
View File

@ -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"
}
]
}

View File

@ -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
}
]
}