Address yale review comments (#124810)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/124821/head
J. Nick Koston 2024-08-28 09:00:52 -10:00 committed by GitHub
parent 2900fa733d
commit 70488ffd15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 267 additions and 283 deletions

View File

@ -26,7 +26,7 @@ from .util import async_create_yale_clientsession
type YaleConfigEntry = ConfigEntry[YaleData]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: YaleConfigEntry) -> bool:
"""Set up yale from a config entry."""
session = async_create_yale_clientsession(hass)
implementation = (

View File

@ -109,12 +109,11 @@ async def async_setup_entry(
for description in SENSOR_TYPES_DOORBELL
)
for doorbell in data.doorbells:
entities.extend(
YaleDoorbellBinarySensor(data, doorbell, description)
for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL
)
entities.extend(
YaleDoorbellBinarySensor(data, doorbell, description)
for description in SENSOR_TYPES_DOORBELL + SENSOR_TYPES_VIDEO_DOORBELL
for doorbell in data.doorbells
)
async_add_entities(entities)

View File

@ -5,7 +5,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import YaleConfigEntry
from .entity import YaleEntityMixin
from .entity import YaleEntity
async def async_setup_entry(
@ -18,7 +18,7 @@ async def async_setup_entry(
async_add_entities(YaleWakeLockButton(data, lock, "wake") for lock in data.locks)
class YaleWakeLockButton(YaleEntityMixin, ButtonEntity):
class YaleWakeLockButton(YaleEntity, ButtonEntity):
"""Representation of an Yale lock wake button."""
_attr_translation_key = "wake"

View File

@ -16,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import YaleConfigEntry, YaleData
from .const import DEFAULT_NAME, DEFAULT_TIMEOUT
from .entity import YaleEntityMixin
from .entity import YaleEntity
_LOGGER = logging.getLogger(__name__)
@ -38,7 +38,7 @@ async def async_setup_entry(
)
class YaleCamera(YaleEntityMixin, Camera):
class YaleCamera(YaleEntity, Camera):
"""An implementation of an Yale security camera."""
_attr_translation_key = "camera"

View File

@ -26,7 +26,9 @@ class YaleConfigFlow(config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=
"""Return logger."""
return _LOGGER
async def async_step_reauth(self, data: Mapping[str, Any]) -> ConfigFlowResult:
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle configuration by re-auth."""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
@ -54,4 +56,5 @@ class YaleConfigFlow(config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=
return self.async_abort(reason="reauth_invalid_user")
return self.async_update_reload_and_abort(entry, data=data)
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_configured()
return await super().async_oauth_create_entry(data)

View File

@ -1,7 +1,5 @@
"""Constants for Yale devices."""
from yalexs.const import Brand
from homeassistant.const import Platform
DEFAULT_TIMEOUT = 25
@ -13,8 +11,6 @@ CONF_INSTALL_ID = "install_id"
VERIFICATION_CODE_KEY = "verification_code"
DEFAULT_BRAND = Brand.YALE_HOME
MANUFACTURER = "Yale Home Inc."
DEFAULT_NAME = "Yale"

View File

@ -4,11 +4,12 @@ from __future__ import annotations
from typing import Any
from yalexs.const import Brand
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant
from . import YaleConfigEntry
from .const import CONF_BRAND, DEFAULT_BRAND
TO_REDACT = {
"HouseID",
@ -45,5 +46,5 @@ async def async_get_config_entry_diagnostics(
)
for doorbell in data.doorbells
},
"brand": entry.data.get(CONF_BRAND, DEFAULT_BRAND),
"brand": Brand.YALE_GLOBAL.value,
}

View File

@ -20,7 +20,7 @@ from .const import MANUFACTURER
DEVICE_TYPES = ["keypad", "lock", "camera", "doorbell", "door", "bell"]
class YaleEntityMixin(Entity):
class YaleEntity(Entity):
"""Base implementation for Yale device."""
_attr_should_poll = False
@ -87,7 +87,7 @@ class YaleEntityMixin(Entity):
self._update_from_data()
class YaleDescriptionEntity(YaleEntityMixin):
class YaleDescriptionEntity(YaleEntity):
"""An Yale entity with a description."""
def __init__(

View File

@ -63,22 +63,17 @@ async def async_setup_entry(
) -> None:
"""Set up the yale event platform."""
data = config_entry.runtime_data
entities: list[YaleEventEntity] = []
for lock in data.locks:
detail = data.get_device_detail(lock.device_id)
if detail.doorbell:
entities.extend(
YaleEventEntity(data, lock, description)
for description in TYPES_DOORBELL
)
for doorbell in data.doorbells:
entities.extend(
YaleEventEntity(data, doorbell, description)
for description in TYPES_DOORBELL + TYPES_VIDEO_DOORBELL
)
entities: list[YaleEventEntity] = [
YaleEventEntity(data, lock, description)
for description in TYPES_DOORBELL
for lock in data.locks
if (detail := data.get_device_detail(lock.device_id)) and detail.doorbell
]
entities.extend(
YaleEventEntity(data, doorbell, description)
for description in TYPES_DOORBELL + TYPES_VIDEO_DOORBELL
for doorbell in data.doorbells
)
async_add_entities(entities)
@ -86,7 +81,6 @@ class YaleEventEntity(YaleDescriptionEntity, EventEntity):
"""An yale event entity."""
entity_description: YaleEventEntityDescription
_attr_has_entity_name = True
_last_activity: Activity | None = None
@callback

View File

@ -19,7 +19,7 @@ from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.dt as dt_util
from . import YaleConfigEntry, YaleData
from .entity import YaleEntityMixin
from .entity import YaleEntity
_LOGGER = logging.getLogger(__name__)
@ -36,7 +36,7 @@ async def async_setup_entry(
async_add_entities(YaleLock(data, lock) for lock in data.locks)
class YaleLock(YaleEntityMixin, RestoreEntity, LockEntity):
class YaleLock(YaleEntity, RestoreEntity, LockEntity):
"""Representation of an Yale lock."""
_attr_name = None

View File

@ -11,6 +11,6 @@
],
"documentation": "https://www.home-assistant.io/integrations/yale",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"loggers": ["socketio", "engineio", "yalexs"],
"requirements": ["yalexs==8.5.4", "yalexs-ble==2.4.3"]
}

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Generic, TypeVar, cast
from typing import Any, cast
from yalexs.activity import ActivityType, LockOperationActivity
from yalexs.doorbell import Doorbell
@ -42,7 +42,7 @@ from .const import (
OPERATION_METHOD_REMOTE,
OPERATION_METHOD_TAG,
)
from .entity import YaleDescriptionEntity, YaleEntityMixin
from .entity import YaleDescriptionEntity, YaleEntity
def _retrieve_device_battery_state(detail: LockDetail) -> int:
@ -55,14 +55,13 @@ def _retrieve_linked_keypad_battery_state(detail: KeypadDetail) -> int | None:
return detail.battery_percentage
_T = TypeVar("_T", LockDetail, KeypadDetail)
@dataclass(frozen=True, kw_only=True)
class YaleSensorEntityDescription(SensorEntityDescription, Generic[_T]):
class YaleSensorEntityDescription[T: LockDetail | KeypadDetail](
SensorEntityDescription
):
"""Mixin for required keys."""
value_fn: Callable[[_T], int | None]
value_fn: Callable[[T], int | None]
SENSOR_TYPE_DEVICE_BATTERY = YaleSensorEntityDescription[LockDetail](
@ -112,7 +111,7 @@ async def async_setup_entry(
async_add_entities(entities)
class YaleOperatorSensor(YaleEntityMixin, RestoreSensor):
class YaleOperatorSensor(YaleEntity, RestoreSensor):
"""Representation of an Yale lock operation sensor."""
_attr_translation_key = "operator"
@ -196,10 +195,12 @@ class YaleOperatorSensor(YaleEntityMixin, RestoreSensor):
self._operated_autorelock = last_attrs[ATTR_OPERATION_AUTORELOCK]
class YaleBatterySensor(YaleDescriptionEntity, SensorEntity, Generic[_T]):
class YaleBatterySensor[T: LockDetail | KeypadDetail](
YaleDescriptionEntity, SensorEntity
):
"""Representation of an Yale sensor."""
entity_description: YaleSensorEntityDescription[_T]
entity_description: YaleSensorEntityDescription[T]
_attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE

View File

@ -63,16 +63,11 @@ def _activity_time_based(latest: Activity) -> Activity | None:
"""Get the latest state of the sensor."""
start = latest.activity_start_time
end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION
if start <= _native_datetime() <= end:
if start <= datetime.now() <= end:
return latest
return None
def _native_datetime() -> datetime:
"""Return time in the format yale uses without timezone."""
return datetime.now()
def retrieve_online_state(data: YaleData, detail: DoorbellDetail | LockDetail) -> bool:
"""Get the latest state of the sensor."""
# The doorbell will go into standby mode when there is no motion

View File

@ -1,12 +1 @@
"""Tests for the yale component."""
MOCK_CONFIG_ENTRY_DATA = {
"auth_implementation": "cloud",
"token": {
"access_token": "access_token",
"expires_in": 1,
"refresh_token": "refresh_token",
"expires_at": 2,
"service": "yale",
},
}

View File

@ -0,0 +1,33 @@
# serializer version: 1
# name: test_doorbell_device_registry
DeviceRegistryEntrySnapshot({
'area_id': 'tmt100_name',
'config_entries': <ANY>,
'configuration_url': 'https://account.aaecosystem.com',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'yale',
'tmt100',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'Yale Home Inc.',
'model': 'hydra1',
'model_id': None,
'name': 'tmt100 Name',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'suggested_area': 'tmt100 Name',
'sw_version': '3.1.0-HYDRC75+201909251139',
'via_device_id': None,
})
# ---

View File

@ -1,7 +1,7 @@
# serializer version: 1
# name: test_diagnostics
dict({
'brand': 'yale_home',
'brand': 'yale_global',
'doorbells': dict({
'K98GiDT45GUL': dict({
'HouseID': '**REDACTED**',

View File

@ -1,7 +1,9 @@
"""The binary_sensor tests for the yale platform."""
import datetime
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from syrupy import SnapshotAssertion
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.const import (
@ -33,28 +35,19 @@ async def test_doorsense(hass: HomeAssistant) -> None:
hass, "get_lock.online_with_doorsense.json"
)
await _create_yale_with_devices(hass, [lock_one])
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
states = hass.states
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True)
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
await hass.services.async_call(LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True)
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
assert (
states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_OFF
)
assert binary_sensor_online_with_doorsense_name.state == STATE_OFF
async def test_lock_bridge_offline(hass: HomeAssistant) -> None:
@ -66,112 +59,78 @@ async def test_lock_bridge_offline(hass: HomeAssistant) -> None:
hass, "get_activity.bridge_offline.json"
)
await _create_yale_with_devices(hass, [lock_one], activities=activities)
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
states = hass.states
assert (
states.get("binary_sensor.online_with_doorsense_name_door").state
== STATE_UNAVAILABLE
)
assert binary_sensor_online_with_doorsense_name.state == STATE_UNAVAILABLE
async def test_create_doorbell(hass: HomeAssistant) -> None:
"""Test creation of a doorbell."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
await _create_yale_with_devices(hass, [doorbell_one])
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
"binary_sensor.k98gidt45gul_name_motion"
states = hass.states
assert states.get("binary_sensor.k98gidt45gul_name_motion").state == STATE_OFF
assert (
states.get("binary_sensor.k98gidt45gul_name_image_capture").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
"binary_sensor.k98gidt45gul_name_image_capture"
assert states.get("binary_sensor.k98gidt45gul_name_connectivity").state == STATE_ON
assert (
states.get("binary_sensor.k98gidt45gul_name_doorbell_ding").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF
binary_sensor_k98gidt45gul_name_online = hass.states.get(
"binary_sensor.k98gidt45gul_name_connectivity"
assert states.get("binary_sensor.k98gidt45gul_name_motion").state == STATE_OFF
assert (
states.get("binary_sensor.k98gidt45gul_name_image_capture").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_online.state == STATE_ON
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
"binary_sensor.k98gidt45gul_name_doorbell_ding"
)
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
"binary_sensor.k98gidt45gul_name_motion"
)
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
"binary_sensor.k98gidt45gul_name_image_capture"
)
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF
async def test_create_doorbell_offline(hass: HomeAssistant) -> None:
"""Test creation of a doorbell that is offline."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json")
await _create_yale_with_devices(hass, [doorbell_one])
binary_sensor_tmt100_name_motion = hass.states.get(
"binary_sensor.tmt100_name_motion"
states = hass.states
assert states.get("binary_sensor.tmt100_name_motion").state == STATE_UNAVAILABLE
assert states.get("binary_sensor.tmt100_name_connectivity").state == STATE_OFF
assert (
states.get("binary_sensor.tmt100_name_doorbell_ding").state == STATE_UNAVAILABLE
)
assert binary_sensor_tmt100_name_motion.state == STATE_UNAVAILABLE
binary_sensor_tmt100_name_online = hass.states.get(
"binary_sensor.tmt100_name_connectivity"
)
assert binary_sensor_tmt100_name_online.state == STATE_OFF
binary_sensor_tmt100_name_ding = hass.states.get(
"binary_sensor.tmt100_name_doorbell_ding"
)
assert binary_sensor_tmt100_name_ding.state == STATE_UNAVAILABLE
async def test_create_doorbell_with_motion(hass: HomeAssistant) -> None:
async def test_create_doorbell_with_motion(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test creation of a doorbell."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
activities = await _mock_activities_from_fixture(
hass, "get_activity.doorbell_motion.json"
)
await _create_yale_with_devices(hass, [doorbell_one], activities=activities)
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
"binary_sensor.k98gidt45gul_name_motion"
states = hass.states
assert states.get("binary_sensor.k98gidt45gul_name_motion").state == STATE_ON
assert states.get("binary_sensor.k98gidt45gul_name_connectivity").state == STATE_ON
assert (
states.get("binary_sensor.k98gidt45gul_name_doorbell_ding").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON
binary_sensor_k98gidt45gul_name_online = hass.states.get(
"binary_sensor.k98gidt45gul_name_connectivity"
)
assert binary_sensor_k98gidt45gul_name_online.state == STATE_ON
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
"binary_sensor.k98gidt45gul_name_doorbell_ding"
)
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
new_time = dt_util.utcnow() + datetime.timedelta(seconds=40)
native_time = datetime.datetime.now() + datetime.timedelta(seconds=40)
with patch(
"homeassistant.components.yale.util._native_datetime",
return_value=native_time,
):
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
"binary_sensor.k98gidt45gul_name_motion"
)
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
freezer.tick(40)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert states.get("binary_sensor.k98gidt45gul_name_motion").state == STATE_OFF
async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
async def test_doorbell_update_via_socketio(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test creation of a doorbell that can be updated via socketio."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
_, socketio = await _create_yale_with_devices(hass, [doorbell_one])
assert doorbell_one.pubsub_channel == "7c7a6672-59c8-3333-ffff-dcd98705cccc"
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
"binary_sensor.k98gidt45gul_name_motion"
states = hass.states
assert states.get("binary_sensor.k98gidt45gul_name_motion").state == STATE_OFF
assert (
states.get("binary_sensor.k98gidt45gul_name_doorbell_ding").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
"binary_sensor.k98gidt45gul_name_doorbell_ding"
)
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
listener = list(socketio._listeners)[0]
listener(
@ -192,10 +151,7 @@ async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
"binary_sensor.k98gidt45gul_name_image_capture"
)
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_ON
assert states.get("binary_sensor.k98gidt45gul_name_image_capture").state == STATE_ON
listener(
doorbell_one.device_id,
@ -226,29 +182,18 @@ async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
"binary_sensor.k98gidt45gul_name_motion"
assert states.get("binary_sensor.k98gidt45gul_name_motion").state == STATE_ON
assert (
states.get("binary_sensor.k98gidt45gul_name_doorbell_ding").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
"binary_sensor.k98gidt45gul_name_doorbell_ding"
freezer.tick(40)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
states.get("binary_sensor.k98gidt45gul_name_image_capture").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
new_time = dt_util.utcnow() + datetime.timedelta(seconds=40)
native_time = datetime.datetime.now() + datetime.timedelta(seconds=40)
with patch(
"homeassistant.components.yale.util._native_datetime",
return_value=native_time,
):
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
binary_sensor_k98gidt45gul_name_image_capture = hass.states.get(
"binary_sensor.k98gidt45gul_name_image_capture"
)
assert binary_sensor_k98gidt45gul_name_image_capture.state == STATE_OFF
listener(
doorbell_one.device_id,
@ -260,37 +205,28 @@ async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
await hass.async_block_till_done()
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
"binary_sensor.k98gidt45gul_name_doorbell_ding"
)
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_ON
new_time = dt_util.utcnow() + datetime.timedelta(seconds=40)
native_time = datetime.datetime.now() + datetime.timedelta(seconds=40)
with patch(
"homeassistant.components.yale.util._native_datetime",
return_value=native_time,
):
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
assert states.get("binary_sensor.k98gidt45gul_name_doorbell_ding").state == STATE_ON
binary_sensor_k98gidt45gul_name_ding = hass.states.get(
"binary_sensor.k98gidt45gul_name_doorbell_ding"
freezer.tick(40)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
states.get("binary_sensor.k98gidt45gul_name_doorbell_ding").state == STATE_OFF
)
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
async def test_doorbell_device_registry(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test creation of a lock with doorsense and bridge ands up in the registry."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json")
await _create_yale_with_devices(hass, [doorbell_one])
reg_device = device_registry.async_get_device(identifiers={("yale", "tmt100")})
assert reg_device.model == "hydra1"
assert reg_device.name == "tmt100 Name"
assert reg_device.manufacturer == "Yale Home Inc."
assert reg_device.sw_version == "3.1.0-HYDRC75+201909251139"
assert reg_device == snapshot
async def test_door_sense_update_via_socketio(hass: HomeAssistant) -> None:
@ -302,11 +238,8 @@ async def test_door_sense_update_via_socketio(hass: HomeAssistant) -> None:
config_entry, socketio = await _create_yale_with_devices(
hass, [lock_one], activities=activities
)
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
states = hass.states
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
listener = list(socketio._listeners)[0]
listener(
@ -316,10 +249,10 @@ async def test_door_sense_update_via_socketio(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
assert (
states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_OFF
)
assert binary_sensor_online_with_doorsense_name.state == STATE_OFF
listener(
lock_one.device_id,
@ -328,33 +261,22 @@ async def test_door_sense_update_via_socketio(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
socketio.connected = True
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
# Ensure socketio status is always preserved
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2))
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
listener(
lock_one.device_id,
@ -363,17 +285,11 @@ async def test_door_sense_update_via_socketio(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4))
await hass.async_block_till_done()
binary_sensor_online_with_doorsense_name = hass.states.get(
"binary_sensor.online_with_doorsense_name_door"
)
assert binary_sensor_online_with_doorsense_name.state == STATE_ON
assert states.get("binary_sensor.online_with_doorsense_name_door").state == STATE_ON
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
@ -383,8 +299,10 @@ async def test_create_lock_with_doorbell(hass: HomeAssistant) -> None:
"""Test creation of a lock with a doorbell."""
lock_one = await _mock_lock_from_fixture(hass, "lock_with_doorbell.online.json")
await _create_yale_with_devices(hass, [lock_one])
ding_sensor = hass.states.get(
"binary_sensor.a6697750d607098bae8d6baa11ef8063_name_doorbell_ding"
states = hass.states
assert (
states.get(
"binary_sensor.a6697750d607098bae8d6baa11ef8063_name_doorbell_ding"
).state
== STATE_OFF
)
assert ding_sensor.state == STATE_OFF

View File

@ -1,16 +1,16 @@
"""Test the yale config flow."""
from collections.abc import Generator
from unittest.mock import Mock, patch
from unittest.mock import ANY, Mock, patch
import pytest
from homeassistant import config_entries
from homeassistant.components.yale.application_credentials import (
OAUTH2_AUTHORIZE,
OAUTH2_TOKEN,
)
from homeassistant.components.yale.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_entry_oauth2_flow
@ -44,7 +44,7 @@ async def test_full_flow(
) -> None:
"""Check full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
DOMAIN, context={"source": SOURCE_USER}
)
state = config_entry_oauth2_flow._encode_jwt(
hass,
@ -78,13 +78,81 @@ async def test_full_flow(
},
)
await hass.config_entries.flow.async_configure(result["flow_id"])
result2 = await hass.config_entries.flow.async_configure(result["flow_id"])
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert len(mock_setup_entry.mock_calls) == 1
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert entry.unique_id == USER_ID
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["result"].unique_id == USER_ID
assert entry.data == {
"auth_implementation": "yale",
"token": {
"access_token": jwt,
"expires_at": ANY,
"expires_in": ANY,
"refresh_token": "mock-refresh-token",
"scope": "any",
"user_id": "mock-user-id",
},
}
@pytest.mark.usefixtures("client_credentials")
@pytest.mark.usefixtures("current_request_with_host")
async def test_full_flow_already_exists(
hass: HomeAssistant,
hass_client_no_auth: ClientSessionGenerator,
aioclient_mock: AiohttpClientMocker,
jwt: str,
mock_setup_entry: Mock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Check full flow for a user that already exists."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
state = config_entry_oauth2_flow._encode_jwt(
hass,
{
"flow_id": result["flow_id"],
"redirect_uri": "https://example.com/auth/external/callback",
},
)
assert result["url"] == (
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
"&redirect_uri=https://example.com/auth/external/callback"
f"&state={state}"
)
client = await hass_client_no_auth()
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
assert resp.status == 200
assert resp.headers["content-type"] == "text/html; charset=utf-8"
aioclient_mock.clear_requests()
aioclient_mock.post(
OAUTH2_TOKEN,
json={
"access_token": jwt,
"scope": "any",
"expires_in": 86399,
"refresh_token": "mock-refresh-token",
"user_id": "mock-user-id",
"expires_at": 1697753347,
},
)
result2 = await hass.config_entries.flow.async_configure(result["flow_id"])
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured"
@pytest.mark.usefixtures("client_credentials")
@pytest.mark.usefixtures("current_request_with_host")

View File

@ -1,7 +1,6 @@
"""The event tests for the yale."""
import datetime
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
@ -42,7 +41,9 @@ async def test_create_doorbell_offline(hass: HomeAssistant) -> None:
assert doorbell_state.state == STATE_UNAVAILABLE
async def test_create_doorbell_with_motion(hass: HomeAssistant) -> None:
async def test_create_doorbell_with_motion(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test creation of a doorbell."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
activities = await _mock_activities_from_fixture(
@ -58,19 +59,16 @@ async def test_create_doorbell_with_motion(hass: HomeAssistant) -> None:
assert doorbell_state is not None
assert doorbell_state.state == STATE_UNKNOWN
new_time = dt_util.utcnow() + datetime.timedelta(seconds=40)
native_time = datetime.datetime.now() + datetime.timedelta(seconds=40)
with patch(
"homeassistant.components.yale.util._native_datetime",
return_value=native_time,
):
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
freezer.tick(40)
async_fire_time_changed(hass)
await hass.async_block_till_done()
motion_state = hass.states.get("event.k98gidt45gul_name_motion")
assert motion_state.state == isotime
async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
async def test_doorbell_update_via_socketio(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test creation of a doorbell that can be updated via socketio."""
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
@ -119,14 +117,9 @@ async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
assert motion_state.state != STATE_UNKNOWN
isotime = motion_state.state
new_time = dt_util.utcnow() + datetime.timedelta(seconds=40)
native_time = datetime.datetime.now() + datetime.timedelta(seconds=40)
with patch(
"homeassistant.components.yale.util._native_datetime",
return_value=native_time,
):
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
freezer.tick(40)
async_fire_time_changed(hass)
await hass.async_block_till_done()
motion_state = hass.states.get("event.k98gidt45gul_name_motion")
assert motion_state is not None
@ -147,14 +140,9 @@ async def test_doorbell_update_via_socketio(hass: HomeAssistant) -> None:
assert doorbell_state.state != STATE_UNKNOWN
isotime = motion_state.state
new_time = dt_util.utcnow() + datetime.timedelta(seconds=40)
native_time = datetime.datetime.now() + datetime.timedelta(seconds=40)
with patch(
"homeassistant.components.yale.util._native_datetime",
return_value=native_time,
):
async_fire_time_changed(hass, new_time)
await hass.async_block_till_done()
freezer.tick(40)
async_fire_time_changed(hass)
await hass.async_block_till_done()
doorbell_state = hass.states.get("event.k98gidt45gul_name_doorbell")
assert doorbell_state is not None

View File

@ -89,16 +89,15 @@ async def test_unlock_throws_yale_api_http_error(hass: HomeAssistant) -> None:
"unlock_return_activities": _unlock_return_activities_side_effect
},
)
last_err = None
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
try:
with pytest.raises(
HomeAssistantError,
match=(
"A6697750D607098BAE8D6BAA11EF8063 Name: This should bubble up as its user"
" consumable"
),
):
await hass.services.async_call(LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True)
except HomeAssistantError as err:
last_err = err
assert str(last_err) == (
"A6697750D607098BAE8D6BAA11EF8063 Name: This should bubble up as its user"
" consumable"
)
async def test_lock_throws_yale_api_http_error(hass: HomeAssistant) -> None:
@ -119,16 +118,15 @@ async def test_lock_throws_yale_api_http_error(hass: HomeAssistant) -> None:
"lock_return_activities": _lock_return_activities_side_effect
},
)
last_err = None
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
try:
with pytest.raises(
HomeAssistantError,
match=(
"A6697750D607098BAE8D6BAA11EF8063 Name: This should bubble up as its user"
" consumable"
),
):
await hass.services.async_call(LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True)
except HomeAssistantError as err:
last_err = err
assert str(last_err) == (
"A6697750D607098BAE8D6BAA11EF8063 Name: This should bubble up as its user"
" consumable"
)
async def test_open_throws_hass_service_not_supported_error(
@ -185,6 +183,7 @@ async def test_load_unload(hass: HomeAssistant) -> None:
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.NOT_LOADED
async def test_load_triggers_ble_discovery(