core/tests/components/august/mocks.py

386 lines
13 KiB
Python

"""Mocks for the august component."""
from __future__ import annotations
import json
import os
import time
from typing import Any, Iterable
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
from yalexs.activity import (
ACTIVITY_ACTIONS_BRIDGE_OPERATION,
ACTIVITY_ACTIONS_DOOR_OPERATION,
ACTIVITY_ACTIONS_DOORBELL_DING,
ACTIVITY_ACTIONS_DOORBELL_MOTION,
ACTIVITY_ACTIONS_DOORBELL_VIEW,
ACTIVITY_ACTIONS_LOCK_OPERATION,
SOURCE_LOCK_OPERATE,
SOURCE_LOG,
BridgeOperationActivity,
DoorbellDingActivity,
DoorbellMotionActivity,
DoorbellViewActivity,
DoorOperationActivity,
LockOperationActivity,
)
from yalexs.authenticator import AuthenticationState
from yalexs.doorbell import Doorbell, DoorbellDetail
from yalexs.lock import Lock, LockDetail
from yalexs.pubnub_async import AugustPubNub
from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
def _mock_get_config():
"""Return a default august config."""
return {
DOMAIN: {
CONF_LOGIN_METHOD: "email",
CONF_USERNAME: "mocked_username",
CONF_PASSWORD: "mocked_password",
}
}
def _mock_authenticator(auth_state):
"""Mock an august authenticator."""
authenticator = MagicMock()
type(authenticator).state = PropertyMock(return_value=auth_state)
return authenticator
@patch("homeassistant.components.august.gateway.ApiAsync")
@patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate")
async def _mock_setup_august(
hass, api_instance, pubnub_mock, authenticate_mock, api_mock
):
"""Set up august integration."""
authenticate_mock.side_effect = MagicMock(
return_value=_mock_august_authentication(
"original_token", 1234, AuthenticationState.AUTHENTICATED
)
)
api_mock.return_value = api_instance
entry = MockConfigEntry(
domain=DOMAIN,
data=_mock_get_config()[DOMAIN],
options={},
)
entry.add_to_hass(hass)
with patch("homeassistant.components.august.async_create_pubnub"), patch(
"homeassistant.components.august.AugustPubNub", return_value=pubnub_mock
):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return entry
async def _create_august_with_devices(
hass: HomeAssistant,
devices: Iterable[LockDetail | DoorbellDetail],
api_call_side_effects: dict[str, Any] | None = None,
activities: list[Any] | None = None,
pubnub: AugustPubNub | None = None,
) -> ConfigEntry:
entry, _ = await _create_august_api_with_devices(
hass, devices, api_call_side_effects, activities, pubnub
)
return entry
async def _create_august_api_with_devices( # noqa: C901
hass, devices, api_call_side_effects=None, activities=None, pubnub=None
):
if api_call_side_effects is None:
api_call_side_effects = {}
if pubnub is None:
pubnub = AugustPubNub()
device_data = {"doorbells": [], "locks": []}
for device in devices:
if isinstance(device, LockDetail):
device_data["locks"].append(
{"base": _mock_august_lock(device.device_id), "detail": device}
)
elif isinstance(device, DoorbellDetail):
device_data["doorbells"].append(
{"base": _mock_august_doorbell(device.device_id), "detail": device}
)
else:
raise ValueError
def _get_device_detail(device_type, device_id):
for device in device_data[device_type]:
if device["detail"].device_id == device_id:
return device["detail"]
raise ValueError
def _get_base_devices(device_type):
base_devices = []
for device in device_data[device_type]:
base_devices.append(device["base"])
return base_devices
def get_lock_detail_side_effect(access_token, device_id):
return _get_device_detail("locks", device_id)
def get_doorbell_detail_side_effect(access_token, device_id):
return _get_device_detail("doorbells", device_id)
def get_operable_locks_side_effect(access_token):
return _get_base_devices("locks")
def get_doorbells_side_effect(access_token):
return _get_base_devices("doorbells")
def get_house_activities_side_effect(access_token, house_id, limit=10):
if activities is not None:
return activities
return []
def lock_return_activities_side_effect(access_token, device_id):
lock = _get_device_detail("locks", device_id)
return [
# There is a check to prevent out of order events
# so we set the doorclosed & lock event in the future
# to prevent a race condition where we reject the event
# because it happened before the dooropen & unlock event.
_mock_lock_operation_activity(lock, "lock", 2000),
_mock_door_operation_activity(lock, "doorclosed", 2000),
]
def unlock_return_activities_side_effect(access_token, device_id):
lock = _get_device_detail("locks", device_id)
return [
_mock_lock_operation_activity(lock, "unlock", 0),
_mock_door_operation_activity(lock, "dooropen", 0),
]
if "get_lock_detail" not in api_call_side_effects:
api_call_side_effects["get_lock_detail"] = get_lock_detail_side_effect
if "get_doorbell_detail" not in api_call_side_effects:
api_call_side_effects["get_doorbell_detail"] = get_doorbell_detail_side_effect
if "get_operable_locks" not in api_call_side_effects:
api_call_side_effects["get_operable_locks"] = get_operable_locks_side_effect
if "get_doorbells" not in api_call_side_effects:
api_call_side_effects["get_doorbells"] = get_doorbells_side_effect
if "get_house_activities" not in api_call_side_effects:
api_call_side_effects["get_house_activities"] = get_house_activities_side_effect
if "lock_return_activities" not in api_call_side_effects:
api_call_side_effects[
"lock_return_activities"
] = lock_return_activities_side_effect
if "unlock_return_activities" not in api_call_side_effects:
api_call_side_effects[
"unlock_return_activities"
] = unlock_return_activities_side_effect
api_instance, entry = await _mock_setup_august_with_api_side_effects(
hass, api_call_side_effects, pubnub
)
if device_data["locks"]:
# Ensure we sync status when the integration is loaded if there
# are any locks
assert api_instance.async_status_async.mock_calls
return entry, api_instance
async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub):
api_instance = MagicMock(name="Api")
if api_call_side_effects["get_lock_detail"]:
type(api_instance).async_get_lock_detail = AsyncMock(
side_effect=api_call_side_effects["get_lock_detail"]
)
if api_call_side_effects["get_operable_locks"]:
type(api_instance).async_get_operable_locks = AsyncMock(
side_effect=api_call_side_effects["get_operable_locks"]
)
if api_call_side_effects["get_doorbells"]:
type(api_instance).async_get_doorbells = AsyncMock(
side_effect=api_call_side_effects["get_doorbells"]
)
if api_call_side_effects["get_doorbell_detail"]:
type(api_instance).async_get_doorbell_detail = AsyncMock(
side_effect=api_call_side_effects["get_doorbell_detail"]
)
if api_call_side_effects["get_house_activities"]:
type(api_instance).async_get_house_activities = AsyncMock(
side_effect=api_call_side_effects["get_house_activities"]
)
if api_call_side_effects["lock_return_activities"]:
type(api_instance).async_lock_return_activities = AsyncMock(
side_effect=api_call_side_effects["lock_return_activities"]
)
if api_call_side_effects["unlock_return_activities"]:
type(api_instance).async_unlock_return_activities = AsyncMock(
side_effect=api_call_side_effects["unlock_return_activities"]
)
api_instance.async_unlock_async = AsyncMock()
api_instance.async_lock_async = AsyncMock()
api_instance.async_status_async = AsyncMock()
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
return api_instance, await _mock_setup_august(hass, api_instance, pubnub)
def _mock_august_authentication(token_text, token_timestamp, state):
authentication = MagicMock(name="yalexs.authentication")
type(authentication).state = PropertyMock(return_value=state)
type(authentication).access_token = PropertyMock(return_value=token_text)
type(authentication).access_token_expires = PropertyMock(
return_value=token_timestamp
)
return authentication
def _mock_august_lock(lockid="mocklockid1", houseid="mockhouseid1"):
return Lock(lockid, _mock_august_lock_data(lockid=lockid, houseid=houseid))
def _mock_august_doorbell(deviceid="mockdeviceid1", houseid="mockhouseid1"):
return Doorbell(
deviceid, _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid)
)
def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1"):
return {
"_id": deviceid,
"DeviceID": deviceid,
"name": f"{deviceid} Name",
"HouseID": houseid,
"UserType": "owner",
"serialNumber": "mockserial",
"battery": 90,
"status": "standby",
"currentFirmwareVersion": "mockfirmware",
"Bridge": {
"_id": "bridgeid1",
"firmwareVersion": "mockfirm",
"operative": True,
},
"LockStatus": {"doorState": "open"},
}
def _mock_august_lock_data(lockid="mocklockid1", houseid="mockhouseid1"):
return {
"_id": lockid,
"LockID": lockid,
"LockName": f"{lockid} Name",
"HouseID": houseid,
"UserType": "owner",
"SerialNumber": "mockserial",
"battery": 90,
"currentFirmwareVersion": "mockfirmware",
"Bridge": {
"_id": "bridgeid1",
"firmwareVersion": "mockfirm",
"operative": True,
},
"LockStatus": {"doorState": "open"},
}
async def _mock_operative_august_lock_detail(hass):
return await _mock_lock_from_fixture(hass, "get_lock.online.json")
async def _mock_inoperative_august_lock_detail(hass):
return await _mock_lock_from_fixture(hass, "get_lock.offline.json")
async def _mock_activities_from_fixture(hass, path):
json_dict = await _load_json_fixture(hass, path)
activities = []
for activity_json in json_dict:
activity = _activity_from_dict(activity_json)
if activity:
activities.append(activity)
return activities
async def _mock_lock_from_fixture(hass, path):
json_dict = await _load_json_fixture(hass, path)
return LockDetail(json_dict)
async def _mock_doorbell_from_fixture(hass, path):
json_dict = await _load_json_fixture(hass, path)
return DoorbellDetail(json_dict)
async def _load_json_fixture(hass, path):
fixture = await hass.async_add_executor_job(
load_fixture, os.path.join("august", path)
)
return json.loads(fixture)
async def _mock_doorsense_enabled_august_lock_detail(hass):
return await _mock_lock_from_fixture(hass, "get_lock.online_with_doorsense.json")
async def _mock_doorsense_missing_august_lock_detail(hass):
return await _mock_lock_from_fixture(hass, "get_lock.online_missing_doorsense.json")
def _mock_lock_operation_activity(lock, action, offset):
return LockOperationActivity(
SOURCE_LOCK_OPERATE,
{
"dateTime": (time.time() + offset) * 1000,
"deviceID": lock.device_id,
"deviceType": "lock",
"action": action,
},
)
def _mock_door_operation_activity(lock, action, offset):
return DoorOperationActivity(
SOURCE_LOCK_OPERATE,
{
"dateTime": (time.time() + offset) * 1000,
"deviceID": lock.device_id,
"deviceType": "lock",
"action": action,
},
)
def _activity_from_dict(activity_dict):
action = activity_dict.get("action")
activity_dict["dateTime"] = time.time() * 1000
if action in ACTIVITY_ACTIONS_DOORBELL_DING:
return DoorbellDingActivity(SOURCE_LOG, activity_dict)
if action in ACTIVITY_ACTIONS_DOORBELL_MOTION:
return DoorbellMotionActivity(SOURCE_LOG, activity_dict)
if action in ACTIVITY_ACTIONS_DOORBELL_VIEW:
return DoorbellViewActivity(SOURCE_LOG, activity_dict)
if action in ACTIVITY_ACTIONS_LOCK_OPERATION:
return LockOperationActivity(SOURCE_LOG, activity_dict)
if action in ACTIVITY_ACTIONS_DOOR_OPERATION:
return DoorOperationActivity(SOURCE_LOG, activity_dict)
if action in ACTIVITY_ACTIONS_BRIDGE_OPERATION:
return BridgeOperationActivity(SOURCE_LOG, activity_dict)
return None