diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index a24777f9ecd..c83221b0ccf 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -7,7 +7,8 @@ import logging from aiohttp import CookieJar from aiohttp.client_exceptions import ServerDisconnectedError -from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient +from pyunifiprotect import ProtectApiClient +from pyunifiprotect.exceptions import ClientError, NotAuthorized from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -68,7 +69,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: nvr_info = await protect.get_nvr() except NotAuthorized as err: raise ConfigEntryAuthFailed(err) from err - except (asyncio.TimeoutError, NvrError, ServerDisconnectedError) as err: + except (asyncio.TimeoutError, ClientError, ServerDisconnectedError) as err: raise ConfigEntryNotReady from err if nvr_info.version < MIN_REQUIRED_PROTECT_V: diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index e183edf4259..9cd15c4e3c2 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -6,8 +6,9 @@ import logging from typing import Any from aiohttp import CookieJar -from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient +from pyunifiprotect import ProtectApiClient from pyunifiprotect.data import NVR +from pyunifiprotect.exceptions import ClientError, NotAuthorized from unifi_discovery import async_console_is_alive import voluptuous as vol @@ -253,7 +254,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): except NotAuthorized as ex: _LOGGER.debug(ex) errors[CONF_PASSWORD] = "invalid_auth" - except NvrError as ex: + except ClientError as ex: _LOGGER.debug(ex) errors["base"] = "cannot_connect" else: diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 1e9729f7930..2a30e18d586 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -6,7 +6,7 @@ from datetime import timedelta import logging from typing import Any -from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient +from pyunifiprotect import ProtectApiClient from pyunifiprotect.data import ( Bootstrap, Event, @@ -15,6 +15,7 @@ from pyunifiprotect.data import ( WSSubscriptionMessage, ) from pyunifiprotect.data.base import ProtectAdoptableDeviceModel +from pyunifiprotect.exceptions import ClientError, NotAuthorized from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback @@ -100,23 +101,27 @@ class ProtectData: try: updates = await self.api.update(force=force) - except NvrError: - if self.last_update_success: - _LOGGER.exception("Error while updating") - self.last_update_success = False - # manually trigger update to mark entities unavailable - self._async_process_updates(self.api.bootstrap) except NotAuthorized: await self.async_stop() _LOGGER.exception("Reauthentication required") self._entry.async_start_reauth(self._hass) self.last_update_success = False + except ClientError: + if self.last_update_success: + _LOGGER.exception("Error while updating") + self.last_update_success = False + # manually trigger update to mark entities unavailable + self._async_process_updates(self.api.bootstrap) else: self.last_update_success = True self._async_process_updates(updates) @callback def _async_process_ws_message(self, message: WSSubscriptionMessage) -> None: + # removed packets are not processed yet + if message.new_obj is None: # pragma: no cover + return + if message.new_obj.model in DEVICES_WITH_ENTITIES: self.async_signal_device_id_update(message.new_obj.id) # trigger update for all Cameras with LCD screens when NVR Doorbell settings updates diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 2554d12c866..dfa748835f5 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -3,7 +3,7 @@ "name": "UniFi Protect", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifiprotect", - "requirements": ["pyunifiprotect==3.9.2", "unifi-discovery==1.1.4"], + "requirements": ["pyunifiprotect==4.0.4", "unifi-discovery==1.1.4"], "dependencies": ["http"], "codeowners": ["@briis", "@AngellusMortis", "@bdraco"], "quality_scale": "platinum", diff --git a/homeassistant/components/unifiprotect/services.py b/homeassistant/components/unifiprotect/services.py index 3b7b3db026f..915c51b6c0a 100644 --- a/homeassistant/components/unifiprotect/services.py +++ b/homeassistant/components/unifiprotect/services.py @@ -8,7 +8,7 @@ from typing import Any, cast from pydantic import ValidationError from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.data import Chime -from pyunifiprotect.exceptions import BadRequest +from pyunifiprotect.exceptions import ClientError import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDeviceClass @@ -100,7 +100,7 @@ async def _async_service_call_nvr( await asyncio.gather( *(getattr(i.bootstrap.nvr, method)(*args, **kwargs) for i in instances) ) - except (BadRequest, ValidationError) as err: + except (ClientError, ValidationError) as err: raise HomeAssistantError(str(err)) from err diff --git a/requirements_all.txt b/requirements_all.txt index 013ef770615..ece350a33c6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1990,7 +1990,7 @@ pytrafikverket==0.2.0.1 pyudev==0.22.0 # homeassistant.components.unifiprotect -pyunifiprotect==3.9.2 +pyunifiprotect==4.0.4 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 660d41d66eb..d7c81558ac5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1325,7 +1325,7 @@ pytrafikverket==0.2.0.1 pyudev==0.22.0 # homeassistant.components.unifiprotect -pyunifiprotect==3.9.2 +pyunifiprotect==4.0.4 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/tests/components/unifiprotect/conftest.py b/tests/components/unifiprotect/conftest.py index 9892bcc3ec6..adc69cc8bf9 100644 --- a/tests/components/unifiprotect/conftest.py +++ b/tests/components/unifiprotect/conftest.py @@ -13,6 +13,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest from pyunifiprotect.data import ( NVR, + Bootstrap, Camera, Chime, Doorlock, @@ -39,69 +40,6 @@ from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture MAC_ADDR = "aa:bb:cc:dd:ee:ff" -@dataclass -class MockBootstrap: - """Mock for Bootstrap.""" - - nvr: NVR - cameras: dict[str, Any] - lights: dict[str, Any] - sensors: dict[str, Any] - viewers: dict[str, Any] - liveviews: dict[str, Any] - events: dict[str, Any] - doorlocks: dict[str, Any] - chimes: dict[str, Any] - - def reset_objects(self) -> None: - """Reset all devices on bootstrap for tests.""" - self.cameras = {} - self.lights = {} - self.sensors = {} - self.viewers = {} - self.liveviews = {} - self.events = {} - self.doorlocks = {} - self.chimes = {} - - def process_ws_packet(self, msg: WSSubscriptionMessage) -> None: - """Fake process method for tests.""" - pass - - def unifi_dict(self) -> dict[str, Any]: - """Return UniFi formatted dict representation of the NVR.""" - return { - "nvr": self.nvr.unifi_dict(), - "cameras": [c.unifi_dict() for c in self.cameras.values()], - "lights": [c.unifi_dict() for c in self.lights.values()], - "sensors": [c.unifi_dict() for c in self.sensors.values()], - "viewers": [c.unifi_dict() for c in self.viewers.values()], - "liveviews": [c.unifi_dict() for c in self.liveviews.values()], - "doorlocks": [c.unifi_dict() for c in self.doorlocks.values()], - "chimes": [c.unifi_dict() for c in self.chimes.values()], - } - - def get_device_from_mac(self, mac: str) -> ProtectAdoptableDeviceModel | None: - """Return device for MAC address.""" - - mac = mac.lower().replace(":", "").replace("-", "").replace("_", "") - - all_devices = ( - self.cameras.values(), - self.lights.values(), - self.sensors.values(), - self.viewers.values(), - self.liveviews.values(), - self.doorlocks.values(), - self.chimes.values(), - ) - for devices in all_devices: - for device in devices: - if device.mac.lower() == mac: - return device - return None - - @dataclass class MockEntityFixture: """Mock for NVR.""" @@ -155,27 +93,42 @@ def mock_old_nvr_fixture(): @pytest.fixture(name="mock_bootstrap") def mock_bootstrap_fixture(mock_nvr: NVR): """Mock Bootstrap fixture.""" - return MockBootstrap( - nvr=mock_nvr, - cameras={}, - lights={}, - sensors={}, - viewers={}, - liveviews={}, - events={}, - doorlocks={}, - chimes={}, - ) + data = json.loads(load_fixture("sample_bootstrap.json", integration=DOMAIN)) + data["nvr"] = mock_nvr + data["cameras"] = [] + data["lights"] = [] + data["sensors"] = [] + data["viewers"] = [] + data["liveviews"] = [] + data["events"] = [] + data["doorlocks"] = [] + data["chimes"] = [] + + return Bootstrap.from_unifi_dict(**data) + + +def reset_objects(bootstrap: Bootstrap): + """Reset bootstrap objects.""" + + bootstrap.cameras = {} + bootstrap.lights = {} + bootstrap.sensors = {} + bootstrap.viewers = {} + bootstrap.liveviews = {} + bootstrap.events = {} + bootstrap.doorlocks = {} + bootstrap.chimes = {} @pytest.fixture -def mock_client(mock_bootstrap: MockBootstrap): +def mock_client(mock_bootstrap: Bootstrap): """Mock ProtectApiClient for testing.""" client = Mock() client.bootstrap = mock_bootstrap - nvr = mock_bootstrap.nvr + nvr = client.bootstrap.nvr nvr._api = client + client.bootstrap._api = client client.base_url = "https://127.0.0.1" client.connection_host = IPv4Address("127.0.0.1") diff --git a/tests/components/unifiprotect/fixtures/sample_bootstrap.json b/tests/components/unifiprotect/fixtures/sample_bootstrap.json new file mode 100644 index 00000000000..2b7326831eb --- /dev/null +++ b/tests/components/unifiprotect/fixtures/sample_bootstrap.json @@ -0,0 +1,633 @@ +{ + "authUserId": "4c5f03a8c8bd48ad8e066285", + "accessKey": "8528571101220:340ff666bffb58bc404b859a:8f3f41a7b180b1ff7463fe4f7f13b528ac3d28668f25d0ecaa30c8e7888559e782b38d4335b40861030b75126eb7cea8385f3f9ab59dfa9a993e50757c277053", + "users": [ + { + "permissions": [], + "lastLoginIp": null, + "lastLoginTime": null, + "isOwner": true, + "enableNotifications": false, + "settings": { + "flags": {} + }, + "groups": ["b061186823695fb901973177"], + "alertRules": [], + "notificationsV2": { + "state": "custom", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [ + { + "inheritFromParent": true, + "motion": [], + "person": [], + "vehicle": [], + "camera": "61b3f5c7033ea703e7000424", + "trigger": { + "when": "always", + "location": "away", + "schedules": [] + } + } + ], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "fe4c12ae2c1348edb7854e2f", + "hasAcceptedInvite": true, + "allPermissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "nvr:write,delete:*", + "group:create,read,write,delete:*", + "user:create,read,write,delete:*", + "schedule:create,read,write,delete:*", + "legacyUFV:read,write,delete:*", + "bridge:create,read,write,delete:*", + "camera:create,read,write,delete,readmedia,deletemedia:*", + "light:create,read,write,delete:*", + "sensor:create,read,write,delete:*", + "doorlock:create,read,write,delete:*", + "viewer:create,read,write,delete:*", + "display:create,read,write,delete:*", + "chime:create,read,write,delete:*" + ], + "cloudAccount": { + "firstName": "Qpvfly", + "lastName": "Ikjzilt", + "email": "QhoFvCv@example.com", + "profileImg": null, + "user": "fe4c12ae2c1348edb7854e2f", + "id": "9efc4511-4539-4402-9581-51cee8b65cf5", + "cloudId": "9efc4511-4539-4402-9581-51cee8b65cf5", + "name": "Qpvfly Ikjzilt", + "modelKey": "cloudIdentity" + }, + "name": "Qpvfly Ikjzilt", + "firstName": "Qpvfly", + "lastName": "Ikjzilt", + "email": "QhoFvCv@example.com", + "localUsername": "QhoFvCv", + "modelKey": "user" + }, + { + "permissions": [], + "lastLoginIp": null, + "lastLoginTime": null, + "isOwner": false, + "enableNotifications": false, + "settings": null, + "groups": ["a7f3b2eb71b4c4e56f1f45ac", "b061186823695fb901973177"], + "alertRules": [], + "notificationsV2": { + "state": "auto", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "dcaef9cb8aed05c7db658a46", + "hasAcceptedInvite": false, + "allPermissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "bridge:read:*", + "camera:read,readmedia:*", + "doorlock:read:*", + "light:read:*", + "sensor:read:*", + "viewer:read:*", + "display:read:*", + "chime:read:*", + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "nvr:write,delete:*", + "group:create,read,write,delete:*", + "user:create,read,write,delete:*", + "schedule:create,read,write,delete:*", + "legacyUFV:read,write,delete:*", + "bridge:create,read,write,delete:*", + "camera:create,read,write,delete,readmedia,deletemedia:*", + "light:create,read,write,delete:*", + "sensor:create,read,write,delete:*", + "doorlock:create,read,write,delete:*", + "viewer:create,read,write,delete:*", + "display:create,read,write,delete:*", + "chime:create,read,write,delete:*" + ], + "cloudAccount": null, + "name": "Uxqg Wcbz", + "firstName": "Uxqg", + "lastName": "Wcbz", + "email": "epHDEhE@example.com", + "localUsername": "epHDEhE", + "modelKey": "user" + }, + { + "permissions": [ + "liveview:*:d65bb41c14d6aa92bfa4a6d1", + "liveview:*:49bbb5005424a0d35152671a", + "liveview:*:b28c38f1220f6b43f3930dff", + "liveview:*:b9861b533a87ea639fa4d438" + ], + "lastLoginIp": null, + "lastLoginTime": null, + "isOwner": false, + "enableNotifications": false, + "settings": { + "flags": {}, + "web": { + "dewarp": { + "61ddb66b018e2703e7008c19": { + "dewarp": false, + "state": { + "pan": 0, + "tilt": -1.5707963267948966, + "zoom": 1.5707963267948966, + "panning": 0, + "tilting": 0 + } + } + }, + "liveview.includeGlobal": true, + "elements.events_viewmode": "grid", + "elements.viewmode": "list" + } + }, + "groups": ["b061186823695fb901973177"], + "location": { + "isAway": true, + "latitude": null, + "longitude": null + }, + "alertRules": [], + "notificationsV2": { + "state": "custom", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [ + { + "inheritFromParent": true, + "motion": [], + "camera": "61b3f5c703d2a703e7000427", + "trigger": { + "when": "always", + "location": "away", + "schedules": [] + } + }, + { + "inheritFromParent": true, + "motion": [], + "person": [], + "vehicle": [], + "camera": "61b3f5c7033ea703e7000424", + "trigger": { + "when": "always", + "location": "away", + "schedules": [] + } + } + ], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "4c5f03a8c8bd48ad8e066285", + "hasAcceptedInvite": false, + "allPermissions": [ + "liveview:*:d65bb41c14d6aa92bfa4a6d1", + "liveview:*:49bbb5005424a0d35152671a", + "liveview:*:b28c38f1220f6b43f3930dff", + "liveview:*:b9861b533a87ea639fa4d438", + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "nvr:write,delete:*", + "group:create,read,write,delete:*", + "user:create,read,write,delete:*", + "schedule:create,read,write,delete:*", + "legacyUFV:read,write,delete:*", + "bridge:create,read,write,delete:*", + "camera:create,read,write,delete,readmedia,deletemedia:*", + "light:create,read,write,delete:*", + "sensor:create,read,write,delete:*", + "doorlock:create,read,write,delete:*", + "viewer:create,read,write,delete:*", + "display:create,read,write,delete:*", + "chime:create,read,write,delete:*" + ], + "cloudAccount": null, + "name": "Ptcmsdo Tfiyoep", + "firstName": "Ptcmsdo", + "lastName": "Tfiyoep", + "email": "EQAoXL@example.com", + "localUsername": "EQAoXL", + "modelKey": "user" + }, + { + "permissions": [], + "lastLoginIp": null, + "lastLoginTime": null, + "isOwner": false, + "enableNotifications": false, + "settings": { + "flags": {}, + "web": { + "dewarp": { + "61c4d1db02c82a03e700429c": { + "dewarp": false, + "state": { + "pan": 0, + "tilt": 0, + "zoom": 1.5707963267948966, + "panning": 0, + "tilting": 0 + } + } + }, + "liveview.includeGlobal": true + } + }, + "groups": ["a7f3b2eb71b4c4e56f1f45ac"], + "alertRules": [], + "notificationsV2": { + "state": "auto", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "bc3dd633553907952a6fe20d", + "hasAcceptedInvite": false, + "allPermissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "bridge:read:*", + "camera:read,readmedia:*", + "doorlock:read:*", + "light:read:*", + "sensor:read:*", + "viewer:read:*", + "display:read:*", + "chime:read:*" + ], + "cloudAccount": null, + "name": "Evdxou Zgyv", + "firstName": "Evdxou", + "lastName": "Zgyv", + "email": "FMZuD@example.com", + "localUsername": "FMZuD", + "modelKey": "user" + }, + { + "permissions": [], + "lastLoginIp": null, + "lastLoginTime": null, + "isOwner": false, + "enableNotifications": false, + "settings": null, + "groups": ["a7f3b2eb71b4c4e56f1f45ac", "b061186823695fb901973177"], + "alertRules": [], + "notificationsV2": { + "state": "auto", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "adec5334b69f56f6a6c47520", + "hasAcceptedInvite": false, + "allPermissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "bridge:read:*", + "camera:read,readmedia:*", + "doorlock:read:*", + "light:read:*", + "sensor:read:*", + "viewer:read:*", + "display:read:*", + "chime:read:*", + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "nvr:write,delete:*", + "group:create,read,write,delete:*", + "user:create,read,write,delete:*", + "schedule:create,read,write,delete:*", + "legacyUFV:read,write,delete:*", + "bridge:create,read,write,delete:*", + "camera:create,read,write,delete,readmedia,deletemedia:*", + "light:create,read,write,delete:*", + "sensor:create,read,write,delete:*", + "doorlock:create,read,write,delete:*", + "viewer:create,read,write,delete:*", + "display:create,read,write,delete:*", + "chime:create,read,write,delete:*" + ], + "cloudAccount": null, + "name": "Qpv Elqfgq", + "firstName": "Qpv", + "lastName": "Elqfgq", + "email": "xdr@example.com", + "localUsername": "xdr", + "modelKey": "user" + }, + { + "permissions": [], + "lastLoginIp": null, + "lastLoginTime": null, + "isOwner": false, + "enableNotifications": false, + "settings": null, + "groups": ["a7f3b2eb71b4c4e56f1f45ac", "b061186823695fb901973177"], + "alertRules": [], + "notificationsV2": { + "state": "auto", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "8593657a25b7826a4288b6af", + "hasAcceptedInvite": false, + "allPermissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "bridge:read:*", + "camera:read,readmedia:*", + "doorlock:read:*", + "light:read:*", + "sensor:read:*", + "viewer:read:*", + "display:read:*", + "chime:read:*", + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "nvr:write,delete:*", + "group:create,read,write,delete:*", + "user:create,read,write,delete:*", + "schedule:create,read,write,delete:*", + "legacyUFV:read,write,delete:*", + "bridge:create,read,write,delete:*", + "camera:create,read,write,delete,readmedia,deletemedia:*", + "light:create,read,write,delete:*", + "sensor:create,read,write,delete:*", + "doorlock:create,read,write,delete:*", + "viewer:create,read,write,delete:*", + "display:create,read,write,delete:*", + "chime:create,read,write,delete:*" + ], + "cloudAccount": null, + "name": "Sgpy Ooevsme", + "firstName": "Sgpy", + "lastName": "Ooevsme", + "email": "WQJNT@example.com", + "localUsername": "WQJNT", + "modelKey": "user" + }, + { + "permissions": [], + "isOwner": false, + "enableNotifications": false, + "groups": ["a7f3b2eb71b4c4e56f1f45ac"], + "alertRules": [], + "notificationsV2": { + "state": "off", + "motionNotifications": { + "trigger": { + "when": "inherit", + "location": "away", + "schedules": [] + }, + "cameras": [], + "doorbells": [], + "lights": [], + "doorlocks": [], + "sensors": [] + }, + "systemNotifications": {} + }, + "featureFlags": { + "notificationsV2": true + }, + "id": "abf647aed3650a781ceba13f", + "hasAcceptedInvite": false, + "allPermissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "bridge:read:*", + "camera:read,readmedia:*", + "doorlock:read:*", + "light:read:*", + "sensor:read:*", + "viewer:read:*", + "display:read:*", + "chime:read:*" + ], + "cloudAccount": null, + "name": "Yiiyq Glx", + "firstName": "Yiiyq", + "lastName": "Glx", + "email": "fBjmm@example.com", + "localUsername": "fBjmm", + "modelKey": "user" + } + ], + "groups": [ + { + "name": "Kubw Xnbb", + "permissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "nvr:write,delete:*", + "group:create,read,write,delete:*", + "user:create,read,write,delete:*", + "schedule:create,read,write,delete:*", + "legacyUFV:read,write,delete:*", + "bridge:create,read,write,delete:*", + "camera:create,read,write,delete,readmedia,deletemedia:*", + "light:create,read,write,delete:*", + "sensor:create,read,write,delete:*", + "doorlock:create,read,write,delete:*", + "viewer:create,read,write,delete:*", + "display:create,read,write,delete:*", + "chime:create,read,write,delete:*" + ], + "type": "preset", + "isDefault": true, + "id": "b061186823695fb901973177", + "modelKey": "group" + }, + { + "name": "Pmbrvp Wyzqs", + "permissions": [ + "nvr:read:*", + "liveview:create", + "user:read,write,delete:$", + "bridge:read:*", + "camera:read,readmedia:*", + "doorlock:read:*", + "light:read:*", + "sensor:read:*", + "viewer:read:*", + "display:read:*", + "chime:read:*" + ], + "type": "preset", + "isDefault": false, + "id": "a7f3b2eb71b4c4e56f1f45ac", + "modelKey": "group" + } + ], + "schedules": [], + "legacyUFVs": [], + "lastUpdateId": "ebf25bac-d5a1-4f1d-a0ee-74c15981eb70", + "displays": [], + "bridges": [ + { + "mac": "A28D0DB15AE1", + "host": "192.168.231.68", + "connectionHost": "192.168.102.63", + "type": "UFP-UAP-B", + "name": "Sffde Gxcaqe", + "upSince": 1639807977891, + "uptime": 3247782, + "lastSeen": 1643055759891, + "connectedSince": 1642374159304, + "state": "CONNECTED", + "hardwareRevision": 19, + "firmwareVersion": "0.3.1", + "latestFirmwareVersion": null, + "firmwareBuild": null, + "isUpdating": false, + "isAdopting": false, + "isAdopted": true, + "isAdoptedByOther": false, + "isProvisioned": false, + "isRebooting": false, + "isSshEnabled": false, + "canAdopt": false, + "isAttemptingToConnect": false, + "wiredConnectionState": { + "phyRate": null + }, + "id": "1f5a055254fb9169d7536fb9", + "isConnected": true, + "platform": "mt7621", + "modelKey": "bridge" + }, + { + "mac": "C65C557CCA95", + "host": "192.168.87.68", + "connectionHost": "192.168.102.63", + "type": "UFP-UAP-B", + "name": "Axiwj Bbd", + "upSince": 1641257260772, + "uptime": null, + "lastSeen": 1643052750862, + "connectedSince": 1643052754695, + "state": "CONNECTED", + "hardwareRevision": 19, + "firmwareVersion": "0.3.1", + "latestFirmwareVersion": null, + "firmwareBuild": null, + "isUpdating": false, + "isAdopting": false, + "isAdopted": true, + "isAdoptedByOther": false, + "isProvisioned": false, + "isRebooting": false, + "isSshEnabled": false, + "canAdopt": false, + "isAttemptingToConnect": false, + "wiredConnectionState": { + "phyRate": null + }, + "id": "e6901e3665a4c0eab0d9c1a5", + "isConnected": true, + "platform": "mt7621", + "modelKey": "bridge" + } + ] +} diff --git a/tests/components/unifiprotect/test_binary_sensor.py b/tests/components/unifiprotect/test_binary_sensor.py index 8fbaf61aca1..644665cc659 100644 --- a/tests/components/unifiprotect/test_binary_sensor.py +++ b/tests/components/unifiprotect/test_binary_sensor.py @@ -36,6 +36,7 @@ from .conftest import ( MockEntityFixture, assert_entity_counts, ids_from_device_description, + reset_objects, ) @@ -51,7 +52,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -62,7 +63,7 @@ async def camera_fixture( camera_obj.is_dark = False camera_obj.is_motion_detected = False - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, @@ -87,14 +88,14 @@ async def light_fixture( # disable pydantic validation so mocking can happen Light.__config__.validate_assignment = False - light_obj = mock_light.copy(deep=True) + light_obj = mock_light.copy() light_obj._api = mock_entry.api light_obj.name = "Test Light" light_obj.is_dark = False light_obj.is_pir_motion_detected = False light_obj.last_motion = now - timedelta(hours=1) - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] mock_entry.api.bootstrap.lights = { light_obj.id: light_obj, @@ -119,7 +120,7 @@ async def camera_none_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -129,7 +130,7 @@ async def camera_none_fixture( camera_obj.is_dark = False camera_obj.is_motion_detected = False - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, @@ -157,7 +158,7 @@ async def sensor_fixture( # disable pydantic validation so mocking can happen Sensor.__config__.validate_assignment = False - sensor_obj = mock_sensor.copy(deep=True) + sensor_obj = mock_sensor.copy() sensor_obj._api = mock_entry.api sensor_obj.name = "Test Sensor" sensor_obj.mount_type = MountType.DOOR @@ -170,7 +171,7 @@ async def sensor_fixture( sensor_obj.alarm_triggered_at = now - timedelta(hours=1) sensor_obj.tampering_detected_at = None - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] mock_entry.api.bootstrap.sensors = { sensor_obj.id: sensor_obj, @@ -198,7 +199,7 @@ async def sensor_none_fixture( # disable pydantic validation so mocking can happen Sensor.__config__.validate_assignment = False - sensor_obj = mock_sensor.copy(deep=True) + sensor_obj = mock_sensor.copy() sensor_obj._api = mock_entry.api sensor_obj.name = "Test Sensor" sensor_obj.mount_type = MountType.LEAK @@ -206,7 +207,7 @@ async def sensor_none_fixture( sensor_obj.alarm_settings.is_enabled = False sensor_obj.tampering_detected_at = None - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] mock_entry.api.bootstrap.sensors = { sensor_obj.id: sensor_obj, diff --git a/tests/components/unifiprotect/test_button.py b/tests/components/unifiprotect/test_button.py index 5b7122f6227..9a1c7009660 100644 --- a/tests/components/unifiprotect/test_button.py +++ b/tests/components/unifiprotect/test_button.py @@ -21,7 +21,7 @@ async def chime_fixture( ): """Fixture for a single camera for testing the button platform.""" - chime_obj = mock_chime.copy(deep=True) + chime_obj = mock_chime.copy() chime_obj._api = mock_entry.api chime_obj.name = "Test Chime" diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index 2f8d2607da0..03b52c7e52e 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -52,7 +52,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen ProtectCamera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -83,7 +83,7 @@ async def camera_package_fixture( ): """Fixture for a single camera for testing the camera platform.""" - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -95,7 +95,7 @@ async def camera_package_fixture( camera_obj.channels[0].rtsp_alias = "test_high_alias" camera_obj.channels[1].is_rtsp_enabled = False camera_obj.channels[2].is_rtsp_enabled = False - package_channel = camera_obj.channels[0].copy(deep=True) + package_channel = camera_obj.channels[0].copy() package_channel.is_rtsp_enabled = False package_channel.name = "Package Camera" package_channel.id = 3 @@ -246,8 +246,9 @@ async def test_basic_setup( ): """Test working setup of unifiprotect entry.""" - camera_high_only = mock_camera.copy(deep=True) + camera_high_only = mock_camera.copy() camera_high_only._api = mock_entry.api + camera_high_only.channels = [c.copy() for c in mock_camera.channels] camera_high_only.channels[0]._api = mock_entry.api camera_high_only.channels[1]._api = mock_entry.api camera_high_only.channels[2]._api = mock_entry.api @@ -259,8 +260,9 @@ async def test_basic_setup( camera_high_only.channels[2].is_rtsp_enabled = False regenerate_device_ids(camera_high_only) - camera_medium_only = mock_camera.copy(deep=True) + camera_medium_only = mock_camera.copy() camera_medium_only._api = mock_entry.api + camera_medium_only.channels = [c.copy() for c in mock_camera.channels] camera_medium_only.channels[0]._api = mock_entry.api camera_medium_only.channels[1]._api = mock_entry.api camera_medium_only.channels[2]._api = mock_entry.api @@ -272,8 +274,9 @@ async def test_basic_setup( camera_medium_only.channels[2].is_rtsp_enabled = False regenerate_device_ids(camera_medium_only) - camera_all_channels = mock_camera.copy(deep=True) + camera_all_channels = mock_camera.copy() camera_all_channels._api = mock_entry.api + camera_all_channels.channels = [c.copy() for c in mock_camera.channels] camera_all_channels.channels[0]._api = mock_entry.api camera_all_channels.channels[1]._api = mock_entry.api camera_all_channels.channels[2]._api = mock_entry.api @@ -289,8 +292,9 @@ async def test_basic_setup( camera_all_channels.channels[2].rtsp_alias = "test_low_alias" regenerate_device_ids(camera_all_channels) - camera_no_channels = mock_camera.copy(deep=True) + camera_no_channels = mock_camera.copy() camera_no_channels._api = mock_entry.api + camera_no_channels.channels = [c.copy() for c in camera_no_channels.channels] camera_no_channels.channels[0]._api = mock_entry.api camera_no_channels.channels[1]._api = mock_entry.api camera_no_channels.channels[2]._api = mock_entry.api @@ -301,8 +305,9 @@ async def test_basic_setup( camera_no_channels.channels[2].is_rtsp_enabled = False regenerate_device_ids(camera_no_channels) - camera_package = mock_camera.copy(deep=True) + camera_package = mock_camera.copy() camera_package._api = mock_entry.api + camera_package.channels = [c.copy() for c in mock_camera.channels] camera_package.channels[0]._api = mock_entry.api camera_package.channels[1]._api = mock_entry.api camera_package.channels[2]._api = mock_entry.api @@ -313,7 +318,7 @@ async def test_basic_setup( camera_package.channels[1].is_rtsp_enabled = False camera_package.channels[2].is_rtsp_enabled = False regenerate_device_ids(camera_package) - package_channel = camera_package.channels[0].copy(deep=True) + package_channel = camera_package.channels[0].copy() package_channel.is_rtsp_enabled = False package_channel.name = "Package Camera" package_channel.id = 3 @@ -398,7 +403,7 @@ async def test_missing_channels( ): """Test setting up camera with no camera channels.""" - camera = mock_camera.copy(deep=True) + camera = mock_camera.copy() camera.channels = [] mock_entry.api.bootstrap.cameras = {camera.id: camera} diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 23dfa12fc97..d36183ba135 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, patch import aiohttp from pyunifiprotect import NotAuthorized, NvrError -from pyunifiprotect.data import NVR, Light +from pyunifiprotect.data import NVR, Bootstrap, Light from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP, DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigEntryState @@ -16,7 +16,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from . import _patch_discovery -from .conftest import MockBootstrap, MockEntityFixture, regenerate_device_ids +from .conftest import MockEntityFixture, regenerate_device_ids from tests.common import MockConfigEntry @@ -52,7 +52,7 @@ async def test_setup_multiple( hass: HomeAssistant, mock_entry: MockEntityFixture, mock_client, - mock_bootstrap: MockBootstrap, + mock_bootstrap: Bootstrap, ): """Test working setup of unifiprotect entry.""" diff --git a/tests/components/unifiprotect/test_light.py b/tests/components/unifiprotect/test_light.py index a3686fdfbd9..c4f324f30fd 100644 --- a/tests/components/unifiprotect/test_light.py +++ b/tests/components/unifiprotect/test_light.py @@ -32,7 +32,7 @@ async def light_fixture( # disable pydantic validation so mocking can happen Light.__config__.validate_assignment = False - light_obj = mock_light.copy(deep=True) + light_obj = mock_light.copy() light_obj._api = mock_entry.api light_obj.name = "Test Light" light_obj.is_light_on = False diff --git a/tests/components/unifiprotect/test_lock.py b/tests/components/unifiprotect/test_lock.py index abcea4ec04e..36b3d140871 100644 --- a/tests/components/unifiprotect/test_lock.py +++ b/tests/components/unifiprotect/test_lock.py @@ -35,7 +35,7 @@ async def doorlock_fixture( # disable pydantic validation so mocking can happen Doorlock.__config__.validate_assignment = False - lock_obj = mock_doorlock.copy(deep=True) + lock_obj = mock_doorlock.copy() lock_obj._api = mock_entry.api lock_obj.name = "Test Lock" lock_obj.lock_status = LockStatusType.OPEN diff --git a/tests/components/unifiprotect/test_media_player.py b/tests/components/unifiprotect/test_media_player.py index d6404ee3fe5..a4cbc9e8d22 100644 --- a/tests/components/unifiprotect/test_media_player.py +++ b/tests/components/unifiprotect/test_media_player.py @@ -38,7 +38,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api diff --git a/tests/components/unifiprotect/test_number.py b/tests/components/unifiprotect/test_number.py index f516ad64a0b..043feae7925 100644 --- a/tests/components/unifiprotect/test_number.py +++ b/tests/components/unifiprotect/test_number.py @@ -23,6 +23,7 @@ from .conftest import ( MockEntityFixture, assert_entity_counts, ids_from_device_description, + reset_objects, ) @@ -35,13 +36,13 @@ async def light_fixture( # disable pydantic validation so mocking can happen Light.__config__.validate_assignment = False - light_obj = mock_light.copy(deep=True) + light_obj = mock_light.copy() light_obj._api = mock_entry.api light_obj.name = "Test Light" light_obj.light_device_settings.pir_sensitivity = 45 light_obj.light_device_settings.pir_duration = timedelta(seconds=45) - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.lights = { light_obj.id: light_obj, } @@ -65,7 +66,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -79,7 +80,7 @@ async def camera_fixture( camera_obj.mic_volume = 0 camera_obj.isp_settings.zoom_position = 0 - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } @@ -103,12 +104,12 @@ async def doorlock_fixture( # disable pydantic validation so mocking can happen Doorlock.__config__.validate_assignment = False - lock_obj = mock_doorlock.copy(deep=True) + lock_obj = mock_doorlock.copy() lock_obj._api = mock_entry.api lock_obj.name = "Test Lock" lock_obj.auto_close_time = timedelta(seconds=45) - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.doorlocks = { lock_obj.id: lock_obj, } @@ -174,7 +175,7 @@ async def test_number_setup_camera_none( ): """Test number entity setup for camera devices (no features).""" - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -185,7 +186,7 @@ async def test_number_setup_camera_none( # has_wdr is an the inverse of has HDR camera_obj.feature_flags.has_hdr = True - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } @@ -204,7 +205,7 @@ async def test_number_setup_camera_missing_attr( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -214,7 +215,7 @@ async def test_number_setup_camera_missing_attr( Camera.__config__.validate_assignment = True - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } diff --git a/tests/components/unifiprotect/test_select.py b/tests/components/unifiprotect/test_select.py index fc0abbe29ca..01263a13cd9 100644 --- a/tests/components/unifiprotect/test_select.py +++ b/tests/components/unifiprotect/test_select.py @@ -44,6 +44,7 @@ from .conftest import ( MockEntityFixture, assert_entity_counts, ids_from_device_description, + reset_objects, ) @@ -59,12 +60,12 @@ async def viewer_fixture( # disable pydantic validation so mocking can happen Viewer.__config__.validate_assignment = False - viewer_obj = mock_viewer.copy(deep=True) + viewer_obj = mock_viewer.copy() viewer_obj._api = mock_entry.api viewer_obj.name = "Test Viewer" viewer_obj.liveview_id = mock_liveview.id - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.viewers = { viewer_obj.id: viewer_obj, } @@ -89,7 +90,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -102,7 +103,7 @@ async def camera_fixture( camera_obj.lcd_message = None camera_obj.chime_duration = 0 - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } @@ -129,14 +130,14 @@ async def light_fixture( # disable pydantic validation so mocking can happen Light.__config__.validate_assignment = False - light_obj = mock_light.copy(deep=True) + light_obj = mock_light.copy() light_obj._api = mock_entry.api light_obj.name = "Test Light" light_obj.camera_id = None light_obj.light_mode_settings.mode = LightModeType.MOTION light_obj.light_mode_settings.enable_at = LightModeEnableType.DARK - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = {camera.id: camera} mock_entry.api.bootstrap.lights = { light_obj.id: light_obj, @@ -161,7 +162,7 @@ async def camera_none_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -172,7 +173,7 @@ async def camera_none_fixture( camera_obj.recording_settings.mode = RecordingMode.ALWAYS camera_obj.isp_settings.ir_led_mode = IRLEDMode.AUTO - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index 4f2540b28c4..eb2558aae3d 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -47,6 +47,7 @@ from .conftest import ( assert_entity_counts, enable_entity, ids_from_device_description, + reset_objects, time_changed, ) @@ -63,7 +64,7 @@ async def sensor_fixture( # disable pydantic validation so mocking can happen Sensor.__config__.validate_assignment = False - sensor_obj = mock_sensor.copy(deep=True) + sensor_obj = mock_sensor.copy() sensor_obj._api = mock_entry.api sensor_obj.name = "Test Sensor" sensor_obj.battery_status.percentage = 10.0 @@ -77,7 +78,7 @@ async def sensor_fixture( sensor_obj.up_since = now sensor_obj.bluetooth_connection_state.signal_strength = -50.0 - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.sensors = { sensor_obj.id: sensor_obj, } @@ -102,7 +103,7 @@ async def sensor_none_fixture( # disable pydantic validation so mocking can happen Sensor.__config__.validate_assignment = False - sensor_obj = mock_sensor.copy(deep=True) + sensor_obj = mock_sensor.copy() sensor_obj._api = mock_entry.api sensor_obj.name = "Test Sensor" sensor_obj.battery_status.percentage = 10.0 @@ -113,7 +114,7 @@ async def sensor_none_fixture( sensor_obj.up_since = now sensor_obj.bluetooth_connection_state.signal_strength = -50.0 - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.sensors = { sensor_obj.id: sensor_obj, } @@ -141,7 +142,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -162,7 +163,7 @@ async def camera_fixture( camera_obj.stats.storage.rate = 0.1 camera_obj.voltage = 20.0 - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, @@ -262,7 +263,7 @@ async def test_sensor_setup_nvr( ): """Test sensor entity setup for NVR device.""" - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) nvr: NVR = mock_entry.api.bootstrap.nvr nvr.up_since = now nvr.system_info.cpu.average_load = 50.0 @@ -339,7 +340,7 @@ async def test_sensor_nvr_missing_values( ): """Test NVR sensor sensors if no data available.""" - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) nvr: NVR = mock_entry.api.bootstrap.nvr nvr.system_info.memory.available = None nvr.system_info.memory.total = None diff --git a/tests/components/unifiprotect/test_services.py b/tests/components/unifiprotect/test_services.py index 2ad3821cc40..d957fe16b4b 100644 --- a/tests/components/unifiprotect/test_services.py +++ b/tests/components/unifiprotect/test_services.py @@ -5,8 +5,8 @@ from __future__ import annotations from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Light, ModelType -from pyunifiprotect.data.devices import Chime +from pyunifiprotect.data import Camera, Chime, Light, ModelType +from pyunifiprotect.data.bootstrap import ProtectDeviceRef from pyunifiprotect.exceptions import BadRequest from homeassistant.components.unifiprotect.const import ATTR_MESSAGE, DOMAIN @@ -163,6 +163,11 @@ async def test_set_chime_paired_doorbells( mock_entry.api.bootstrap.chimes = { mock_chime.id: mock_chime, } + mock_entry.api.bootstrap.mac_lookup = { + mock_chime.mac.lower(): ProtectDeviceRef( + model=mock_chime.model, id=mock_chime.id + ) + } camera1 = mock_camera.copy() camera1.name = "Test Camera 1" @@ -186,6 +191,12 @@ async def test_set_chime_paired_doorbells( camera1.id: camera1, camera2.id: camera2, } + mock_entry.api.bootstrap.mac_lookup[camera1.mac.lower()] = ProtectDeviceRef( + model=camera1.model, id=camera1.id + ) + mock_entry.api.bootstrap.mac_lookup[camera2.mac.lower()] = ProtectDeviceRef( + model=camera2.model, id=camera2.id + ) await hass.config_entries.async_setup(mock_entry.entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index 8ca1ef9b533..1bd6dbeb349 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -28,6 +28,7 @@ from .conftest import ( assert_entity_counts, enable_entity, ids_from_device_description, + reset_objects, ) CAMERA_SWITCHES_BASIC = [ @@ -51,13 +52,13 @@ async def light_fixture( # disable pydantic validation so mocking can happen Light.__config__.validate_assignment = False - light_obj = mock_light.copy(deep=True) + light_obj = mock_light.copy() light_obj._api = mock_entry.api light_obj.name = "Test Light" light_obj.is_ssh_enabled = False light_obj.light_device_settings.is_indicator_enabled = False - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.lights = { light_obj.id: light_obj, } @@ -81,7 +82,7 @@ async def camera_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -110,7 +111,7 @@ async def camera_fixture( camera_obj.osd_settings.is_debug_enabled = False camera_obj.smart_detect_settings.object_types = [] - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } @@ -134,7 +135,7 @@ async def camera_none_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -153,7 +154,7 @@ async def camera_none_fixture( camera_obj.osd_settings.is_logo_enabled = False camera_obj.osd_settings.is_debug_enabled = False - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, } @@ -177,7 +178,8 @@ async def camera_privacy_fixture( # disable pydantic validation so mocking can happen Camera.__config__.validate_assignment = False - camera_obj = mock_camera.copy(deep=True) + # mock_camera._update_lock = None + camera_obj = mock_camera.copy() camera_obj._api = mock_entry.api camera_obj.channels[0]._api = mock_entry.api camera_obj.channels[1]._api = mock_entry.api @@ -197,7 +199,7 @@ async def camera_privacy_fixture( camera_obj.osd_settings.is_logo_enabled = False camera_obj.osd_settings.is_debug_enabled = False - mock_entry.api.bootstrap.reset_objects() + reset_objects(mock_entry.api.bootstrap) mock_entry.api.bootstrap.cameras = { camera_obj.id: camera_obj, }