diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 29a268f4dd7..5d27efa285e 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -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 diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 07cc2508ce8..4a4f0ece39b 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -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 = { diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 9b4cceccd42..7afa742f3ca 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -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"] } diff --git a/requirements_all.txt b/requirements_all.txt index b8a25ef5165..abb413a874b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2d0a5bc0098..04e603409f0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 7f2f9e0882b..0266f6fb5bf 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -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"}, + ) diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 415ce26076a..0fbd120ea8b 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -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( diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index 77e6ca41b4e..cf2555e67a5 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -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() diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index 6d33f44e4b7..d63a861fa28 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -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(