Skip to content

Commit

Permalink
Cleanup August activity processing and add tests (home-assistant#31774)
Browse files Browse the repository at this point in the history
* Update py-august to 0.12.0

* Upstream update also resolves issue home-assistant#28960
  • Loading branch information
bdraco authored Feb 12, 2020
1 parent 834acd8 commit 6879105
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 52 deletions.
27 changes: 14 additions & 13 deletions homeassistant/components/august/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -138,12 +138,12 @@ def update(self):

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
Expand All @@ -153,7 +153,7 @@ def _update_door_state(self, door_state, update_start_time):
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
Expand All @@ -162,25 +162,26 @@ def _sync_door_activity(self, activity):
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,
)

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/august/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 15 additions & 14 deletions homeassistant/components/august/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,15 +67,15 @@ def update(self):

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
Expand All @@ -84,25 +84,26 @@ def _sync_lock_activity(self, activity):
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,
)

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/august/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 95 additions & 0 deletions tests/components/august/mocks.py
Original file line number Diff line number Diff line change
@@ -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
113 changes: 113 additions & 0 deletions tests/components/august/test_binary_sensor.py
Original file line number Diff line number Diff line change
@@ -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)
)
Loading

0 comments on commit 6879105

Please sign in to comment.