core/tests/components/august/test_lock.py

461 lines
16 KiB
Python

"""The lock tests for the august platform."""
import datetime
from unittest.mock import Mock
from aiohttp import ClientResponseError
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from yalexs.manager.activity import INITIAL_LOCK_RESYNC_TIME
from yalexs.pubnub_async import AugustPubNub
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockState
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_LOCK,
SERVICE_OPEN,
SERVICE_UNLOCK,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.util.dt as dt_util
from .mocks import (
_create_august_with_devices,
_mock_activities_from_fixture,
_mock_doorsense_enabled_august_lock_detail,
_mock_lock_from_fixture,
_mock_lock_with_unlatch,
_mock_operative_august_lock_detail,
)
from tests.common import async_fire_time_changed
async def test_lock_device_registry(
hass: HomeAssistant, device_registry: dr.DeviceRegistry, snapshot: SnapshotAssertion
) -> None:
"""Test creation of a lock with doorsense and bridge ands up in the registry."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
await _create_august_with_devices(hass, [lock_one])
reg_device = device_registry.async_get_device(
identifiers={("august", "online_with_doorsense")}
)
assert reg_device == snapshot
async def test_lock_changed_by(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json")
await _create_august_with_devices(hass, [lock_one], activities=activities)
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.LOCKED
assert lock_state.attributes["changed_by"] == "Your favorite elven princess"
async def test_state_locking(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge that is locking."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
activities = await _mock_activities_from_fixture(hass, "get_activity.locking.json")
await _create_august_with_devices(hass, [lock_one], activities=activities)
assert hass.states.get("lock.online_with_doorsense_name").state == LockState.LOCKING
async def test_state_unlocking(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge that is unlocking."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
activities = await _mock_activities_from_fixture(
hass, "get_activity.unlocking.json"
)
await _create_august_with_devices(hass, [lock_one], activities=activities)
assert (
hass.states.get("lock.online_with_doorsense_name").state == LockState.UNLOCKING
)
async def test_state_jammed(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge that is jammed."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
activities = await _mock_activities_from_fixture(hass, "get_activity.jammed.json")
await _create_august_with_devices(hass, [lock_one], activities=activities)
assert hass.states.get("lock.online_with_doorsense_name").state == LockState.JAMMED
async def test_one_lock_operation(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test creation of a lock with doorsense and bridge."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
await _create_august_with_devices(hass, [lock_one])
states = hass.states
lock_state = states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.LOCKED
assert lock_state.attributes["battery_level"] == 92
assert lock_state.attributes["friendly_name"] == "online_with_doorsense Name"
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True)
lock_state = states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.UNLOCKED
assert lock_state.attributes["battery_level"] == 92
assert lock_state.attributes["friendly_name"] == "online_with_doorsense Name"
await hass.services.async_call(LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True)
assert states.get("lock.online_with_doorsense_name").state == LockState.LOCKED
# No activity means it will be unavailable until the activity feed has data
lock_operator_sensor = entity_registry.async_get(
"sensor.online_with_doorsense_name_operator"
)
assert lock_operator_sensor
assert (
states.get("sensor.online_with_doorsense_name_operator").state == STATE_UNKNOWN
)
async def test_open_lock_operation(hass: HomeAssistant) -> None:
"""Test open lock operation using the open service."""
lock_with_unlatch = await _mock_lock_with_unlatch(hass)
await _create_august_with_devices(hass, [lock_with_unlatch])
lock_online_with_unlatch_name = hass.states.get("lock.online_with_unlatch_name")
assert lock_online_with_unlatch_name.state == LockState.LOCKED
data = {ATTR_ENTITY_ID: "lock.online_with_unlatch_name"}
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)
lock_online_with_unlatch_name = hass.states.get("lock.online_with_unlatch_name")
assert lock_online_with_unlatch_name.state == LockState.UNLOCKED
async def test_open_lock_operation_pubnub_connected(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test open lock operation using the open service when pubnub is connected."""
lock_with_unlatch = await _mock_lock_with_unlatch(hass)
assert lock_with_unlatch.pubsub_channel == "pubsub"
pubnub = AugustPubNub()
await _create_august_with_devices(hass, [lock_with_unlatch], pubnub=pubnub)
pubnub.connected = True
assert hass.states.get("lock.online_with_unlatch_name").state == LockState.LOCKED
data = {ATTR_ENTITY_ID: "lock.online_with_unlatch_name"}
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)
pubnub.message(
pubnub,
Mock(
channel=lock_with_unlatch.pubsub_channel,
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
message={
"status": "kAugLockState_Unlocked",
},
),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert hass.states.get("lock.online_with_unlatch_name").state == LockState.UNLOCKED
await hass.async_block_till_done()
async def test_one_lock_operation_pubnub_connected(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test lock and unlock operations are async when pubnub is connected."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
assert lock_one.pubsub_channel == "pubsub"
pubnub = AugustPubNub()
await _create_august_with_devices(hass, [lock_one], pubnub=pubnub)
pubnub.connected = True
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.LOCKED
assert lock_state.attributes["battery_level"] == 92
assert lock_state.attributes["friendly_name"] == "online_with_doorsense Name"
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True)
pubnub.message(
pubnub,
Mock(
channel=lock_one.pubsub_channel,
timetoken=(dt_util.utcnow().timestamp() + 1) * 10000000,
message={
"status": "kAugLockState_Unlocked",
},
),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.UNLOCKED
assert lock_state.attributes["battery_level"] == 92
assert lock_state.attributes["friendly_name"] == "online_with_doorsense Name"
await hass.services.async_call(LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True)
pubnub.message(
pubnub,
Mock(
channel=lock_one.pubsub_channel,
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
message={
"status": "kAugLockState_Locked",
},
),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.LOCKED
# No activity means it will be unavailable until the activity feed has data
lock_operator_sensor = entity_registry.async_get(
"sensor.online_with_doorsense_name_operator"
)
assert lock_operator_sensor
assert (
hass.states.get("sensor.online_with_doorsense_name_operator").state
== STATE_UNKNOWN
)
freezer.tick(INITIAL_LOCK_RESYNC_TIME)
pubnub.message(
pubnub,
Mock(
channel=lock_one.pubsub_channel,
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
message={
"status": "kAugLockState_Unlocked",
},
),
)
await hass.async_block_till_done()
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.UNLOCKED
async def test_lock_jammed(hass: HomeAssistant) -> None:
"""Test lock gets jammed on unlock."""
def _unlock_return_activities_side_effect(access_token, device_id):
raise ClientResponseError(None, None, status=531)
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
await _create_august_with_devices(
hass,
[lock_one],
api_call_side_effects={
"unlock_return_activities": _unlock_return_activities_side_effect
},
)
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.LOCKED
assert lock_state.attributes["battery_level"] == 92
assert lock_state.attributes["friendly_name"] == "online_with_doorsense Name"
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True)
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.JAMMED
async def test_lock_throws_exception_on_unknown_status_code(
hass: HomeAssistant,
) -> None:
"""Test lock throws exception."""
def _unlock_return_activities_side_effect(access_token, device_id):
raise ClientResponseError(None, None, status=500)
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
await _create_august_with_devices(
hass,
[lock_one],
api_call_side_effects={
"unlock_return_activities": _unlock_return_activities_side_effect
},
)
lock_state = hass.states.get("lock.online_with_doorsense_name")
assert lock_state.state == LockState.LOCKED
assert lock_state.attributes["battery_level"] == 92
assert lock_state.attributes["friendly_name"] == "online_with_doorsense Name"
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
with pytest.raises(ClientResponseError):
await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True)
async def test_one_lock_unknown_state(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge."""
lock_one = await _mock_lock_from_fixture(
hass,
"get_lock.online.unknown_state.json",
)
await _create_august_with_devices(hass, [lock_one])
assert hass.states.get("lock.brokenid_name").state == STATE_UNKNOWN
async def test_lock_bridge_offline(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge that goes offline."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
activities = await _mock_activities_from_fixture(
hass, "get_activity.bridge_offline.json"
)
await _create_august_with_devices(hass, [lock_one], activities=activities)
assert hass.states.get("lock.online_with_doorsense_name").state == STATE_UNAVAILABLE
async def test_lock_bridge_online(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge that goes offline."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
activities = await _mock_activities_from_fixture(
hass, "get_activity.bridge_online.json"
)
await _create_august_with_devices(hass, [lock_one], activities=activities)
assert hass.states.get("lock.online_with_doorsense_name").state == LockState.LOCKED
async def test_lock_update_via_pubnub(hass: HomeAssistant) -> None:
"""Test creation of a lock with doorsense and bridge."""
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
states = hass.states
assert lock_one.pubsub_channel == "pubsub"
pubnub = AugustPubNub()
activities = await _mock_activities_from_fixture(hass, "get_activity.lock.json")
config_entry = await _create_august_with_devices(
hass, [lock_one], activities=activities, pubnub=pubnub
)
pubnub.connected = True
assert states.get("lock.online_with_doorsense_name").state == LockState.LOCKED
pubnub.message(
pubnub,
Mock(
channel=lock_one.pubsub_channel,
timetoken=dt_util.utcnow().timestamp() * 10000000,
message={
"status": "kAugLockState_Unlocking",
},
),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert states.get("lock.online_with_doorsense_name").state == LockState.UNLOCKING
pubnub.message(
pubnub,
Mock(
channel=lock_one.pubsub_channel,
timetoken=(dt_util.utcnow().timestamp() + 1) * 10000000,
message={
"status": "kAugLockState_Locking",
},
),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert states.get("lock.online_with_doorsense_name").state == LockState.LOCKING
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
await hass.async_block_till_done()
assert hass.states.get("lock.online_with_doorsense_name").state == LockState.LOCKING
pubnub.connected = True
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
await hass.async_block_till_done()
assert states.get("lock.online_with_doorsense_name").state == LockState.LOCKING
# Ensure pubnub status is always preserved
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2))
await hass.async_block_till_done()
assert states.get("lock.online_with_doorsense_name").state == LockState.LOCKING
pubnub.message(
pubnub,
Mock(
channel=lock_one.pubsub_channel,
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
message={
"status": "kAugLockState_Unlocking",
},
),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert states.get("lock.online_with_doorsense_name").state == LockState.UNLOCKING
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4))
await hass.async_block_till_done()
assert states.get("lock.online_with_doorsense_name").state == LockState.UNLOCKING
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
async def test_open_throws_hass_service_not_supported_error(
hass: HomeAssistant,
) -> None:
"""Test open throws correct error on entity does not support this service error."""
mocked_lock_detail = await _mock_operative_august_lock_detail(hass)
await _create_august_with_devices(hass, [mocked_lock_detail])
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
with pytest.raises(HomeAssistantError, match="does not support this service"):
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)