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.
pull/19747/head
Andrew Hayworth 2019-01-03 07:41:18 -06:00 committed by Fabian Affolter
parent 688bdc6532
commit 87a0118082
2 changed files with 26 additions and 2 deletions

View File

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

View File

@ -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."""