diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 0984eb0629f..10b3e98bb2e 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta import logging -from august.activity import ActivityType +from august.activity import ACTIVITY_ACTION_STATES, ActivityType from august.lock import LockDoorStatus from homeassistant.components.binary_sensor import BinarySensorDevice @@ -138,12 +138,12 @@ class AugustDoorBinarySensor(BinarySensorDevice): self._state = self._state == LockDoorStatus.OPEN - activity = self._data.get_latest_device_activity( + door_activity = self._data.get_latest_device_activity( self._door.device_id, ActivityType.DOOR_OPERATION ) - if activity is not None: - self._sync_door_activity(activity) + if door_activity is not None: + self._sync_door_activity(door_activity) def _update_door_state(self, door_state, update_start_time): new_state = door_state == LockDoorStatus.OPEN @@ -153,7 +153,7 @@ class AugustDoorBinarySensor(BinarySensorDevice): self._door.device_id, door_state, update_start_time ) - def _sync_door_activity(self, activity): + def _sync_door_activity(self, door_activity): """Check the activity for the latest door open/close activity (events). We use this to determine the door state in between calls to the lock @@ -162,25 +162,26 @@ class AugustDoorBinarySensor(BinarySensorDevice): last_door_state_update_time_utc = self._data.get_last_door_state_update_time_utc( self._door.device_id ) - activity_end_time_utc = dt.as_utc(activity.activity_end_time) + activity_end_time_utc = dt.as_utc(door_activity.activity_end_time) if activity_end_time_utc > last_door_state_update_time_utc: _LOGGER.debug( "The activity log has new events for %s: [action=%s] [activity_end_time_utc=%s] > [last_door_state_update_time_utc=%s]", self.name, - activity.action, + door_activity.action, activity_end_time_utc, last_door_state_update_time_utc, ) - activity_start_time_utc = dt.as_utc(activity.activity_start_time) - if activity.action == "doorclosed": - self._update_door_state(LockDoorStatus.CLOSED, activity_start_time_utc) - elif activity.action == "dooropen": - self._update_door_state(LockDoorStatus.OPEN, activity_start_time_utc) + activity_start_time_utc = dt.as_utc(door_activity.activity_start_time) + if door_activity.action in ACTIVITY_ACTION_STATES: + self._update_door_state( + ACTIVITY_ACTION_STATES[door_activity.action], + activity_start_time_utc, + ) else: _LOGGER.info( "Unhandled door activity action %s for %s", - activity.action, + door_activity.action, self.name, ) diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index 885ee444c6b..aa23d9b7874 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -22,10 +22,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class AugustCamera(Camera): - """An implementation of a Canary security camera.""" + """An implementation of a August security camera.""" def __init__(self, data, doorbell, timeout): - """Initialize a Canary security camera.""" + """Initialize a August security camera.""" super().__init__() self._data = data self._doorbell = doorbell diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 908e20e68ca..4e9f5191b2c 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from august.activity import ActivityType +from august.activity import ACTIVITY_ACTION_STATES, ActivityType from august.lock import LockStatus from homeassistant.components.lock import LockDevice @@ -67,15 +67,15 @@ class AugustLock(LockDevice): self._lock_detail = self._data.get_lock_detail(self._lock.device_id) - activity = self._data.get_latest_device_activity( + lock_activity = self._data.get_latest_device_activity( self._lock.device_id, ActivityType.LOCK_OPERATION ) - if activity is not None: - self._changed_by = activity.operated_by - self._sync_lock_activity(activity) + if lock_activity is not None: + self._changed_by = lock_activity.operated_by + self._sync_lock_activity(lock_activity) - def _sync_lock_activity(self, activity): + def _sync_lock_activity(self, lock_activity): """Check the activity for the latest lock/unlock activity (events). We use this to determine the lock state in between calls to the lock @@ -84,25 +84,26 @@ class AugustLock(LockDevice): last_lock_status_update_time_utc = self._data.get_last_lock_status_update_time_utc( self._lock.device_id ) - activity_end_time_utc = dt.as_utc(activity.activity_end_time) + activity_end_time_utc = dt.as_utc(lock_activity.activity_end_time) if activity_end_time_utc > last_lock_status_update_time_utc: _LOGGER.debug( "The activity log has new events for %s: [action=%s] [activity_end_time_utc=%s] > [last_lock_status_update_time_utc=%s]", self.name, - activity.action, + lock_activity.action, activity_end_time_utc, last_lock_status_update_time_utc, ) - activity_start_time_utc = dt.as_utc(activity.activity_start_time) - if activity.action == "lock" or activity.action == "onetouchlock": - self._update_lock_status(LockStatus.LOCKED, activity_start_time_utc) - elif activity.action == "unlock": - self._update_lock_status(LockStatus.UNLOCKED, activity_start_time_utc) + activity_start_time_utc = dt.as_utc(lock_activity.activity_start_time) + if lock_activity.action in ACTIVITY_ACTION_STATES: + self._update_lock_status( + ACTIVITY_ACTION_STATES[lock_activity.action], + activity_start_time_utc, + ) else: _LOGGER.info( "Unhandled lock activity action %s for %s", - activity.action, + lock_activity.action, self.name, ) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index fb5bb3ef3ef..9b4cceccd42 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.11.0"], + "requirements": ["py-august==0.12.0"], "dependencies": ["configurator"], "codeowners": ["@bdraco"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7abd76ab175..6a1699d88fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ pushover_complete==1.1.1 pwmled==1.4.1 # homeassistant.components.august -py-august==0.11.0 +py-august==0.12.0 # homeassistant.components.canary py-canary==0.5.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 10acc321d3a..130f9c4530d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -390,7 +390,7 @@ pure-python-adb==0.2.2.dev0 pushbullet.py==0.11.0 # homeassistant.components.august -py-august==0.11.0 +py-august==0.12.0 # homeassistant.components.canary py-canary==0.5.0 diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py new file mode 100644 index 00000000000..7f2f9e0882b --- /dev/null +++ b/tests/components/august/mocks.py @@ -0,0 +1,95 @@ +"""Mocks for the august component.""" +import datetime +from unittest.mock import MagicMock, PropertyMock + +from august.activity import Activity + +from homeassistant.components.august import AugustData +from homeassistant.util import dt + + +class MockActivity(Activity): + """A mock for py-august Activity class.""" + + def __init__( + self, action=None, activity_start_timestamp=None, activity_end_timestamp=None + ): + """Init the py-august Activity class mock.""" + self._action = action + self._activity_start_timestamp = activity_start_timestamp + self._activity_end_timestamp = activity_end_timestamp + + @property + def activity_start_time(self): + """Mock the time activity started.""" + return datetime.datetime.fromtimestamp(self._activity_start_timestamp) + + @property + def activity_end_time(self): + """Mock the time activity ended.""" + return datetime.datetime.fromtimestamp(self._activity_end_timestamp) + + @property + def action(self): + """Mock the action.""" + return self._action + + +class MockAugustData(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 + ): + """Mock AugustData.""" + self._last_lock_status_update_time_utc = dt.as_utc( + datetime.datetime.fromtimestamp(last_lock_status_update_timestamp) + ) + self._last_door_state_update_time_utc = dt.as_utc( + datetime.datetime.fromtimestamp(last_lock_status_update_timestamp) + ) + + def get_last_lock_status_update_time_utc(self, device_id): + """Mock to get last lock status update time.""" + return self._last_lock_status_update_time_utc + + def set_last_lock_status_update_time_utc(self, device_id, update_time): + """Mock to set last lock status update time.""" + self._last_lock_status_update_time_utc = update_time + + def get_last_door_state_update_time_utc(self, device_id): + """Mock to get last door state update time.""" + return self._last_door_state_update_time_utc + + def set_last_door_state_update_time_utc(self, device_id, update_time): + """Mock to set last door state update time.""" + 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( + name="august.authenticator.should_refresh", return_value=0 + ) + authenticator.refresh_access_token = MagicMock( + name="august.authenticator.refresh_access_token" + ) + return authenticator + + +def _mock_august_authentication(token_text, token_timestamp): + authentication = MagicMock(name="august.authentication") + type(authentication).access_token = PropertyMock(return_value=token_text) + type(authentication).access_token_expires = PropertyMock( + return_value=token_timestamp + ) + return authentication diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py new file mode 100644 index 00000000000..415ce26076a --- /dev/null +++ b/tests/components/august/test_binary_sensor.py @@ -0,0 +1,113 @@ +"""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, + _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) + door_activity_start_timestamp = 1234 + door_activity = MockActivity( + action=ACTION_DOOR_OPEN, + activity_start_timestamp=door_activity_start_timestamp, + activity_end_timestamp=5678, + ) + door._sync_door_activity(door_activity) + assert door.last_update_door_state["door_state"] == LockDoorStatus.OPEN + assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(door_activity_start_timestamp) + ) + + +def test__sync_door_activity_doorclosed(): + """Test _sync_door_activity doorclosed.""" + data = MockAugustData(last_door_state_update_timestamp=1) + door = MockAugustDoorBinarySensor(august_data=data) + door_activity_timestamp = 1234 + door_activity = MockActivity( + action=ACTION_DOOR_CLOSED, + activity_start_timestamp=door_activity_timestamp, + activity_end_timestamp=door_activity_timestamp, + ) + door._sync_door_activity(door_activity) + assert door.last_update_door_state["door_state"] == LockDoorStatus.CLOSED + assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(door_activity_timestamp) + ) + + +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) + first_door_activity_timestamp = 1234 + door_activity = MockActivity( + action=ACTION_DOOR_OPEN, + activity_start_timestamp=first_door_activity_timestamp, + activity_end_timestamp=first_door_activity_timestamp, + ) + door._sync_door_activity(door_activity) + assert door.last_update_door_state["door_state"] == LockDoorStatus.OPEN + assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(first_door_activity_timestamp) + ) + + # 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)) + ) + door_activity_timestamp = 2 + door_activity = MockActivity( + action=ACTION_DOOR_CLOSED, + activity_start_timestamp=door_activity_timestamp, + activity_end_timestamp=door_activity_timestamp, + ) + door._sync_door_activity(door_activity) + assert door.last_update_door_state["door_state"] == LockDoorStatus.OPEN + assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(first_door_activity_timestamp) + ) diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index e84df35b6b1..97d7d4e613b 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -1,27 +1,12 @@ """The tests for the august platform.""" -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import MagicMock from homeassistant.components import august - -def _mock_august_authenticator(): - authenticator = MagicMock(name="august.authenticator") - authenticator.should_refresh = MagicMock( - name="august.authenticator.should_refresh", return_value=0 - ) - authenticator.refresh_access_token = MagicMock( - name="august.authenticator.refresh_access_token" - ) - return authenticator - - -def _mock_august_authentication(token_text, token_timestamp): - authentication = MagicMock(name="august.authentication") - type(authentication).access_token = PropertyMock(return_value=token_text) - type(authentication).access_token_expires = PropertyMock( - return_value=token_timestamp - ) - return authentication +from tests.components.august.mocks import ( + _mock_august_authentication, + _mock_august_authenticator, +) def test__refresh_access_token(): diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py new file mode 100644 index 00000000000..6d33f44e4b7 --- /dev/null +++ b/tests/components/august/test_lock.py @@ -0,0 +1,131 @@ +"""The lock tests for the august platform.""" + +import datetime +from unittest.mock import MagicMock + +from august.activity import ( + ACTION_LOCK_LOCK, + ACTION_LOCK_ONETOUCHLOCK, + ACTION_LOCK_UNLOCK, +) +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, + _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) + lock_activity_start_timestamp = 1234 + lock_activity = MockActivity( + action=ACTION_LOCK_ONETOUCHLOCK, + activity_start_timestamp=lock_activity_start_timestamp, + activity_end_timestamp=5678, + ) + lock._sync_lock_activity(lock_activity) + assert lock.last_update_lock_status["lock_status"] == LockStatus.LOCKED + assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(lock_activity_start_timestamp) + ) + + +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) + lock_activity_start_timestamp = 1234 + lock_activity = MockActivity( + action=ACTION_LOCK_LOCK, + activity_start_timestamp=lock_activity_start_timestamp, + activity_end_timestamp=5678, + ) + lock._sync_lock_activity(lock_activity) + assert lock.last_update_lock_status["lock_status"] == LockStatus.LOCKED + assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(lock_activity_start_timestamp) + ) + + +def test__sync_lock_activity_unlocked(): + """Test _sync_lock_activity unlocking.""" + data = MockAugustData(last_lock_status_update_timestamp=1) + lock = MockAugustLock(august_data=data) + lock_activity_timestamp = 1234 + lock_activity = MockActivity( + action=ACTION_LOCK_UNLOCK, + activity_start_timestamp=lock_activity_timestamp, + activity_end_timestamp=lock_activity_timestamp, + ) + lock._sync_lock_activity(lock_activity) + assert lock.last_update_lock_status["lock_status"] == LockStatus.UNLOCKED + assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(lock_activity_timestamp) + ) + + +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) + first_lock_activity_timestamp = 1234 + lock_activity = MockActivity( + action=ACTION_LOCK_UNLOCK, + activity_start_timestamp=first_lock_activity_timestamp, + activity_end_timestamp=first_lock_activity_timestamp, + ) + lock._sync_lock_activity(lock_activity) + assert lock.last_update_lock_status["lock_status"] == LockStatus.UNLOCKED + assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(first_lock_activity_timestamp) + ) + + # 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)) + ) + lock_activity_timestamp = 2 + lock_activity = MockActivity( + action=ACTION_LOCK_LOCK, + activity_start_timestamp=lock_activity_timestamp, + activity_end_timestamp=lock_activity_timestamp, + ) + lock._sync_lock_activity(lock_activity) + assert lock.last_update_lock_status["lock_status"] == LockStatus.UNLOCKED + assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc( + datetime.datetime.fromtimestamp(first_lock_activity_timestamp) + )