Bump yalesmartalarmclient to 0.4.0 (#124165)

* Bump yalesmartalarmclient to 0.4.0

* Fix various
This commit is contained in:
G Johansson 2024-08-18 22:41:45 +02:00 committed by GitHub
parent 50f9c1e5a4
commit 05aeb3fbd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 121 additions and 62 deletions

View File

@ -146,12 +146,7 @@ class YaleDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Fetch data from Yale.""" """Fetch data from Yale."""
try: try:
arm_status = self.yale.get_armed_status() arm_status = self.yale.get_armed_status()
data = self.yale.get_all() data = self.yale.get_information()
cycle = data["CYCLE"]
status = data["STATUS"]
online = data["ONLINE"]
panel_info = data["PANEL INFO"]
except AuthenticationError as error: except AuthenticationError as error:
raise ConfigEntryAuthFailed from error raise ConfigEntryAuthFailed from error
except YALE_BASE_ERRORS as error: except YALE_BASE_ERRORS as error:
@ -159,8 +154,8 @@ class YaleDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
return { return {
"arm_status": arm_status, "arm_status": arm_status,
"cycle": cycle, "cycle": data.cycle,
"status": status, "status": data.status,
"online": online, "online": data.online,
"panel_info": panel_info, "panel_info": data.panel_info,
} }

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import asdict
from typing import Any from typing import Any
from homeassistant.components.diagnostics import async_redact_data from homeassistant.components.diagnostics import async_redact_data
@ -29,4 +30,4 @@ async def async_get_config_entry_diagnostics(
assert coordinator.yale assert coordinator.yale
get_all_data = await hass.async_add_executor_job(coordinator.yale.get_all) get_all_data = await hass.async_add_executor_job(coordinator.yale.get_all)
return async_redact_data(get_all_data, TO_REDACT) return async_redact_data(asdict(get_all_data), TO_REDACT)

View File

@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any
from homeassistant.components.lock import LockEntity from homeassistant.components.lock import LockEntity
from homeassistant.const import ATTR_CODE from homeassistant.const import ATTR_CODE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import YaleConfigEntry from . import YaleConfigEntry
@ -61,17 +61,22 @@ class YaleDoorlock(YaleEntity, LockEntity):
"""Set lock.""" """Set lock."""
if TYPE_CHECKING: if TYPE_CHECKING:
assert self.coordinator.yale, "Connection to API is missing" assert self.coordinator.yale, "Connection to API is missing"
if command == "unlocked" and not code:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="no_code",
)
try: try:
get_lock = await self.hass.async_add_executor_job( get_lock = await self.hass.async_add_executor_job(
self.coordinator.yale.lock_api.get, self.lock_name self.coordinator.yale.lock_api.get, self.lock_name
) )
if command == "locked": if get_lock and command == "locked":
lock_state = await self.hass.async_add_executor_job( lock_state = await self.hass.async_add_executor_job(
self.coordinator.yale.lock_api.close_lock, self.coordinator.yale.lock_api.close_lock,
get_lock, get_lock,
) )
if command == "unlocked": if code and get_lock and command == "unlocked":
lock_state = await self.hass.async_add_executor_job( lock_state = await self.hass.async_add_executor_job(
self.coordinator.yale.lock_api.open_lock, get_lock, code self.coordinator.yale.lock_api.open_lock, get_lock, code
) )

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/yale_smart_alarm", "documentation": "https://www.home-assistant.io/integrations/yale_smart_alarm",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["yalesmartalarmclient"], "loggers": ["yalesmartalarmclient"],
"requirements": ["yalesmartalarmclient==0.3.9"] "requirements": ["yalesmartalarmclient==0.4.0"]
} }

View File

@ -67,6 +67,9 @@
"set_lock": { "set_lock": {
"message": "Could not set lock for {name}: {error}" "message": "Could not set lock for {name}: {error}"
}, },
"no_code": {
"message": "Can not unlock without code"
},
"could_not_change_lock": { "could_not_change_lock": {
"message": "Could not set lock, check system ready for lock" "message": "Could not set lock, check system ready for lock"
}, },

View File

@ -2955,7 +2955,7 @@ xmltodict==0.13.0
xs1-api-client==3.0.0 xs1-api-client==3.0.0
# homeassistant.components.yale_smart_alarm # homeassistant.components.yale_smart_alarm
yalesmartalarmclient==0.3.9 yalesmartalarmclient==0.4.0
# homeassistant.components.august # homeassistant.components.august
# homeassistant.components.yalexs_ble # homeassistant.components.yalexs_ble

View File

@ -2335,7 +2335,7 @@ xknxproject==3.7.1
xmltodict==0.13.0 xmltodict==0.13.0
# homeassistant.components.yale_smart_alarm # homeassistant.components.yale_smart_alarm
yalesmartalarmclient==0.3.9 yalesmartalarmclient==0.4.0
# homeassistant.components.august # homeassistant.components.august
# homeassistant.components.yalexs_ble # homeassistant.components.yalexs_ble

View File

@ -7,6 +7,7 @@ from typing import Any
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import pytest import pytest
from yalesmartalarmclient import YaleSmartAlarmData
from yalesmartalarmclient.const import YALE_STATE_ARM_FULL from yalesmartalarmclient.const import YALE_STATE_ARM_FULL
from homeassistant.components.yale_smart_alarm.const import DOMAIN, PLATFORMS from homeassistant.components.yale_smart_alarm.const import DOMAIN, PLATFORMS
@ -33,7 +34,10 @@ async def patch_platform_constant() -> list[Platform]:
@pytest.fixture @pytest.fixture
async def load_config_entry( async def load_config_entry(
hass: HomeAssistant, load_json: dict[str, Any], load_platforms: list[Platform] hass: HomeAssistant,
get_data: YaleSmartAlarmData,
get_all_data: YaleSmartAlarmData,
load_platforms: list[Platform],
) -> tuple[MockConfigEntry, Mock]: ) -> tuple[MockConfigEntry, Mock]:
"""Set up the Yale Smart Living integration in Home Assistant.""" """Set up the Yale Smart Living integration in Home Assistant."""
with patch("homeassistant.components.yale_smart_alarm.PLATFORMS", load_platforms): with patch("homeassistant.components.yale_smart_alarm.PLATFORMS", load_platforms):
@ -56,7 +60,8 @@ async def load_config_entry(
client = mock_client_class.return_value client = mock_client_class.return_value
client.auth = Mock() client.auth = Mock()
client.lock_api = Mock() client.lock_api = Mock()
client.get_all.return_value = load_json client.get_all.return_value = get_all_data
client.get_information.return_value = get_data
client.get_armed_status.return_value = YALE_STATE_ARM_FULL client.get_armed_status.return_value = YALE_STATE_ARM_FULL
await hass.config_entries.async_setup(config_entry.entry_id) await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -64,10 +69,50 @@ async def load_config_entry(
return (config_entry, client) return (config_entry, client)
@pytest.fixture(name="load_json", scope="package") @pytest.fixture(name="loaded_fixture", scope="package")
def load_json_from_fixture() -> dict[str, Any]: def get_fixture_data() -> dict[str, Any]:
"""Load fixture with json data and return.""" """Load fixture with json data and return."""
data_fixture = load_fixture("get_all.json", "yale_smart_alarm") data_fixture = load_fixture("get_all.json", "yale_smart_alarm")
json_data: dict[str, Any] = json.loads(data_fixture) json_data: dict[str, Any] = json.loads(data_fixture)
return json_data return json_data
@pytest.fixture(name="get_data", scope="package")
def get_update_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
"""Load update data and return."""
status = loaded_fixture["STATUS"]
cycle = loaded_fixture["CYCLE"]
online = loaded_fixture["ONLINE"]
panel_info = loaded_fixture["PANEL INFO"]
return YaleSmartAlarmData(
status=status,
cycle=cycle,
online=online,
panel_info=panel_info,
)
@pytest.fixture(name="get_all_data", scope="package")
def get_diag_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
"""Load all data and return."""
devices = loaded_fixture["DEVICES"]
mode = loaded_fixture["MODE"]
status = loaded_fixture["STATUS"]
cycle = loaded_fixture["CYCLE"]
online = loaded_fixture["ONLINE"]
history = loaded_fixture["HISTORY"]
panel_info = loaded_fixture["PANEL INFO"]
auth_check = loaded_fixture["AUTH CHECK"]
return YaleSmartAlarmData(
devices=devices,
mode=mode,
status=status,
cycle=cycle,
online=online,
history=history,
panel_info=panel_info,
auth_check=auth_check,
)

View File

@ -1,7 +1,7 @@
# serializer version: 1 # serializer version: 1
# name: test_diagnostics # name: test_diagnostics
dict({ dict({
'AUTH CHECK': dict({ 'auth_check': dict({
'agent': False, 'agent': False,
'dealer_group': 'yale', 'dealer_group': 'yale',
'dealer_id': '605', 'dealer_id': '605',
@ -16,7 +16,7 @@
'user_id': '**REDACTED**', 'user_id': '**REDACTED**',
'xml_version': '2', 'xml_version': '2',
}), }),
'CYCLE': dict({ 'cycle': dict({
'alarm_event_latest': None, 'alarm_event_latest': None,
'capture_latest': None, 'capture_latest': None,
'device_status': list([ 'device_status': list([
@ -650,7 +650,7 @@
'utc_event_time': None, 'utc_event_time': None,
}), }),
}), }),
'DEVICES': list([ 'devices': list([
dict({ dict({
'address': '**REDACTED**', 'address': '**REDACTED**',
'area': '1', 'area': '1',
@ -1249,7 +1249,7 @@
'type_no': '40', 'type_no': '40',
}), }),
]), ]),
'HISTORY': list([ 'history': list([
dict({ dict({
'area': 1, 'area': 1,
'cid': '18180701000', 'cid': '18180701000',
@ -1391,14 +1391,14 @@
'zone': 1, 'zone': 1,
}), }),
]), ]),
'MODE': list([ 'mode': list([
dict({ dict({
'area': '1', 'area': '1',
'mode': 'disarm', 'mode': 'disarm',
}), }),
]), ]),
'ONLINE': 'online', 'online': 'online',
'PANEL INFO': dict({ 'panel_info': dict({
'SMS_Balance': '50', 'SMS_Balance': '50',
'contact': '', 'contact': '',
'dealer_name': 'Poland', 'dealer_name': 'Poland',
@ -1416,7 +1416,7 @@
'zb_version': '4.1.2.6.2', 'zb_version': '4.1.2.6.2',
'zw_version': '', 'zw_version': '',
}), }),
'STATUS': dict({ 'status': dict({
'acfail': 'main.normal', 'acfail': 'main.normal',
'battery': 'main.normal', 'battery': 'main.normal',
'gsm_rssi': '0', 'gsm_rssi': '0',

View File

@ -3,12 +3,15 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from typing import Any
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import pytest import pytest
from yalesmartalarmclient.const import YALE_STATE_ARM_FULL from yalesmartalarmclient import (
from yalesmartalarmclient.exceptions import AuthenticationError, UnknownError YALE_STATE_ARM_FULL,
AuthenticationError,
UnknownError,
YaleSmartAlarmData,
)
from homeassistant.components.yale_smart_alarm.const import DOMAIN from homeassistant.components.yale_smart_alarm.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER from homeassistant.config_entries import SOURCE_USER
@ -32,7 +35,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed
) )
async def test_coordinator_setup_errors( async def test_coordinator_setup_errors(
hass: HomeAssistant, hass: HomeAssistant,
load_json: dict[str, Any], get_data: YaleSmartAlarmData,
p_error: Exception, p_error: Exception,
) -> None: ) -> None:
"""Test the Yale Smart Living coordinator with errors.""" """Test the Yale Smart Living coordinator with errors."""
@ -64,7 +67,7 @@ async def test_coordinator_setup_errors(
async def test_coordinator_setup_and_update_errors( async def test_coordinator_setup_and_update_errors(
hass: HomeAssistant, hass: HomeAssistant,
load_config_entry: tuple[MockConfigEntry, Mock], load_config_entry: tuple[MockConfigEntry, Mock],
load_json: dict[str, Any], get_data: YaleSmartAlarmData,
) -> None: ) -> None:
"""Test the Yale Smart Living coordinator with errors.""" """Test the Yale Smart Living coordinator with errors."""
@ -74,51 +77,51 @@ async def test_coordinator_setup_and_update_errors(
assert state.state == STATE_ALARM_ARMED_AWAY assert state.state == STATE_ALARM_ARMED_AWAY
client.reset_mock() client.reset_mock()
client.get_all.side_effect = ConnectionError("Could not connect") client.get_information.side_effect = ConnectionError("Could not connect")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1))
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
client.get_all.assert_called_once() client.get_information.assert_called_once()
state = hass.states.get("alarm_control_panel.yale_smart_alarm") state = hass.states.get("alarm_control_panel.yale_smart_alarm")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
client.reset_mock() client.reset_mock()
client.get_all.side_effect = ConnectionError("Could not connect") client.get_information.side_effect = ConnectionError("Could not connect")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2))
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
client.get_all.assert_called_once() client.get_information.assert_called_once()
state = hass.states.get("alarm_control_panel.yale_smart_alarm") state = hass.states.get("alarm_control_panel.yale_smart_alarm")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
client.reset_mock() client.reset_mock()
client.get_all.side_effect = TimeoutError("Could not connect") client.get_information.side_effect = TimeoutError("Could not connect")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=3)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=3))
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
client.get_all.assert_called_once() client.get_information.assert_called_once()
state = hass.states.get("alarm_control_panel.yale_smart_alarm") state = hass.states.get("alarm_control_panel.yale_smart_alarm")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
client.reset_mock() client.reset_mock()
client.get_all.side_effect = UnknownError("info") client.get_information.side_effect = UnknownError("info")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=4)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=4))
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
client.get_all.assert_called_once() client.get_information.assert_called_once()
state = hass.states.get("alarm_control_panel.yale_smart_alarm") state = hass.states.get("alarm_control_panel.yale_smart_alarm")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
client.reset_mock() client.reset_mock()
client.get_all.side_effect = None client.get_information.side_effect = None
client.get_all.return_value = load_json client.get_information.return_value = get_data
client.get_armed_status.return_value = YALE_STATE_ARM_FULL client.get_armed_status.return_value = YALE_STATE_ARM_FULL
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
client.get_all.assert_called_once() client.get_information.assert_called_once()
state = hass.states.get("alarm_control_panel.yale_smart_alarm") state = hass.states.get("alarm_control_panel.yale_smart_alarm")
assert state.state == STATE_ALARM_ARMED_AWAY assert state.state == STATE_ALARM_ARMED_AWAY
client.reset_mock() client.reset_mock()
client.get_all.side_effect = AuthenticationError("Can not authenticate") client.get_information.side_effect = AuthenticationError("Can not authenticate")
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=6)) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=6))
await hass.async_block_till_done(wait_background_tasks=True) await hass.async_block_till_done(wait_background_tasks=True)
client.get_all.assert_called_once() client.get_information.assert_called_once()
state = hass.states.get("alarm_control_panel.yale_smart_alarm") state = hass.states.get("alarm_control_panel.yale_smart_alarm")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE

View File

@ -3,13 +3,11 @@
from __future__ import annotations from __future__ import annotations
from copy import deepcopy from copy import deepcopy
from typing import Any
from unittest.mock import Mock from unittest.mock import Mock
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from yalesmartalarmclient.exceptions import UnknownError from yalesmartalarmclient import UnknownError, YaleDoorManAPI, YaleSmartAlarmData
from yalesmartalarmclient.lock import YaleDoorManAPI
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
@ -20,7 +18,7 @@ from homeassistant.const import (
Platform, Platform,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform from tests.common import MockConfigEntry, snapshot_platform
@ -47,7 +45,7 @@ async def test_lock(
) )
async def test_lock_service_calls( async def test_lock_service_calls(
hass: HomeAssistant, hass: HomeAssistant,
load_json: dict[str, Any], get_data: YaleSmartAlarmData,
load_config_entry: tuple[MockConfigEntry, Mock], load_config_entry: tuple[MockConfigEntry, Mock],
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
@ -56,8 +54,8 @@ async def test_lock_service_calls(
client = load_config_entry[1] client = load_config_entry[1]
data = deepcopy(load_json) data = deepcopy(get_data.cycle)
data["data"] = data.pop("DEVICES") data["data"] = data.pop("device_status")
client.auth.get_authenticated = Mock(return_value=data) client.auth.get_authenticated = Mock(return_value=data)
client.auth.post_authenticated = Mock(return_value={"code": "000"}) client.auth.post_authenticated = Mock(return_value={"code": "000"})
@ -66,6 +64,14 @@ async def test_lock_service_calls(
state = hass.states.get("lock.device1") state = hass.states.get("lock.device1")
assert state.state == "locked" assert state.state == "locked"
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
LOCK_DOMAIN,
SERVICE_UNLOCK,
{ATTR_ENTITY_ID: "lock.device1"},
blocking=True,
)
await hass.services.async_call( await hass.services.async_call(
LOCK_DOMAIN, LOCK_DOMAIN,
SERVICE_UNLOCK, SERVICE_UNLOCK,
@ -93,7 +99,7 @@ async def test_lock_service_calls(
) )
async def test_lock_service_call_fails( async def test_lock_service_call_fails(
hass: HomeAssistant, hass: HomeAssistant,
load_json: dict[str, Any], get_data: YaleSmartAlarmData,
load_config_entry: tuple[MockConfigEntry, Mock], load_config_entry: tuple[MockConfigEntry, Mock],
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
@ -102,8 +108,8 @@ async def test_lock_service_call_fails(
client = load_config_entry[1] client = load_config_entry[1]
data = deepcopy(load_json) data = deepcopy(get_data.cycle)
data["data"] = data.pop("DEVICES") data["data"] = data.pop("device_status")
client.auth.get_authenticated = Mock(return_value=data) client.auth.get_authenticated = Mock(return_value=data)
client.auth.post_authenticated = Mock(side_effect=UnknownError("test_side_effect")) client.auth.post_authenticated = Mock(side_effect=UnknownError("test_side_effect"))
@ -145,7 +151,7 @@ async def test_lock_service_call_fails(
) )
async def test_lock_service_call_fails_with_incorrect_status( async def test_lock_service_call_fails_with_incorrect_status(
hass: HomeAssistant, hass: HomeAssistant,
load_json: dict[str, Any], get_data: YaleSmartAlarmData,
load_config_entry: tuple[MockConfigEntry, Mock], load_config_entry: tuple[MockConfigEntry, Mock],
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
@ -154,8 +160,8 @@ async def test_lock_service_call_fails_with_incorrect_status(
client = load_config_entry[1] client = load_config_entry[1]
data = deepcopy(load_json) data = deepcopy(get_data.cycle)
data["data"] = data.pop("DEVICES") data["data"] = data.pop("device_status")
client.auth.get_authenticated = Mock(return_value=data) client.auth.get_authenticated = Mock(return_value=data)
client.auth.post_authenticated = Mock(return_value={"code": "FFF"}) client.auth.post_authenticated = Mock(return_value={"code": "FFF"})

View File

@ -2,9 +2,10 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
from unittest.mock import Mock from unittest.mock import Mock
from yalesmartalarmclient import YaleSmartAlarmData
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -13,7 +14,7 @@ from tests.common import MockConfigEntry
async def test_coordinator_setup_and_update_errors( async def test_coordinator_setup_and_update_errors(
hass: HomeAssistant, hass: HomeAssistant,
load_config_entry: tuple[MockConfigEntry, Mock], load_config_entry: tuple[MockConfigEntry, Mock],
load_json: dict[str, Any], get_data: YaleSmartAlarmData,
) -> None: ) -> None:
"""Test the Yale Smart Living coordinator with errors.""" """Test the Yale Smart Living coordinator with errors."""