From 87a0118082928cad9c0ea5655945bd36f7b48717 Mon Sep 17 00:00:00 2001 From: Andrew Hayworth Date: Thu, 3 Jan 2019 07:41:18 -0600 Subject: [PATCH] Do not choke on no awair data (#19708) * awair: do not choke on no data The awair API returns an empty response for various air data queries when a device is offline. The underlying library (python_awair) does not directly inform us that a device is offline, since we really can only infer it from an empty response - there is no online/offline indicator in the graphql API. So - we should just ensure that we do not attempt to update device state from an empty response. This ensures that the platform does not crash when starting up with offline devices, and also ensures that the platform is marked unavailable once devices go offline. * Fix typo Further proof that coding after 10pm is rolling the dice. --- homeassistant/components/sensor/awair.py | 4 ++++ tests/components/sensor/test_awair.py | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/awair.py b/homeassistant/components/sensor/awair.py index bce0acb5141..9a45cb66a86 100644 --- a/homeassistant/components/sensor/awair.py +++ b/homeassistant/components/sensor/awair.py @@ -215,6 +215,10 @@ class AwairData: async def _async_update(self): """Get the data from Awair API.""" resp = await self._client.air_data_latest(self._uuid) + + if not resp: + return + timestamp = dt.parse_datetime(resp[0][ATTR_TIMESTAMP]) self.attrs[ATTR_LAST_API_UPDATE] = timestamp self.data[ATTR_SCORE] = resp[0][ATTR_SCORE] diff --git a/tests/components/sensor/test_awair.py b/tests/components/sensor/test_awair.py index d3de2abb121..e00cc816518 100644 --- a/tests/components/sensor/test_awair.py +++ b/tests/components/sensor/test_awair.py @@ -45,6 +45,7 @@ AIR_DATA_FIXTURE_UPDATED = json.loads( load_fixture("awair_air_data_latest_updated.json") ) AIR_DATA_FIXTURE_UPDATED[0][ATTR_TIMESTAMP] = str(NOW + timedelta(minutes=5)) +AIR_DATA_FIXTURE_EMPTY = [] @contextmanager @@ -60,13 +61,13 @@ def alter_time(retval): yield -async def setup_awair(hass, config=None): +async def setup_awair(hass, config=None, data_fixture=AIR_DATA_FIXTURE): """Load the Awair platform.""" devices_json = json.loads(load_fixture("awair_devices.json")) devices_mock = mock_coro(devices_json) devices_patch = patch( "python_awair.AwairClient.devices", return_value=devices_mock) - air_data_mock = mock_coro(AIR_DATA_FIXTURE) + air_data_mock = mock_coro(data_fixture) air_data_patch = patch( "python_awair.AwairClient.air_data_latest", return_value=air_data_mock ) @@ -136,6 +137,12 @@ async def test_bad_platform_setup(hass): assert not hass.states.async_all() +async def test_awair_setup_no_data(hass): + """Ensure that we do not crash during setup when no data is returned.""" + await setup_awair(hass, data_fixture=AIR_DATA_FIXTURE_EMPTY) + assert not hass.states.async_all() + + async def test_awair_misc_attributes(hass): """Test that desired attributes are set.""" await setup_awair(hass) @@ -252,6 +259,19 @@ async def test_availability(hass): assert hass.states.get("sensor.awair_score").state == "79" + future = NOW + timedelta(minutes=90) + fixture = AIR_DATA_FIXTURE_EMPTY + data_patch = patch( + "python_awair.AwairClient.air_data_latest", + return_value=mock_coro(fixture) + ) + + with data_patch, alter_time(future): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert hass.states.get("sensor.awair_score").state == STATE_UNAVAILABLE + async def test_async_update(hass): """Ensure we can update sensors."""