From e63122437273bd49550f6498310201eaee6df844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=20Bj=C3=B6rck?= Date: Sun, 10 Mar 2024 09:11:10 +0100 Subject: [PATCH] Bump yalexs to 2.0.0 (#111706) Co-authored-by: J. Nick Koston --- homeassistant/components/august/__init__.py | 7 +++ homeassistant/components/august/camera.py | 35 ++++++++++-- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/august/mocks.py | 50 +++++++++++----- tests/components/august/test_camera.py | 57 ++++++++++++++++++- 7 files changed, 133 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 82552ffa3d6..2db2b173f6b 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -306,6 +306,13 @@ class AugustData(AugustSubscriberMixin): exc_info=err, ) + async def refresh_camera_by_id(self, device_id: str) -> None: + """Re-fetch doorbell/camera data from API.""" + await self._async_update_device_detail( + self._doorbells_by_id[device_id], + self._api.async_get_doorbell_detail, + ) + async def _async_refresh_device_detail_by_id(self, device_id: str) -> None: if device_id in self._locks_by_id: if self.activity_stream and self.activity_stream.pubnub.connected: diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index f5380ce6732..188a55bd4b9 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -2,9 +2,12 @@ from __future__ import annotations +import logging + from aiohttp import ClientSession from yalexs.activity import ActivityType -from yalexs.doorbell import Doorbell +from yalexs.const import Brand +from yalexs.doorbell import ContentTokenExpired, Doorbell from yalexs.util import update_doorbell_image_from_activity from homeassistant.components.camera import Camera @@ -17,6 +20,8 @@ from . import AugustData from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN from .entity import AugustEntityMixin +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, @@ -48,6 +53,7 @@ class AugustCamera(AugustEntityMixin, Camera): self._timeout = timeout self._session = session self._image_url = None + self._content_token = None self._image_content = None self._attr_unique_id = f"{self._device_id:s}_camera" self._attr_motion_detection_enabled = True @@ -63,6 +69,12 @@ class AugustCamera(AugustEntityMixin, Camera): """Return the camera model.""" return self._detail.model + async def _async_update(self): + """Update device.""" + _LOGGER.debug("async_update called %s", self._detail.device_name) + await self._data.refresh_camera_by_id(self._device_id) + self._update_from_data() + @callback def _update_from_data(self) -> None: """Get the latest state of the sensor.""" @@ -70,7 +82,6 @@ class AugustCamera(AugustEntityMixin, Camera): self._device_id, {ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE}, ) - if doorbell_activity is not None: update_doorbell_image_from_activity(self._detail, doorbell_activity) @@ -82,7 +93,23 @@ class AugustCamera(AugustEntityMixin, Camera): if self._image_url is not self._detail.image_url: self._image_url = self._detail.image_url - self._image_content = await self._detail.async_get_doorbell_image( - self._session, timeout=self._timeout + self._content_token = self._detail.content_token or self._content_token + _LOGGER.debug( + "calling doorbell async_get_doorbell_image, %s", + self._detail.device_name, ) + try: + self._image_content = await self._detail.async_get_doorbell_image( + self._session, timeout=self._timeout + ) + except ContentTokenExpired: + if self._data.brand == Brand.YALE_HOME: + _LOGGER.debug( + "Error fetching camera image, updating content-token from api to retry" + ) + await self._async_update() + self._image_content = await self._detail.async_get_doorbell_image( + self._session, timeout=self._timeout + ) + return self._image_content diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 83685e6e6a1..27c5f11ec6e 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -28,5 +28,5 @@ "documentation": "https://www.home-assistant.io/integrations/august", "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], - "requirements": ["yalexs==1.11.4", "yalexs-ble==2.4.2"] + "requirements": ["yalexs==2.0.0", "yalexs-ble==2.4.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index bb22d21f40d..73440b45d50 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2893,7 +2893,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.2 # homeassistant.components.august -yalexs==1.11.4 +yalexs==2.0.0 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a584d53ec81..8fc51989fbf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2228,7 +2228,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.2 # homeassistant.components.august -yalexs==1.11.4 +yalexs==2.0.0 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 08677764f35..4e017066e90 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -26,11 +26,12 @@ from yalexs.activity import ( LockOperationActivity, ) from yalexs.authenticator import AuthenticationState +from yalexs.const import Brand 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.components.august.const import CONF_BRAND, CONF_LOGIN_METHOD, DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -38,13 +39,14 @@ from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture -def _mock_get_config(): +def _mock_get_config(brand: Brand = Brand.AUGUST): """Return a default august config.""" return { DOMAIN: { CONF_LOGIN_METHOD: "email", CONF_USERNAME: "mocked_username", CONF_PASSWORD: "mocked_password", + CONF_BRAND: brand, } } @@ -59,7 +61,7 @@ def _mock_authenticator(auth_state): @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 + hass, api_instance, pubnub_mock, authenticate_mock, api_mock, brand ): """Set up august integration.""" authenticate_mock.side_effect = MagicMock( @@ -70,7 +72,7 @@ async def _mock_setup_august( api_mock.return_value = api_instance entry = MockConfigEntry( domain=DOMAIN, - data=_mock_get_config()[DOMAIN], + data=_mock_get_config(brand)[DOMAIN], options={}, ) entry.add_to_hass(hass) @@ -88,21 +90,26 @@ async def _create_august_with_devices( api_call_side_effects: dict[str, Any] | None = None, activities: list[Any] | None = None, pubnub: AugustPubNub | None = None, + brand: Brand = Brand.AUGUST, ) -> ConfigEntry: entry, _ = await _create_august_api_with_devices( - hass, devices, api_call_side_effects, activities, pubnub + hass, devices, api_call_side_effects, activities, pubnub, brand ) return entry async def _create_august_api_with_devices( # noqa: C901 - hass, devices, api_call_side_effects=None, activities=None, pubnub=None + hass, + devices, + api_call_side_effects=None, + activities=None, + pubnub=None, + brand=Brand.AUGUST, ): 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): @@ -111,7 +118,13 @@ async def _create_august_api_with_devices( # noqa: C901 ) elif isinstance(device, DoorbellDetail): device_data["doorbells"].append( - {"base": _mock_august_doorbell(device.device_id), "detail": device} + { + "base": _mock_august_doorbell( + deviceid=device.device_id, + brand=device._data.get("brand", Brand.AUGUST), + ), + "detail": device, + } ) else: raise ValueError # noqa: TRY004 @@ -182,7 +195,7 @@ async def _create_august_api_with_devices( # noqa: C901 ) api_instance, entry = await _mock_setup_august_with_api_side_effects( - hass, api_call_side_effects, pubnub + hass, api_call_side_effects, pubnub, brand ) if device_data["locks"]: @@ -193,7 +206,9 @@ async def _create_august_api_with_devices( # noqa: C901 return entry, api_instance -async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub): +async def _mock_setup_august_with_api_side_effects( + hass, api_call_side_effects, pubnub, brand=Brand.AUGUST +): api_instance = MagicMock(name="Api") if api_call_side_effects["get_lock_detail"]: @@ -236,7 +251,9 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, 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) + return api_instance, await _mock_setup_august( + hass, api_instance, pubnub, brand=brand + ) def _mock_august_authentication(token_text, token_timestamp, state): @@ -253,13 +270,18 @@ 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"): +def _mock_august_doorbell( + deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST +): return Doorbell( - deviceid, _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid) + deviceid, + _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid, brand=brand), ) -def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1"): +def _mock_august_doorbell_data( + deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST +): return { "_id": deviceid, "DeviceID": deviceid, diff --git a/tests/components/august/test_camera.py b/tests/components/august/test_camera.py index e19b935b672..539a26cc30f 100644 --- a/tests/components/august/test_camera.py +++ b/tests/components/august/test_camera.py @@ -3,6 +3,9 @@ from http import HTTPStatus from unittest.mock import patch +from yalexs.const import Brand +from yalexs.doorbell import ContentTokenExpired + from homeassistant.const import STATE_IDLE from homeassistant.core import HomeAssistant @@ -20,7 +23,7 @@ async def test_create_doorbell( with patch.object( doorbell_one, "async_get_doorbell_image", create=False, return_value="image" ): - await _create_august_with_devices(hass, [doorbell_one]) + await _create_august_with_devices(hass, [doorbell_one], brand=Brand.AUGUST) camera_k98gidt45gul_name_camera = hass.states.get( "camera.k98gidt45gul_name_camera" @@ -36,3 +39,55 @@ async def test_create_doorbell( assert resp.status == HTTPStatus.OK body = await resp.text() assert body == "image" + + +async def test_doorbell_refresh_content_token_recover( + hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator +) -> None: + """Test camera image content token expired.""" + doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + with patch.object( + doorbell_two, + "async_get_doorbell_image", + create=False, + side_effect=[ContentTokenExpired, "image"], + ): + await _create_august_with_devices( + hass, + [doorbell_two], + brand=Brand.YALE_HOME, + ) + url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[ + "entity_picture" + ] + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.OK + body = await resp.text() + assert body == "image" + + +async def test_doorbell_refresh_content_token_fail( + hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator +) -> None: + """Test camera image content token expired.""" + doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + with patch.object( + doorbell_two, + "async_get_doorbell_image", + create=False, + side_effect=ContentTokenExpired, + ): + await _create_august_with_devices( + hass, + [doorbell_two], + brand=Brand.YALE_HOME, + ) + url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[ + "entity_picture" + ] + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR