Provide user consumable errors when lock operations fail (#31864)
* Provide user consumable errors when lock operations fail This resolves issue #26672 * include from in raise * pylint * Cleanup of mocking.
This commit is contained in:
parent
18dfb02355
commit
00ac7a7d70
9 changed files with 194 additions and 93 deletions
|
@ -4,7 +4,7 @@ from datetime import timedelta
|
|||
from functools import partial
|
||||
import logging
|
||||
|
||||
from august.api import Api
|
||||
from august.api import Api, AugustApiHTTPError
|
||||
from august.authenticator import AuthenticationState, Authenticator, ValidationResult
|
||||
from requests import RequestException, Session
|
||||
import voluptuous as vol
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.const import (
|
|||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle, dt
|
||||
|
@ -364,6 +365,12 @@ class AugustData:
|
|||
await self._async_update_locks()
|
||||
return self._lock_detail_by_id.get(lock_id)
|
||||
|
||||
def get_lock_name(self, device_id):
|
||||
"""Return lock name as August has it stored."""
|
||||
for lock in self._locks:
|
||||
if lock.device_id == device_id:
|
||||
return lock.device_name
|
||||
|
||||
async def async_get_door_state(self, lock_id):
|
||||
"""Return status if the door is open or closed.
|
||||
|
||||
|
@ -472,8 +479,33 @@ class AugustData:
|
|||
|
||||
def lock(self, device_id):
|
||||
"""Lock the device."""
|
||||
return self._api.lock(self._access_token, device_id)
|
||||
return _call_api_operation_that_requires_bridge(
|
||||
self.get_lock_name(device_id),
|
||||
"lock",
|
||||
self._api.lock,
|
||||
self._access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
def unlock(self, device_id):
|
||||
"""Unlock the device."""
|
||||
return self._api.unlock(self._access_token, device_id)
|
||||
return _call_api_operation_that_requires_bridge(
|
||||
self.get_lock_name(device_id),
|
||||
"unlock",
|
||||
self._api.unlock,
|
||||
self._access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
|
||||
def _call_api_operation_that_requires_bridge(
|
||||
device_name, operation_name, func, *args, **kwargs
|
||||
):
|
||||
"""Call an API that requires the bridge to be online."""
|
||||
ret = None
|
||||
try:
|
||||
ret = func(*args, **kwargs)
|
||||
except AugustApiHTTPError as err:
|
||||
raise HomeAssistantError(device_name + ": " + str(err))
|
||||
|
||||
return ret
|
||||
|
|
|
@ -56,7 +56,7 @@ async def _async_activity_time_based_state(data, doorbell, activity_types):
|
|||
return None
|
||||
|
||||
|
||||
# Sensor types: Name, device_class, async_state_provider
|
||||
# sensor_type: [name, device_class, async_state_provider]
|
||||
SENSOR_TYPES_DOOR = {"door_open": ["Open", "door", _async_retrieve_door_state]}
|
||||
|
||||
SENSOR_TYPES_DOORBELL = {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "august",
|
||||
"name": "August",
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"requirements": ["py-august==0.12.0"],
|
||||
"requirements": ["py-august==0.14.0"],
|
||||
"dependencies": ["configurator"],
|
||||
"codeowners": ["@bdraco"]
|
||||
}
|
||||
|
|
|
@ -1075,7 +1075,7 @@ pushover_complete==1.1.1
|
|||
pwmled==1.4.1
|
||||
|
||||
# homeassistant.components.august
|
||||
py-august==0.12.0
|
||||
py-august==0.14.0
|
||||
|
||||
# homeassistant.components.canary
|
||||
py-canary==0.5.0
|
||||
|
|
|
@ -391,7 +391,7 @@ pure-python-adb==0.2.2.dev0
|
|||
pushbullet.py==0.11.0
|
||||
|
||||
# homeassistant.components.august
|
||||
py-august==0.12.0
|
||||
py-august==0.14.0
|
||||
|
||||
# homeassistant.components.canary
|
||||
py-canary==0.5.0
|
||||
|
|
|
@ -3,11 +3,32 @@ import datetime
|
|||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from august.activity import Activity
|
||||
from august.api import Api
|
||||
from august.exceptions import AugustApiHTTPError
|
||||
from august.lock import Lock
|
||||
|
||||
from homeassistant.components.august import AugustData
|
||||
from homeassistant.components.august.binary_sensor import AugustDoorBinarySensor
|
||||
from homeassistant.components.august.lock import AugustLock
|
||||
from homeassistant.util import dt
|
||||
|
||||
|
||||
class MockAugustApi(Api):
|
||||
"""A mock for py-august Api class."""
|
||||
|
||||
def _call_api(self, *args, **kwargs):
|
||||
"""Mock the time activity started."""
|
||||
raise AugustApiHTTPError("This should bubble up as its user consumable")
|
||||
|
||||
|
||||
class MockAugustApiFailing(MockAugustApi):
|
||||
"""A mock for py-august Api class that always has an AugustApiHTTPError."""
|
||||
|
||||
def _call_api(self, *args, **kwargs):
|
||||
"""Mock the time activity started."""
|
||||
raise AugustApiHTTPError("This should bubble up as its user consumable")
|
||||
|
||||
|
||||
class MockActivity(Activity):
|
||||
"""A mock for py-august Activity class."""
|
||||
|
||||
|
@ -35,14 +56,48 @@ class MockActivity(Activity):
|
|||
return self._action
|
||||
|
||||
|
||||
class MockAugustData(AugustData):
|
||||
class MockAugustComponentDoorBinarySensor(AugustDoorBinarySensor):
|
||||
"""A mock for august component AugustDoorBinarySensor class."""
|
||||
|
||||
def _update_door_state(self, door_state, activity_start_time_utc):
|
||||
"""Mock updating the lock status."""
|
||||
self._data.set_last_door_state_update_time_utc(
|
||||
self._door.device_id, activity_start_time_utc
|
||||
)
|
||||
self.last_update_door_state = {}
|
||||
self.last_update_door_state["door_state"] = door_state
|
||||
self.last_update_door_state["activity_start_time_utc"] = activity_start_time_utc
|
||||
|
||||
|
||||
class MockAugustComponentLock(AugustLock):
|
||||
"""A mock for august component AugustLock class."""
|
||||
|
||||
def _update_lock_status(self, lock_status, activity_start_time_utc):
|
||||
"""Mock updating the lock status."""
|
||||
self._data.set_last_lock_status_update_time_utc(
|
||||
self._lock.device_id, activity_start_time_utc
|
||||
)
|
||||
self.last_update_lock_status = {}
|
||||
self.last_update_lock_status["lock_status"] = lock_status
|
||||
self.last_update_lock_status[
|
||||
"activity_start_time_utc"
|
||||
] = activity_start_time_utc
|
||||
|
||||
|
||||
class MockAugustComponentData(AugustData):
|
||||
"""A wrapper to mock AugustData."""
|
||||
|
||||
# AugustData support multiple locks, however for the purposes of
|
||||
# mocking we currently only mock one lockid
|
||||
|
||||
def __init__(
|
||||
self, last_lock_status_update_timestamp=1, last_door_state_update_timestamp=1
|
||||
self,
|
||||
last_lock_status_update_timestamp=1,
|
||||
last_door_state_update_timestamp=1,
|
||||
api=MockAugustApi(),
|
||||
access_token="mocked_access_token",
|
||||
locks=[],
|
||||
doorbells=[],
|
||||
):
|
||||
"""Mock AugustData."""
|
||||
self._last_lock_status_update_time_utc = dt.as_utc(
|
||||
|
@ -51,6 +106,20 @@ class MockAugustData(AugustData):
|
|||
self._last_door_state_update_time_utc = dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(last_lock_status_update_timestamp)
|
||||
)
|
||||
self._api = api
|
||||
self._access_token = access_token
|
||||
self._locks = locks
|
||||
self._doorbells = doorbells
|
||||
self._lock_status_by_id = {}
|
||||
self._lock_last_status_update_time_utc_by_id = {}
|
||||
|
||||
def set_mocked_locks(self, locks):
|
||||
"""Set lock mocks."""
|
||||
self._locks = locks
|
||||
|
||||
def set_mocked_doorbells(self, doorbells):
|
||||
"""Set doorbell mocks."""
|
||||
self._doorbells = doorbells
|
||||
|
||||
def get_last_lock_status_update_time_utc(self, device_id):
|
||||
"""Mock to get last lock status update time."""
|
||||
|
@ -69,12 +138,6 @@ class MockAugustData(AugustData):
|
|||
self._last_door_state_update_time_utc = update_time
|
||||
|
||||
|
||||
def _mock_august_lock():
|
||||
lock = MagicMock(name="august.lock")
|
||||
type(lock).device_id = PropertyMock(return_value="lock_device_id_1")
|
||||
return lock
|
||||
|
||||
|
||||
def _mock_august_authenticator():
|
||||
authenticator = MagicMock(name="august.authenticator")
|
||||
authenticator.should_refresh = MagicMock(
|
||||
|
@ -93,3 +156,10 @@ def _mock_august_authentication(token_text, token_timestamp):
|
|||
return_value=token_timestamp
|
||||
)
|
||||
return authentication
|
||||
|
||||
|
||||
def _mock_august_lock():
|
||||
return Lock(
|
||||
"mockdeviceid1",
|
||||
{"LockName": "Mocked Lock 1", "HouseID": "mockhouseid1", "UserType": "owner"},
|
||||
)
|
||||
|
|
|
@ -1,54 +1,26 @@
|
|||
"""The lock tests for the august platform."""
|
||||
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from august.activity import ACTION_DOOR_CLOSED, ACTION_DOOR_OPEN
|
||||
from august.lock import LockDoorStatus
|
||||
|
||||
from homeassistant.components.august.binary_sensor import AugustDoorBinarySensor
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.components.august.mocks import (
|
||||
MockActivity,
|
||||
MockAugustData,
|
||||
MockAugustComponentData,
|
||||
MockAugustComponentDoorBinarySensor,
|
||||
_mock_august_lock,
|
||||
)
|
||||
|
||||
|
||||
class MockAugustDoorBinarySensor(AugustDoorBinarySensor):
|
||||
"""A mock for august component AugustLock class."""
|
||||
|
||||
def __init__(self, august_data=None):
|
||||
"""Init the mock for august component AugustLock class."""
|
||||
self._data = august_data
|
||||
self._door = _mock_august_lock()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Mock name."""
|
||||
return "mockedname1"
|
||||
|
||||
@property
|
||||
def device_id(self):
|
||||
"""Mock device_id."""
|
||||
return "mockdeviceid1"
|
||||
|
||||
def _update_door_state(self, door_state, activity_start_time_utc):
|
||||
"""Mock updating the lock status."""
|
||||
self._data.set_last_door_state_update_time_utc(
|
||||
self._door.device_id, activity_start_time_utc
|
||||
)
|
||||
self.last_update_door_state = {}
|
||||
self.last_update_door_state["door_state"] = door_state
|
||||
self.last_update_door_state["activity_start_time_utc"] = activity_start_time_utc
|
||||
return MagicMock()
|
||||
|
||||
|
||||
def test__sync_door_activity_doored_via_dooropen():
|
||||
"""Test _sync_door_activity dooropen."""
|
||||
data = MockAugustData(last_door_state_update_timestamp=1)
|
||||
door = MockAugustDoorBinarySensor(august_data=data)
|
||||
data = MockAugustComponentData(last_door_state_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
door = MockAugustComponentDoorBinarySensor(data, "door_open", lock)
|
||||
door_activity_start_timestamp = 1234
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_OPEN,
|
||||
|
@ -64,8 +36,10 @@ def test__sync_door_activity_doored_via_dooropen():
|
|||
|
||||
def test__sync_door_activity_doorclosed():
|
||||
"""Test _sync_door_activity doorclosed."""
|
||||
data = MockAugustData(last_door_state_update_timestamp=1)
|
||||
door = MockAugustDoorBinarySensor(august_data=data)
|
||||
data = MockAugustComponentData(last_door_state_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
door = MockAugustComponentDoorBinarySensor(data, "door_open", lock)
|
||||
door_activity_timestamp = 1234
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_CLOSED,
|
||||
|
@ -81,8 +55,10 @@ def test__sync_door_activity_doorclosed():
|
|||
|
||||
def test__sync_door_activity_ignores_old_data():
|
||||
"""Test _sync_door_activity dooropen then expired doorclosed."""
|
||||
data = MockAugustData(last_door_state_update_timestamp=1)
|
||||
door = MockAugustDoorBinarySensor(august_data=data)
|
||||
data = MockAugustComponentData(last_door_state_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
door = MockAugustComponentDoorBinarySensor(data, "door_open", lock)
|
||||
first_door_activity_timestamp = 1234
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_OPEN,
|
||||
|
@ -98,7 +74,7 @@ def test__sync_door_activity_ignores_old_data():
|
|||
# Now we do the update with an older start time to
|
||||
# make sure it ignored
|
||||
data.set_last_door_state_update_time_utc(
|
||||
door.device_id, dt.as_utc(datetime.datetime.fromtimestamp(1000))
|
||||
lock.device_id, dt.as_utc(datetime.datetime.fromtimestamp(1000))
|
||||
)
|
||||
door_activity_timestamp = 2
|
||||
door_activity = MockActivity(
|
||||
|
|
|
@ -3,15 +3,57 @@ import asyncio
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
from homeassistant.components import august
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from tests.components.august.mocks import (
|
||||
MockAugustApiFailing,
|
||||
MockAugustComponentData,
|
||||
_mock_august_authentication,
|
||||
_mock_august_authenticator,
|
||||
_mock_august_lock,
|
||||
)
|
||||
|
||||
|
||||
def test_get_lock_name():
|
||||
"""Get the lock name from August data."""
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
assert data.get_lock_name("mockdeviceid1") == "Mocked Lock 1"
|
||||
|
||||
|
||||
def test_unlock_throws_august_api_http_error():
|
||||
"""Test unlock."""
|
||||
data = MockAugustComponentData(api=MockAugustApiFailing())
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
last_err = None
|
||||
try:
|
||||
data.unlock("mockdeviceid1")
|
||||
except HomeAssistantError as err:
|
||||
last_err = err
|
||||
assert (
|
||||
str(last_err) == "Mocked Lock 1: This should bubble up as its user consumable"
|
||||
)
|
||||
|
||||
|
||||
def test_lock_throws_august_api_http_error():
|
||||
"""Test lock."""
|
||||
data = MockAugustComponentData(api=MockAugustApiFailing())
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
last_err = None
|
||||
try:
|
||||
data.unlock("mockdeviceid1")
|
||||
except HomeAssistantError as err:
|
||||
last_err = err
|
||||
assert (
|
||||
str(last_err) == "Mocked Lock 1: This should bubble up as its user consumable"
|
||||
)
|
||||
|
||||
|
||||
async def test__refresh_access_token(hass):
|
||||
"""Set up things to be run when tests are started."""
|
||||
"""Test refresh of the access token."""
|
||||
authentication = _mock_august_authentication("original_token", 1234)
|
||||
authenticator = _mock_august_authenticator()
|
||||
token_refresh_lock = asyncio.Lock()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"""The lock tests for the august platform."""
|
||||
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from august.activity import (
|
||||
ACTION_LOCK_LOCK,
|
||||
|
@ -10,46 +9,22 @@ from august.activity import (
|
|||
)
|
||||
from august.lock import LockStatus
|
||||
|
||||
from homeassistant.components.august.lock import AugustLock
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.components.august.mocks import (
|
||||
MockActivity,
|
||||
MockAugustData,
|
||||
MockAugustComponentData,
|
||||
MockAugustComponentLock,
|
||||
_mock_august_lock,
|
||||
)
|
||||
|
||||
|
||||
class MockAugustLock(AugustLock):
|
||||
"""A mock for august component AugustLock class."""
|
||||
|
||||
def __init__(self, august_data=None):
|
||||
"""Init the mock for august component AugustLock class."""
|
||||
self._data = august_data
|
||||
self._lock = _mock_august_lock()
|
||||
|
||||
@property
|
||||
def device_id(self):
|
||||
"""Mock device_id."""
|
||||
return "mockdeviceid1"
|
||||
|
||||
def _update_lock_status(self, lock_status, activity_start_time_utc):
|
||||
"""Mock updating the lock status."""
|
||||
self._data.set_last_lock_status_update_time_utc(
|
||||
self._lock.device_id, activity_start_time_utc
|
||||
)
|
||||
self.last_update_lock_status = {}
|
||||
self.last_update_lock_status["lock_status"] = lock_status
|
||||
self.last_update_lock_status[
|
||||
"activity_start_time_utc"
|
||||
] = activity_start_time_utc
|
||||
return MagicMock()
|
||||
|
||||
|
||||
def test__sync_lock_activity_locked_via_onetouchlock():
|
||||
"""Test _sync_lock_activity locking."""
|
||||
data = MockAugustData(last_lock_status_update_timestamp=1)
|
||||
lock = MockAugustLock(august_data=data)
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
august_lock = _mock_august_lock()
|
||||
data.set_mocked_locks([august_lock])
|
||||
lock = MockAugustComponentLock(data, august_lock)
|
||||
lock_activity_start_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_ONETOUCHLOCK,
|
||||
|
@ -65,8 +40,10 @@ def test__sync_lock_activity_locked_via_onetouchlock():
|
|||
|
||||
def test__sync_lock_activity_locked_via_lock():
|
||||
"""Test _sync_lock_activity locking."""
|
||||
data = MockAugustData(last_lock_status_update_timestamp=1)
|
||||
lock = MockAugustLock(august_data=data)
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
august_lock = _mock_august_lock()
|
||||
data.set_mocked_locks([august_lock])
|
||||
lock = MockAugustComponentLock(data, august_lock)
|
||||
lock_activity_start_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_LOCK,
|
||||
|
@ -82,8 +59,10 @@ def test__sync_lock_activity_locked_via_lock():
|
|||
|
||||
def test__sync_lock_activity_unlocked():
|
||||
"""Test _sync_lock_activity unlocking."""
|
||||
data = MockAugustData(last_lock_status_update_timestamp=1)
|
||||
lock = MockAugustLock(august_data=data)
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
august_lock = _mock_august_lock()
|
||||
data.set_mocked_locks([august_lock])
|
||||
lock = MockAugustComponentLock(data, august_lock)
|
||||
lock_activity_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_UNLOCK,
|
||||
|
@ -99,8 +78,10 @@ def test__sync_lock_activity_unlocked():
|
|||
|
||||
def test__sync_lock_activity_ignores_old_data():
|
||||
"""Test _sync_lock_activity unlocking."""
|
||||
data = MockAugustData(last_lock_status_update_timestamp=1)
|
||||
lock = MockAugustLock(august_data=data)
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
august_lock = _mock_august_lock()
|
||||
data.set_mocked_locks([august_lock])
|
||||
lock = MockAugustComponentLock(data, august_lock)
|
||||
first_lock_activity_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_UNLOCK,
|
||||
|
@ -116,7 +97,7 @@ def test__sync_lock_activity_ignores_old_data():
|
|||
# Now we do the update with an older start time to
|
||||
# make sure it ignored
|
||||
data.set_last_lock_status_update_time_utc(
|
||||
lock.device_id, dt.as_utc(datetime.datetime.fromtimestamp(1000))
|
||||
august_lock.device_id, dt.as_utc(datetime.datetime.fromtimestamp(1000))
|
||||
)
|
||||
lock_activity_timestamp = 2
|
||||
lock_activity = MockActivity(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue