From e67f8720e81e8618370aaf3e6a36b8b1553b9e9a Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sat, 25 Jun 2022 11:15:38 -0400 Subject: [PATCH] Refactor UniFi Protect tests (#73971) Co-authored-by: J. Nick Koston --- tests/components/unifiprotect/conftest.py | 327 +++++++------ .../unifiprotect/fixtures/sample_camera.json | 54 +-- .../unifiprotect/fixtures/sample_chime.json | 2 +- .../fixtures/sample_doorlock.json | 6 +- .../unifiprotect/fixtures/sample_light.json | 14 +- .../unifiprotect/fixtures/sample_sensor.json | 24 +- .../unifiprotect/test_binary_sensor.py | 320 +++---------- tests/components/unifiprotect/test_button.py | 45 +- tests/components/unifiprotect/test_camera.py | 408 ++++++----------- .../unifiprotect/test_config_flow.py | 54 ++- .../unifiprotect/test_diagnostics.py | 47 +- tests/components/unifiprotect/test_init.py | 165 +++---- tests/components/unifiprotect/test_light.py | 98 ++-- tests/components/unifiprotect/test_lock.py | 174 ++++--- .../unifiprotect/test_media_player.py | 197 ++++---- tests/components/unifiprotect/test_migrate.py | 222 ++++----- tests/components/unifiprotect/test_number.py | 192 ++------ tests/components/unifiprotect/test_select.py | 429 +++++++----------- tests/components/unifiprotect/test_sensor.py | 371 ++++++--------- .../components/unifiprotect/test_services.py | 96 +--- tests/components/unifiprotect/test_switch.py | 416 ++++++----------- tests/components/unifiprotect/utils.py | 168 +++++++ 22 files changed, 1535 insertions(+), 2294 deletions(-) create mode 100644 tests/components/unifiprotect/utils.py diff --git a/tests/components/unifiprotect/conftest.py b/tests/components/unifiprotect/conftest.py index 68945ac0988..51cef190e2f 100644 --- a/tests/components/unifiprotect/conftest.py +++ b/tests/components/unifiprotect/conftest.py @@ -3,14 +3,14 @@ from __future__ import annotations from collections.abc import Callable -from dataclasses import dataclass -from datetime import timedelta +from datetime import datetime, timedelta from ipaddress import IPv4Address import json from typing import Any from unittest.mock import AsyncMock, Mock, patch import pytest +from pyunifiprotect import ProtectApiClient from pyunifiprotect.data import ( NVR, Bootstrap, @@ -19,37 +19,27 @@ from pyunifiprotect.data import ( Doorlock, Light, Liveview, - ProtectAdoptableDeviceModel, Sensor, + SmartDetectObjectType, + VideoMode, Viewer, WSSubscriptionMessage, ) -from pyunifiprotect.test_util.anonymize import random_hex from homeassistant.components.unifiprotect.const import DOMAIN -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant, split_entity_id -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.entity import EntityDescription +from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util from . import _patch_discovery +from .utils import MockUFPFixture -from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture +from tests.common import MockConfigEntry, load_fixture MAC_ADDR = "aa:bb:cc:dd:ee:ff" -@dataclass -class MockEntityFixture: - """Mock for NVR.""" - - entry: MockConfigEntry - api: Mock - - -@pytest.fixture(name="mock_nvr") -def mock_nvr_fixture(): +@pytest.fixture(name="nvr") +def mock_nvr(): """Mock UniFi Protect Camera device.""" data = json.loads(load_fixture("sample_nvr.json", integration=DOMAIN)) @@ -63,7 +53,7 @@ def mock_nvr_fixture(): NVR.__config__.validate_assignment = True -@pytest.fixture(name="mock_ufp_config_entry") +@pytest.fixture(name="ufp_config_entry") def mock_ufp_config_entry(): """Mock the unifiprotect config entry.""" @@ -81,8 +71,8 @@ def mock_ufp_config_entry(): ) -@pytest.fixture(name="mock_old_nvr") -def mock_old_nvr_fixture(): +@pytest.fixture(name="old_nvr") +def old_nvr(): """Mock UniFi Protect Camera device.""" data = json.loads(load_fixture("sample_nvr.json", integration=DOMAIN)) @@ -90,11 +80,11 @@ def mock_old_nvr_fixture(): return NVR.from_unifi_dict(**data) -@pytest.fixture(name="mock_bootstrap") -def mock_bootstrap_fixture(mock_nvr: NVR): +@pytest.fixture(name="bootstrap") +def bootstrap_fixture(nvr: NVR): """Mock Bootstrap fixture.""" data = json.loads(load_fixture("sample_bootstrap.json", integration=DOMAIN)) - data["nvr"] = mock_nvr + data["nvr"] = nvr data["cameras"] = [] data["lights"] = [] data["sensors"] = [] @@ -107,24 +97,11 @@ def mock_bootstrap_fixture(mock_nvr: NVR): 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: Bootstrap): +@pytest.fixture(name="ufp_client") +def mock_ufp_client(bootstrap: Bootstrap): """Mock ProtectApiClient for testing.""" client = Mock() - client.bootstrap = mock_bootstrap + client.bootstrap = bootstrap nvr = client.bootstrap.nvr nvr._api = client @@ -133,161 +110,227 @@ def mock_client(mock_bootstrap: Bootstrap): client.base_url = "https://127.0.0.1" client.connection_host = IPv4Address("127.0.0.1") client.get_nvr = AsyncMock(return_value=nvr) - client.update = AsyncMock(return_value=mock_bootstrap) + client.update = AsyncMock(return_value=bootstrap) client.async_disconnect_ws = AsyncMock() - - def subscribe(ws_callback: Callable[[WSSubscriptionMessage], None]) -> Any: - client.ws_subscription = ws_callback - - return Mock() - - client.subscribe_websocket = subscribe return client -@pytest.fixture +@pytest.fixture(name="ufp") def mock_entry( - hass: HomeAssistant, - mock_ufp_config_entry: MockConfigEntry, - mock_client, # pylint: disable=redefined-outer-name + hass: HomeAssistant, ufp_config_entry: MockConfigEntry, ufp_client: ProtectApiClient ): """Mock ProtectApiClient for testing.""" with _patch_discovery(no_device=True), patch( "homeassistant.components.unifiprotect.ProtectApiClient" ) as mock_api: - mock_ufp_config_entry.add_to_hass(hass) + ufp_config_entry.add_to_hass(hass) - mock_api.return_value = mock_client + mock_api.return_value = ufp_client - yield MockEntityFixture(mock_ufp_config_entry, mock_client) + ufp = MockUFPFixture(ufp_config_entry, ufp_client) + + def subscribe(ws_callback: Callable[[WSSubscriptionMessage], None]) -> Any: + ufp.ws_subscription = ws_callback + return Mock() + + ufp_client.subscribe_websocket = subscribe + yield ufp @pytest.fixture -def mock_liveview(): +def liveview(): """Mock UniFi Protect Liveview.""" data = json.loads(load_fixture("sample_liveview.json", integration=DOMAIN)) return Liveview.from_unifi_dict(**data) -@pytest.fixture -def mock_camera(): +@pytest.fixture(name="camera") +def camera_fixture(fixed_now: datetime): """Mock UniFi Protect Camera device.""" + # disable pydantic validation so mocking can happen + Camera.__config__.validate_assignment = False + data = json.loads(load_fixture("sample_camera.json", integration=DOMAIN)) - return Camera.from_unifi_dict(**data) + camera = Camera.from_unifi_dict(**data) + camera.last_motion = fixed_now - timedelta(hours=1) + + yield camera + + Camera.__config__.validate_assignment = True + + +@pytest.fixture(name="camera_all") +def camera_all_fixture(camera: Camera): + """Mock UniFi Protect Camera device.""" + + all_camera = camera.copy() + all_camera.channels = [all_camera.channels[0].copy()] + + medium_channel = all_camera.channels[0].copy() + medium_channel.name = "Medium" + medium_channel.id = 1 + medium_channel.rtsp_alias = "test_medium_alias" + all_camera.channels.append(medium_channel) + + low_channel = all_camera.channels[0].copy() + low_channel.name = "Low" + low_channel.id = 2 + low_channel.rtsp_alias = "test_medium_alias" + all_camera.channels.append(low_channel) + + return all_camera + + +@pytest.fixture(name="doorbell") +def doorbell_fixture(camera: Camera, fixed_now: datetime): + """Mock UniFi Protect Camera device (with chime).""" + + doorbell = camera.copy() + doorbell.channels = [c.copy() for c in doorbell.channels] + + package_channel = doorbell.channels[0].copy() + package_channel.name = "Package Camera" + package_channel.id = 3 + package_channel.fps = 2 + package_channel.rtsp_alias = "test_package_alias" + + doorbell.channels.append(package_channel) + doorbell.feature_flags.video_modes = [VideoMode.DEFAULT, VideoMode.HIGH_FPS] + doorbell.feature_flags.smart_detect_types = [ + SmartDetectObjectType.PERSON, + SmartDetectObjectType.VEHICLE, + ] + doorbell.feature_flags.has_hdr = True + doorbell.feature_flags.has_lcd_screen = True + doorbell.feature_flags.has_speaker = True + doorbell.feature_flags.has_privacy_mask = True + doorbell.feature_flags.has_chime = True + doorbell.feature_flags.has_smart_detect = True + doorbell.feature_flags.has_package_camera = True + doorbell.feature_flags.has_led_status = True + doorbell.last_ring = fixed_now - timedelta(hours=1) + return doorbell @pytest.fixture -def mock_light(): +def unadopted_camera(camera: Camera): + """Mock UniFi Protect Camera device (unadopted).""" + + no_camera = camera.copy() + no_camera.channels = [c.copy() for c in no_camera.channels] + no_camera.name = "Unadopted Camera" + no_camera.is_adopted = False + return no_camera + + +@pytest.fixture(name="light") +def light_fixture(): """Mock UniFi Protect Light device.""" + # disable pydantic validation so mocking can happen + Light.__config__.validate_assignment = False + data = json.loads(load_fixture("sample_light.json", integration=DOMAIN)) - return Light.from_unifi_dict(**data) + yield Light.from_unifi_dict(**data) + + Light.__config__.validate_assignment = True @pytest.fixture -def mock_viewer(): +def unadopted_light(light: Light): + """Mock UniFi Protect Light device (unadopted).""" + + no_light = light.copy() + no_light.name = "Unadopted Light" + no_light.is_adopted = False + return no_light + + +@pytest.fixture +def viewer(): """Mock UniFi Protect Viewport device.""" + # disable pydantic validation so mocking can happen + Viewer.__config__.validate_assignment = False + data = json.loads(load_fixture("sample_viewport.json", integration=DOMAIN)) - return Viewer.from_unifi_dict(**data) + yield Viewer.from_unifi_dict(**data) + + Viewer.__config__.validate_assignment = True -@pytest.fixture -def mock_sensor(): +@pytest.fixture(name="sensor") +def sensor_fixture(fixed_now: datetime): """Mock UniFi Protect Sensor device.""" + # disable pydantic validation so mocking can happen + Sensor.__config__.validate_assignment = False + data = json.loads(load_fixture("sample_sensor.json", integration=DOMAIN)) - return Sensor.from_unifi_dict(**data) + sensor: Sensor = Sensor.from_unifi_dict(**data) + sensor.motion_detected_at = fixed_now - timedelta(hours=1) + sensor.open_status_changed_at = fixed_now - timedelta(hours=1) + sensor.alarm_triggered_at = fixed_now - timedelta(hours=1) + yield sensor + + Sensor.__config__.validate_assignment = True -@pytest.fixture -def mock_doorlock(): +@pytest.fixture(name="sensor_all") +def csensor_all_fixture(sensor: Sensor): + """Mock UniFi Protect Sensor device.""" + + all_sensor = sensor.copy() + all_sensor.light_settings.is_enabled = True + all_sensor.humidity_settings.is_enabled = True + all_sensor.temperature_settings.is_enabled = True + all_sensor.alarm_settings.is_enabled = True + all_sensor.led_settings.is_enabled = True + all_sensor.motion_settings.is_enabled = True + + return all_sensor + + +@pytest.fixture(name="doorlock") +def doorlock_fixture(): """Mock UniFi Protect Doorlock device.""" + # disable pydantic validation so mocking can happen + Doorlock.__config__.validate_assignment = False + data = json.loads(load_fixture("sample_doorlock.json", integration=DOMAIN)) - return Doorlock.from_unifi_dict(**data) + yield Doorlock.from_unifi_dict(**data) + + Doorlock.__config__.validate_assignment = True @pytest.fixture -def mock_chime(): +def unadopted_doorlock(doorlock: Doorlock): + """Mock UniFi Protect Light device (unadopted).""" + + no_doorlock = doorlock.copy() + no_doorlock.name = "Unadopted Lock" + no_doorlock.is_adopted = False + return no_doorlock + + +@pytest.fixture +def chime(): """Mock UniFi Protect Chime device.""" + # disable pydantic validation so mocking can happen + Chime.__config__.validate_assignment = False + data = json.loads(load_fixture("sample_chime.json", integration=DOMAIN)) - return Chime.from_unifi_dict(**data) + yield Chime.from_unifi_dict(**data) + + Chime.__config__.validate_assignment = True -@pytest.fixture -def now(): +@pytest.fixture(name="fixed_now") +def fixed_now_fixture(): """Return datetime object that will be consistent throughout test.""" return dt_util.utcnow() - - -async def time_changed(hass: HomeAssistant, seconds: int) -> None: - """Trigger time changed.""" - next_update = dt_util.utcnow() + timedelta(seconds) - async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() - - -async def enable_entity( - hass: HomeAssistant, entry_id: str, entity_id: str -) -> er.RegistryEntry: - """Enable a disabled entity.""" - entity_registry = er.async_get(hass) - - updated_entity = entity_registry.async_update_entity(entity_id, disabled_by=None) - assert not updated_entity.disabled - await hass.config_entries.async_reload(entry_id) - await hass.async_block_till_done() - - return updated_entity - - -def assert_entity_counts( - hass: HomeAssistant, platform: Platform, total: int, enabled: int -) -> None: - """Assert entity counts for a given platform.""" - - entity_registry = er.async_get(hass) - - entities = [ - e for e in entity_registry.entities if split_entity_id(e)[0] == platform.value - ] - - assert len(entities) == total - assert len(hass.states.async_all(platform.value)) == enabled - - -def ids_from_device_description( - platform: Platform, - device: ProtectAdoptableDeviceModel, - description: EntityDescription, -) -> tuple[str, str]: - """Return expected unique_id and entity_id for a give platform/device/description combination.""" - - entity_name = ( - device.name.lower().replace(":", "").replace(" ", "_").replace("-", "_") - ) - description_entity_name = ( - description.name.lower().replace(":", "").replace(" ", "_").replace("-", "_") - ) - - unique_id = f"{device.mac}_{description.key}" - entity_id = f"{platform.value}.{entity_name}_{description_entity_name}" - - return unique_id, entity_id - - -def generate_random_ids() -> tuple[str, str]: - """Generate random IDs for device.""" - - return random_hex(24).lower(), random_hex(12).upper() - - -def regenerate_device_ids(device: ProtectAdoptableDeviceModel) -> None: - """Regenerate the IDs on UFP device.""" - - device.id, device.mac = generate_random_ids() diff --git a/tests/components/unifiprotect/fixtures/sample_camera.json b/tests/components/unifiprotect/fixtures/sample_camera.json index eb07c6df63b..e7ffbd0abcc 100644 --- a/tests/components/unifiprotect/fixtures/sample_camera.json +++ b/tests/components/unifiprotect/fixtures/sample_camera.json @@ -4,7 +4,7 @@ "host": "192.168.6.90", "connectionHost": "192.168.178.217", "type": "UVC G4 Instant", - "name": "Fufail Qqjx", + "name": "Test Camera", "upSince": 1640020678036, "uptime": 3203, "lastSeen": 1640023881036, @@ -20,18 +20,18 @@ "isAdoptedByOther": false, "isProvisioned": true, "isRebooting": false, - "isSshEnabled": true, + "isSshEnabled": false, "canAdopt": false, "isAttemptingToConnect": false, "lastMotion": 1640021213927, - "micVolume": 100, + "micVolume": 0, "isMicEnabled": true, "isRecording": false, "isWirelessUplinkEnabled": true, "isMotionDetected": false, "isSmartDetected": false, "phyRate": 72, - "hdrMode": true, + "hdrMode": false, "videoMode": "default", "isProbingForWifi": false, "apMac": null, @@ -57,18 +57,18 @@ } }, "videoReconfigurationInProgress": false, - "voltage": null, + "voltage": 20.0, "wiredConnectionState": { - "phyRate": null + "phyRate": 1000 }, "channels": [ { "id": 0, "videoId": "video1", - "name": "Jzi Bftu", + "name": "High", "enabled": true, "isRtspEnabled": true, - "rtspAlias": "ANOAPfoKMW7VixG1", + "rtspAlias": "test_high_alias", "width": 2688, "height": 1512, "fps": 30, @@ -83,10 +83,10 @@ { "id": 1, "videoId": "video2", - "name": "Rgcpxsf Xfwt", + "name": "Medium", "enabled": true, - "isRtspEnabled": true, - "rtspAlias": "XHXAdHVKGVEzMNTP", + "isRtspEnabled": false, + "rtspAlias": null, "width": 1280, "height": 720, "fps": 30, @@ -101,7 +101,7 @@ { "id": 2, "videoId": "video3", - "name": "Umefvk Fug", + "name": "Low", "enabled": true, "isRtspEnabled": false, "rtspAlias": null, @@ -121,7 +121,7 @@ "aeMode": "auto", "irLedMode": "auto", "irLedLevel": 255, - "wdr": 1, + "wdr": 0, "icrSensitivity": 0, "brightness": 50, "contrast": 50, @@ -161,8 +161,8 @@ "quality": 100 }, "osdSettings": { - "isNameEnabled": true, - "isDateEnabled": true, + "isNameEnabled": false, + "isDateEnabled": false, "isLogoEnabled": false, "isDebugEnabled": false }, @@ -181,7 +181,7 @@ "minMotionEventTrigger": 1000, "endMotionEventDelay": 3000, "suppressIlluminationSurge": false, - "mode": "detections", + "mode": "always", "geofencing": "off", "motionAlgorithm": "enhanced", "enablePirTimelapse": false, @@ -223,8 +223,8 @@ ], "smartDetectLines": [], "stats": { - "rxBytes": 33684237, - "txBytes": 1208318620, + "rxBytes": 100, + "txBytes": 100, "wifi": { "channel": 6, "frequency": 2437, @@ -248,8 +248,8 @@ "timelapseEndLQ": 1640021765237 }, "storage": { - "used": 20401094656, - "rate": 693.424269097809 + "used": 100, + "rate": 0.1 }, "wifiQuality": 100, "wifiStrength": -35 @@ -257,7 +257,7 @@ "featureFlags": { "canAdjustIrLedLevel": false, "canMagicZoom": false, - "canOpticalZoom": false, + "canOpticalZoom": true, "canTouchFocus": false, "hasAccelerometer": true, "hasAec": true, @@ -268,15 +268,15 @@ "hasIcrSensitivity": true, "hasLdc": false, "hasLedIr": true, - "hasLedStatus": true, + "hasLedStatus": false, "hasLineIn": false, "hasMic": true, - "hasPrivacyMask": true, + "hasPrivacyMask": false, "hasRtc": false, "hasSdCard": false, - "hasSpeaker": true, + "hasSpeaker": false, "hasWifi": true, - "hasHdr": true, + "hasHdr": false, "hasAutoICROnly": true, "videoModes": ["default"], "videoModeMaxFps": [], @@ -353,14 +353,14 @@ "frequency": 2437, "phyRate": 72, "signalQuality": 100, - "signalStrength": -35, + "signalStrength": -50, "ssid": "Mortis Camera" }, "lenses": [], "id": "0de062b4f6922d489d3b312d", "isConnected": true, "platform": "sav530q", - "hasSpeaker": true, + "hasSpeaker": false, "hasWifi": true, "audioBitrate": 64000, "canManage": false, diff --git a/tests/components/unifiprotect/fixtures/sample_chime.json b/tests/components/unifiprotect/fixtures/sample_chime.json index 975cfcebaea..4a2637fc700 100644 --- a/tests/components/unifiprotect/fixtures/sample_chime.json +++ b/tests/components/unifiprotect/fixtures/sample_chime.json @@ -3,7 +3,7 @@ "host": "192.168.144.146", "connectionHost": "192.168.234.27", "type": "UP Chime", - "name": "Xaorvu Tvsv", + "name": "Test Chime", "upSince": 1651882870009, "uptime": 567870, "lastSeen": 1652450740009, diff --git a/tests/components/unifiprotect/fixtures/sample_doorlock.json b/tests/components/unifiprotect/fixtures/sample_doorlock.json index 12cd7858e9d..a2e2ba0ab89 100644 --- a/tests/components/unifiprotect/fixtures/sample_doorlock.json +++ b/tests/components/unifiprotect/fixtures/sample_doorlock.json @@ -3,7 +3,7 @@ "host": null, "connectionHost": "192.168.102.63", "type": "UFP-LOCK-R", - "name": "Wkltg Qcjxv", + "name": "Test Lock", "upSince": 1643050461849, "uptime": null, "lastSeen": 1643052750858, @@ -23,9 +23,9 @@ "canAdopt": false, "isAttemptingToConnect": false, "credentials": "955756200c7f43936df9d5f7865f058e1528945aac0f0cb27cef960eb58f17db", - "lockStatus": "CLOSING", + "lockStatus": "OPEN", "enableHomekit": false, - "autoCloseTimeMs": 15000, + "autoCloseTimeMs": 45000, "wiredConnectionState": { "phyRate": null }, diff --git a/tests/components/unifiprotect/fixtures/sample_light.json b/tests/components/unifiprotect/fixtures/sample_light.json index ed0f89f3a11..ce7de9e852c 100644 --- a/tests/components/unifiprotect/fixtures/sample_light.json +++ b/tests/components/unifiprotect/fixtures/sample_light.json @@ -3,7 +3,7 @@ "host": "192.168.10.86", "connectionHost": "192.168.178.217", "type": "UP FloodLight", - "name": "Byyfbpe Ufoka", + "name": "Test Light", "upSince": 1638128991022, "uptime": 1894890, "lastSeen": 1640023881022, @@ -19,7 +19,7 @@ "isAdoptedByOther": false, "isProvisioned": false, "isRebooting": false, - "isSshEnabled": true, + "isSshEnabled": false, "canAdopt": false, "isAttemptingToConnect": false, "isPirMotionDetected": false, @@ -31,20 +31,20 @@ "phyRate": 100 }, "lightDeviceSettings": { - "isIndicatorEnabled": true, + "isIndicatorEnabled": false, "ledLevel": 6, "luxSensitivity": "medium", - "pirDuration": 120000, - "pirSensitivity": 46 + "pirDuration": 45000, + "pirSensitivity": 45 }, "lightOnSettings": { "isLedForceOn": false }, "lightModeSettings": { - "mode": "off", + "mode": "motion", "enableAt": "fulltime" }, - "camera": "193be66559c03ec5629f54cd", + "camera": null, "id": "37dd610720816cfb5c547967", "isConnected": true, "isCameraPaired": true, diff --git a/tests/components/unifiprotect/fixtures/sample_sensor.json b/tests/components/unifiprotect/fixtures/sample_sensor.json index 08ce9a17be2..cbba1f7583e 100644 --- a/tests/components/unifiprotect/fixtures/sample_sensor.json +++ b/tests/components/unifiprotect/fixtures/sample_sensor.json @@ -2,7 +2,7 @@ "mac": "26DBAFF133A4", "connectionHost": "192.168.216.198", "type": "UFP-SENSE", - "name": "Egdczv Urg", + "name": "Test Sensor", "upSince": 1641256963255, "uptime": null, "lastSeen": 1641259127934, @@ -25,7 +25,7 @@ "mountType": "door", "leakDetectedAt": null, "tamperingDetectedAt": null, - "isOpened": true, + "isOpened": false, "openStatusChangedAt": 1641269036582, "alarmTriggeredAt": null, "motionDetectedAt": 1641269044824, @@ -34,53 +34,53 @@ }, "stats": { "light": { - "value": 0, + "value": 10.0, "status": "neutral" }, "humidity": { - "value": 35, + "value": 10.0, "status": "neutral" }, "temperature": { - "value": 17.23, + "value": 10.0, "status": "neutral" } }, "bluetoothConnectionState": { "signalQuality": 15, - "signalStrength": -84 + "signalStrength": -50 }, "batteryStatus": { - "percentage": 100, + "percentage": 10, "isLow": false }, "alarmSettings": { "isEnabled": false }, "lightSettings": { - "isEnabled": true, + "isEnabled": false, "lowThreshold": null, "highThreshold": null, "margin": 10 }, "motionSettings": { - "isEnabled": true, + "isEnabled": false, "sensitivity": 100 }, "temperatureSettings": { - "isEnabled": true, + "isEnabled": false, "lowThreshold": null, "highThreshold": null, "margin": 0.1 }, "humiditySettings": { - "isEnabled": true, + "isEnabled": false, "lowThreshold": null, "highThreshold": null, "margin": 1 }, "ledSettings": { - "isEnabled": true + "isEnabled": false }, "bridge": "61b3f5c90050a703e700042a", "camera": "2f9beb2e6f79af3c32c22d49", diff --git a/tests/components/unifiprotect/test_binary_sensor.py b/tests/components/unifiprotect/test_binary_sensor.py index da9969ad868..856c034905f 100644 --- a/tests/components/unifiprotect/test_binary_sensor.py +++ b/tests/components/unifiprotect/test_binary_sensor.py @@ -2,11 +2,9 @@ # pylint: disable=protected-access from __future__ import annotations -from copy import copy from datetime import datetime, timedelta from unittest.mock import Mock -import pytest from pyunifiprotect.data import Camera, Event, EventType, Light, MountType, Sensor from pyunifiprotect.data.nvr import EventMetadata @@ -32,218 +30,25 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import ( - MockEntityFixture, +from .utils import ( + MockUFPFixture, assert_entity_counts, ids_from_device_description, - regenerate_device_ids, - reset_objects, + init_entry, ) LIGHT_SENSOR_WRITE = LIGHT_SENSORS[:2] SENSE_SENSORS_WRITE = SENSE_SENSORS[:4] -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_camera: Camera, - now: datetime, -): - """Fixture for a single camera for testing the binary_sensor platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_chime = True - camera_obj.last_ring = now - timedelta(hours=1) - camera_obj.is_dark = False - camera_obj.is_motion_detected = False - regenerate_device_ids(camera_obj) - - no_camera_obj = mock_camera.copy() - no_camera_obj._api = mock_entry.api - no_camera_obj.channels[0]._api = mock_entry.api - no_camera_obj.channels[1]._api = mock_entry.api - no_camera_obj.channels[2]._api = mock_entry.api - no_camera_obj.name = "Unadopted Camera" - no_camera_obj.is_adopted = False - regenerate_device_ids(no_camera_obj) - - 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, - no_camera_obj.id: no_camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.BINARY_SENSOR, 9, 9) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - -@pytest.fixture(name="light") -async def light_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light, now: datetime -): - """Fixture for a single light for testing the binary_sensor platform.""" - - # disable pydantic validation so mocking can happen - Light.__config__.validate_assignment = False - - 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) - - 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, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.BINARY_SENSOR, 8, 8) - - yield light_obj - - Light.__config__.validate_assignment = True - - -@pytest.fixture(name="camera_none") -async def camera_none_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the binary_sensor platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_chime = False - camera_obj.is_dark = False - camera_obj.is_motion_detected = False - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.nvr.system_info.storage.devices = [] - mock_entry.api.bootstrap.nvr.system_info.ustorage = None - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.BINARY_SENSOR, 2, 2) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - -@pytest.fixture(name="sensor") -async def sensor_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_sensor: Sensor, - now: datetime, -): - """Fixture for a single sensor for testing the binary_sensor platform.""" - - # disable pydantic validation so mocking can happen - Sensor.__config__.validate_assignment = False - - sensor_obj = mock_sensor.copy() - sensor_obj._api = mock_entry.api - sensor_obj.name = "Test Sensor" - sensor_obj.mount_type = MountType.DOOR - sensor_obj.is_opened = False - sensor_obj.battery_status.is_low = False - sensor_obj.is_motion_detected = False - sensor_obj.alarm_settings.is_enabled = True - sensor_obj.motion_detected_at = now - timedelta(hours=1) - sensor_obj.open_status_changed_at = now - timedelta(hours=1) - sensor_obj.alarm_triggered_at = now - timedelta(hours=1) - sensor_obj.tampering_detected_at = None - - 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, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10) - - yield sensor_obj - - Sensor.__config__.validate_assignment = True - - -@pytest.fixture(name="sensor_none") -async def sensor_none_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_sensor: Sensor, - now: datetime, -): - """Fixture for a single sensor for testing the binary_sensor platform.""" - - # disable pydantic validation so mocking can happen - Sensor.__config__.validate_assignment = False - - sensor_obj = mock_sensor.copy() - sensor_obj._api = mock_entry.api - sensor_obj.name = "Test Sensor" - sensor_obj.mount_type = MountType.LEAK - sensor_obj.battery_status.is_low = False - sensor_obj.alarm_settings.is_enabled = False - sensor_obj.tampering_detected_at = None - - 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, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10) - - yield sensor_obj - - Sensor.__config__.validate_assignment = True - - async def test_binary_sensor_setup_light( - hass: HomeAssistant, light: Light, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test binary_sensor entity setup for light devices.""" + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 8, 8) + entity_registry = er.async_get(hass) for description in LIGHT_SENSOR_WRITE: @@ -262,15 +67,22 @@ async def test_binary_sensor_setup_light( async def test_binary_sensor_setup_camera_all( - hass: HomeAssistant, camera: Camera, now: datetime + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test binary_sensor entity setup for camera devices (all features).""" + ufp.api.bootstrap.nvr.system_info.ustorage = None + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 3, 3) + entity_registry = er.async_get(hass) description = CAMERA_SENSORS[0] unique_id, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, camera, description + Platform.BINARY_SENSOR, doorbell, description ) entity = entity_registry.async_get(entity_id) @@ -285,7 +97,7 @@ async def test_binary_sensor_setup_camera_all( # Is Dark description = CAMERA_SENSORS[1] unique_id, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, camera, description + Platform.BINARY_SENSOR, doorbell, description ) entity = entity_registry.async_get(entity_id) @@ -300,7 +112,7 @@ async def test_binary_sensor_setup_camera_all( # Motion description = MOTION_SENSORS[0] unique_id, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, camera, description + Platform.BINARY_SENSOR, doorbell, description ) entity = entity_registry.async_get(entity_id) @@ -315,16 +127,19 @@ async def test_binary_sensor_setup_camera_all( async def test_binary_sensor_setup_camera_none( - hass: HomeAssistant, - camera_none: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, camera: Camera ): """Test binary_sensor entity setup for camera devices (no features).""" + ufp.api.bootstrap.nvr.system_info.ustorage = None + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 2, 2) + entity_registry = er.async_get(hass) description = CAMERA_SENSORS[1] unique_id, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, camera_none, description + Platform.BINARY_SENSOR, camera, description ) entity = entity_registry.async_get(entity_id) @@ -338,15 +153,18 @@ async def test_binary_sensor_setup_camera_none( async def test_binary_sensor_setup_sensor( - hass: HomeAssistant, sensor: Sensor, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, sensor_all: Sensor ): """Test binary_sensor entity setup for sensor devices.""" + await init_entry(hass, ufp, [sensor_all]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10) + entity_registry = er.async_get(hass) for description in SENSE_SENSORS_WRITE: unique_id, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, sensor, description + Platform.BINARY_SENSOR, sensor_all, description ) entity = entity_registry.async_get(entity_id) @@ -360,10 +178,14 @@ async def test_binary_sensor_setup_sensor( async def test_binary_sensor_setup_sensor_none( - hass: HomeAssistant, sensor_none: Sensor + hass: HomeAssistant, ufp: MockUFPFixture, sensor: Sensor ): """Test binary_sensor entity setup for sensor with most sensors disabled.""" + sensor.mount_type = MountType.LEAK + await init_entry(hass, ufp, [sensor]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10) + entity_registry = er.async_get(hass) expected = [ @@ -374,7 +196,7 @@ async def test_binary_sensor_setup_sensor_none( ] for index, description in enumerate(SENSE_SENSORS_WRITE): unique_id, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, sensor_none, description + Platform.BINARY_SENSOR, sensor, description ) entity = entity_registry.async_get(entity_id) @@ -388,27 +210,33 @@ async def test_binary_sensor_setup_sensor_none( async def test_binary_sensor_update_motion( - hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera, now: datetime + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, + fixed_now: datetime, ): """Test binary_sensor motion entity.""" + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 9, 9) + _, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, camera, MOTION_SENSORS[0] + Platform.BINARY_SENSOR, doorbell, MOTION_SENSORS[0] ) event = Event( id="test_event_id", type=EventType.MOTION, - start=now - timedelta(seconds=1), + start=fixed_now - timedelta(seconds=1), end=None, score=100, smart_detect_types=[], smart_detect_event_ids=[], - camera_id=camera.id, + camera_id=doorbell.id, ) - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera.copy() + new_camera = doorbell.copy() new_camera.is_motion_detected = True new_camera.last_motion_event_id = event.id @@ -416,10 +244,9 @@ async def test_binary_sensor_update_motion( mock_msg.changed_data = {} mock_msg.new_obj = new_camera - new_bootstrap.cameras = {new_camera.id: new_camera} - new_bootstrap.events = {event.id: event} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.api.bootstrap.events = {event.id: event} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -430,10 +257,13 @@ async def test_binary_sensor_update_motion( async def test_binary_sensor_update_light_motion( - hass: HomeAssistant, mock_entry: MockEntityFixture, light: Light, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, fixed_now: datetime ): """Test binary_sensor motion entity.""" + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 8, 8) + _, entity_id = ids_from_device_description( Platform.BINARY_SENSOR, light, LIGHT_SENSOR_WRITE[1] ) @@ -442,16 +272,15 @@ async def test_binary_sensor_update_light_motion( event = Event( id="test_event_id", type=EventType.MOTION_LIGHT, - start=now - timedelta(seconds=1), + start=fixed_now - timedelta(seconds=1), end=None, score=100, smart_detect_types=[], smart_detect_event_ids=[], metadata=event_metadata, - api=mock_entry.api, + api=ufp.api, ) - new_bootstrap = copy(mock_entry.api.bootstrap) new_light = light.copy() new_light.is_pir_motion_detected = True new_light.last_motion_event_id = event.id @@ -460,10 +289,9 @@ async def test_binary_sensor_update_light_motion( mock_msg.changed_data = {} mock_msg.new_obj = event - new_bootstrap.lights = {new_light.id: new_light} - new_bootstrap.events = {event.id: event} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.lights = {new_light.id: new_light} + ufp.api.bootstrap.events = {event.id: event} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -472,29 +300,30 @@ async def test_binary_sensor_update_light_motion( async def test_binary_sensor_update_mount_type_window( - hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor + hass: HomeAssistant, ufp: MockUFPFixture, sensor_all: Sensor ): """Test binary_sensor motion entity.""" + await init_entry(hass, ufp, [sensor_all]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10) + _, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, sensor, SENSE_SENSORS_WRITE[0] + Platform.BINARY_SENSOR, sensor_all, SENSE_SENSORS_WRITE[0] ) state = hass.states.get(entity_id) assert state assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.DOOR.value - new_bootstrap = copy(mock_entry.api.bootstrap) - new_sensor = sensor.copy() + new_sensor = sensor_all.copy() new_sensor.mount_type = MountType.WINDOW mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_sensor - new_bootstrap.sensors = {new_sensor.id: new_sensor} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.sensors = {new_sensor.id: new_sensor} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -503,29 +332,30 @@ async def test_binary_sensor_update_mount_type_window( async def test_binary_sensor_update_mount_type_garage( - hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor + hass: HomeAssistant, ufp: MockUFPFixture, sensor_all: Sensor ): """Test binary_sensor motion entity.""" + await init_entry(hass, ufp, [sensor_all]) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 10, 10) + _, entity_id = ids_from_device_description( - Platform.BINARY_SENSOR, sensor, SENSE_SENSORS_WRITE[0] + Platform.BINARY_SENSOR, sensor_all, SENSE_SENSORS_WRITE[0] ) state = hass.states.get(entity_id) assert state assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.DOOR.value - new_bootstrap = copy(mock_entry.api.bootstrap) - new_sensor = sensor.copy() + new_sensor = sensor_all.copy() new_sensor.mount_type = MountType.GARAGE mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_sensor - new_bootstrap.sensors = {new_sensor.id: new_sensor} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.sensors = {new_sensor.id: new_sensor} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) diff --git a/tests/components/unifiprotect/test_button.py b/tests/components/unifiprotect/test_button.py index 9a1c7009660..a846214a7aa 100644 --- a/tests/components/unifiprotect/test_button.py +++ b/tests/components/unifiprotect/test_button.py @@ -4,7 +4,6 @@ from __future__ import annotations from unittest.mock import AsyncMock -import pytest from pyunifiprotect.data.devices import Chime from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION @@ -12,39 +11,20 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import MockEntityFixture, assert_entity_counts, enable_entity - - -@pytest.fixture(name="chime") -async def chime_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_chime: Chime -): - """Fixture for a single camera for testing the button platform.""" - - chime_obj = mock_chime.copy() - chime_obj._api = mock_entry.api - chime_obj.name = "Test Chime" - - mock_entry.api.bootstrap.chimes = { - chime_obj.id: chime_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.BUTTON, 3, 2) - - return chime_obj +from .utils import MockUFPFixture, assert_entity_counts, enable_entity, init_entry async def test_reboot_button( hass: HomeAssistant, - mock_entry: MockEntityFixture, + ufp: MockUFPFixture, chime: Chime, ): """Test button entity.""" - mock_entry.api.reboot_device = AsyncMock() + await init_entry(hass, ufp, [chime]) + assert_entity_counts(hass, Platform.BUTTON, 3, 2) + + ufp.api.reboot_device = AsyncMock() unique_id = f"{chime.mac}_reboot" entity_id = "button.test_chime_reboot_device" @@ -55,7 +35,7 @@ async def test_reboot_button( assert entity.disabled assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION @@ -63,17 +43,20 @@ async def test_reboot_button( await hass.services.async_call( "button", "press", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - mock_entry.api.reboot_device.assert_called_once() + ufp.api.reboot_device.assert_called_once() async def test_chime_button( hass: HomeAssistant, - mock_entry: MockEntityFixture, + ufp: MockUFPFixture, chime: Chime, ): """Test button entity.""" - mock_entry.api.play_speaker = AsyncMock() + await init_entry(hass, ufp, [chime]) + assert_entity_counts(hass, Platform.BUTTON, 3, 2) + + ufp.api.play_speaker = AsyncMock() unique_id = f"{chime.mac}_play" entity_id = "button.test_chime_play_chime" @@ -91,4 +74,4 @@ async def test_chime_button( await hass.services.async_call( "button", "press", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - mock_entry.api.play_speaker.assert_called_once() + ufp.api.play_speaker.assert_called_once() diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index 66da8e8ec04..6fad7cb899e 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -2,16 +2,13 @@ # pylint: disable=protected-access from __future__ import annotations -from copy import copy from unittest.mock import AsyncMock, Mock -import pytest from pyunifiprotect.data import Camera as ProtectCamera, CameraChannel, StateType from pyunifiprotect.exceptions import NvrError from homeassistant.components.camera import ( SUPPORT_STREAM, - Camera, async_get_image, async_get_stream_source, ) @@ -34,87 +31,15 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component -from .conftest import ( - MockEntityFixture, +from .utils import ( + MockUFPFixture, assert_entity_counts, enable_entity, - regenerate_device_ids, + init_entry, time_changed, ) -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the camera platform.""" - - # disable pydantic validation so mocking can happen - ProtectCamera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.channels[0].is_rtsp_enabled = True - camera_obj.channels[0].name = "High" - camera_obj.channels[1].is_rtsp_enabled = False - camera_obj.channels[2].is_rtsp_enabled = False - - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.CAMERA, 2, 1) - - yield (camera_obj, "camera.test_camera_high") - - ProtectCamera.__config__.validate_assignment = True - - -@pytest.fixture(name="camera_package") -async def camera_package_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the camera platform.""" - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_package_camera = True - camera_obj.channels[0].is_rtsp_enabled = True - camera_obj.channels[0].name = "High" - 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() - package_channel.is_rtsp_enabled = False - package_channel.name = "Package Camera" - package_channel.id = 3 - package_channel.fps = 2 - package_channel.rtsp_alias = "test_package_alias" - camera_obj.channels.append(package_channel) - - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.CAMERA, 3, 2) - - return (camera_obj, "camera.test_camera_package_camera") - - def validate_default_camera_entity( hass: HomeAssistant, camera_obj: ProtectCamera, @@ -242,99 +167,46 @@ async def validate_no_stream_camera_state( async def test_basic_setup( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: ProtectCamera + hass: HomeAssistant, + ufp: MockUFPFixture, + camera_all: ProtectCamera, + doorbell: ProtectCamera, ): """Test working setup of unifiprotect entry.""" - 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 + camera_high_only = camera_all.copy() + camera_high_only.channels = [c.copy() for c in camera_all.channels] camera_high_only.name = "Test Camera 1" camera_high_only.channels[0].is_rtsp_enabled = True - camera_high_only.channels[0].name = "High" - camera_high_only.channels[0].rtsp_alias = "test_high_alias" camera_high_only.channels[1].is_rtsp_enabled = False camera_high_only.channels[2].is_rtsp_enabled = False - regenerate_device_ids(camera_high_only) - 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 + camera_medium_only = camera_all.copy() + camera_medium_only.channels = [c.copy() for c in camera_all.channels] camera_medium_only.name = "Test Camera 2" camera_medium_only.channels[0].is_rtsp_enabled = False camera_medium_only.channels[1].is_rtsp_enabled = True - camera_medium_only.channels[1].name = "Medium" - camera_medium_only.channels[1].rtsp_alias = "test_medium_alias" camera_medium_only.channels[2].is_rtsp_enabled = False - regenerate_device_ids(camera_medium_only) - 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 - camera_all_channels.name = "Test Camera 3" - camera_all_channels.channels[0].is_rtsp_enabled = True - camera_all_channels.channels[0].name = "High" - camera_all_channels.channels[0].rtsp_alias = "test_high_alias" - camera_all_channels.channels[1].is_rtsp_enabled = True - camera_all_channels.channels[1].name = "Medium" - camera_all_channels.channels[1].rtsp_alias = "test_medium_alias" - camera_all_channels.channels[2].is_rtsp_enabled = True - camera_all_channels.channels[2].name = "Low" - camera_all_channels.channels[2].rtsp_alias = "test_low_alias" - regenerate_device_ids(camera_all_channels) + camera_all.name = "Test Camera 3" - 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 + camera_no_channels = camera_all.copy() + camera_no_channels.channels = [c.copy() for c in camera_all.channels] camera_no_channels.name = "Test Camera 4" camera_no_channels.channels[0].is_rtsp_enabled = False - camera_no_channels.channels[0].name = "High" camera_no_channels.channels[1].is_rtsp_enabled = False camera_no_channels.channels[2].is_rtsp_enabled = False - regenerate_device_ids(camera_no_channels) - 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 - camera_package.name = "Test Camera 5" - camera_package.channels[0].is_rtsp_enabled = True - camera_package.channels[0].name = "High" - camera_package.channels[0].rtsp_alias = "test_high_alias" - 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() - package_channel.is_rtsp_enabled = False - package_channel.name = "Package Camera" - package_channel.id = 3 - package_channel.fps = 2 - package_channel.rtsp_alias = "test_package_alias" - camera_package.channels.append(package_channel) + doorbell.name = "Test Camera 5" - mock_entry.api.bootstrap.cameras = { - camera_high_only.id: camera_high_only, - camera_medium_only.id: camera_medium_only, - camera_all_channels.id: camera_all_channels, - camera_no_channels.id: camera_no_channels, - camera_package.id: camera_package, - } - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + devices = [ + camera_high_only, + camera_medium_only, + camera_all, + camera_no_channels, + doorbell, + ] + await init_entry(hass, ufp, devices) assert_entity_counts(hass, Platform.CAMERA, 14, 6) @@ -343,7 +215,7 @@ async def test_basic_setup( await validate_rtsps_camera_state(hass, camera_high_only, 0, entity_id) entity_id = validate_rtsp_camera_entity(hass, camera_high_only, 0) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) await validate_rtsp_camera_state(hass, camera_high_only, 0, entity_id) # test camera 2 @@ -351,32 +223,32 @@ async def test_basic_setup( await validate_rtsps_camera_state(hass, camera_medium_only, 1, entity_id) entity_id = validate_rtsp_camera_entity(hass, camera_medium_only, 1) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) await validate_rtsp_camera_state(hass, camera_medium_only, 1, entity_id) # test camera 3 - entity_id = validate_default_camera_entity(hass, camera_all_channels, 0) - await validate_rtsps_camera_state(hass, camera_all_channels, 0, entity_id) + entity_id = validate_default_camera_entity(hass, camera_all, 0) + await validate_rtsps_camera_state(hass, camera_all, 0, entity_id) - entity_id = validate_rtsp_camera_entity(hass, camera_all_channels, 0) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - await validate_rtsp_camera_state(hass, camera_all_channels, 0, entity_id) + entity_id = validate_rtsp_camera_entity(hass, camera_all, 0) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + await validate_rtsp_camera_state(hass, camera_all, 0, entity_id) - entity_id = validate_rtsps_camera_entity(hass, camera_all_channels, 1) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - await validate_rtsps_camera_state(hass, camera_all_channels, 1, entity_id) + entity_id = validate_rtsps_camera_entity(hass, camera_all, 1) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + await validate_rtsps_camera_state(hass, camera_all, 1, entity_id) - entity_id = validate_rtsp_camera_entity(hass, camera_all_channels, 1) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - await validate_rtsp_camera_state(hass, camera_all_channels, 1, entity_id) + entity_id = validate_rtsp_camera_entity(hass, camera_all, 1) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + await validate_rtsp_camera_state(hass, camera_all, 1, entity_id) - entity_id = validate_rtsps_camera_entity(hass, camera_all_channels, 2) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - await validate_rtsps_camera_state(hass, camera_all_channels, 2, entity_id) + entity_id = validate_rtsps_camera_entity(hass, camera_all, 2) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + await validate_rtsps_camera_state(hass, camera_all, 2, entity_id) - entity_id = validate_rtsp_camera_entity(hass, camera_all_channels, 2) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - await validate_rtsp_camera_state(hass, camera_all_channels, 2, entity_id) + entity_id = validate_rtsp_camera_entity(hass, camera_all, 2) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + await validate_rtsp_camera_state(hass, camera_all, 2, entity_id) # test camera 4 entity_id = validate_default_camera_entity(hass, camera_no_channels, 0) @@ -385,197 +257,194 @@ async def test_basic_setup( ) # test camera 5 - entity_id = validate_default_camera_entity(hass, camera_package, 0) - await validate_rtsps_camera_state(hass, camera_package, 0, entity_id) + entity_id = validate_default_camera_entity(hass, doorbell, 0) + await validate_rtsps_camera_state(hass, doorbell, 0, entity_id) - entity_id = validate_rtsp_camera_entity(hass, camera_package, 0) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - await validate_rtsp_camera_state(hass, camera_package, 0, entity_id) + entity_id = validate_rtsp_camera_entity(hass, doorbell, 0) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + await validate_rtsp_camera_state(hass, doorbell, 0, entity_id) - entity_id = validate_default_camera_entity(hass, camera_package, 3) - await validate_no_stream_camera_state( - hass, camera_package, 3, entity_id, features=0 - ) + entity_id = validate_default_camera_entity(hass, doorbell, 3) + await validate_no_stream_camera_state(hass, doorbell, 3, entity_id, features=0) async def test_missing_channels( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: ProtectCamera + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Test setting up camera with no camera channels.""" - camera = mock_camera.copy() - camera.channels = [] + camera1 = camera.copy() + camera1.channels = [] - mock_entry.api.bootstrap.cameras = {camera.id: camera} + await init_entry(hass, ufp, [camera1]) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - entity_registry = er.async_get(hass) - - assert len(hass.states.async_all()) == 0 - assert len(entity_registry.entities) == 0 + assert_entity_counts(hass, Platform.CAMERA, 0, 0) async def test_camera_image( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[Camera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Test retrieving camera image.""" - mock_entry.api.get_camera_snapshot = AsyncMock() + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) - await async_get_image(hass, camera[1]) - mock_entry.api.get_camera_snapshot.assert_called_once() + ufp.api.get_camera_snapshot = AsyncMock() + + await async_get_image(hass, "camera.test_camera_high") + ufp.api.get_camera_snapshot.assert_called_once() async def test_package_camera_image( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera_package: tuple[Camera, str], + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: ProtectCamera ): """Test retrieving package camera image.""" - mock_entry.api.get_package_camera_snapshot = AsyncMock() + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.CAMERA, 3, 2) - await async_get_image(hass, camera_package[1]) - mock_entry.api.get_package_camera_snapshot.assert_called_once() + ufp.api.get_package_camera_snapshot = AsyncMock() + + await async_get_image(hass, "camera.test_camera_package_camera") + ufp.api.get_package_camera_snapshot.assert_called_once() async def test_camera_generic_update( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[ProtectCamera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Tests generic entity update service.""" + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + assert await async_setup_component(hass, "homeassistant", {}) - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "idle" - mock_entry.api.update = AsyncMock(return_value=None) + ufp.api.update = AsyncMock(return_value=None) await hass.services.async_call( "homeassistant", "update_entity", - {ATTR_ENTITY_ID: camera[1]}, + {ATTR_ENTITY_ID: entity_id}, blocking=True, ) - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "idle" async def test_camera_interval_update( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[ProtectCamera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Interval updates updates camera entity.""" - state = hass.states.get(camera[1]) + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + + state = hass.states.get(entity_id) assert state and state.state == "idle" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera[0].copy() + new_camera = camera.copy() new_camera.is_recording = True - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.update = AsyncMock(return_value=new_bootstrap) - mock_entry.api.bootstrap = new_bootstrap + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.api.update = AsyncMock(return_value=ufp.api.bootstrap) await time_changed(hass, DEFAULT_SCAN_INTERVAL) - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "recording" async def test_camera_bad_interval_update( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[Camera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Interval updates marks camera unavailable.""" - state = hass.states.get(camera[1]) + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + + state = hass.states.get(entity_id) assert state and state.state == "idle" # update fails - mock_entry.api.update = AsyncMock(side_effect=NvrError) + ufp.api.update = AsyncMock(side_effect=NvrError) await time_changed(hass, DEFAULT_SCAN_INTERVAL) - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "unavailable" # next update succeeds - mock_entry.api.update = AsyncMock(return_value=mock_entry.api.bootstrap) + ufp.api.update = AsyncMock(return_value=ufp.api.bootstrap) await time_changed(hass, DEFAULT_SCAN_INTERVAL) - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "idle" async def test_camera_ws_update( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[ProtectCamera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """WS update updates camera entity.""" - state = hass.states.get(camera[1]) + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + + state = hass.states.get(entity_id) assert state and state.state == "idle" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera[0].copy() + new_camera = camera.copy() new_camera.is_recording = True - no_camera = camera[0].copy() + no_camera = camera.copy() no_camera.is_adopted = False - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.bootstrap = new_bootstrap - + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_camera - mock_entry.api.ws_subscription(mock_msg) + ufp.ws_msg(mock_msg) mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = no_camera - mock_entry.api.ws_subscription(mock_msg) + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "recording" async def test_camera_ws_update_offline( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[ProtectCamera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """WS updates marks camera unavailable.""" - state = hass.states.get(camera[1]) + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + + state = hass.states.get(entity_id) assert state and state.state == "idle" # camera goes offline - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera[0].copy() + new_camera = camera.copy() new_camera.state = StateType.DISCONNECTED mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_camera - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "unavailable" # camera comes back online @@ -585,50 +454,53 @@ async def test_camera_ws_update_offline( mock_msg.changed_data = {} mock_msg.new_obj = new_camera - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(camera[1]) + state = hass.states.get(entity_id) assert state and state.state == "idle" async def test_camera_enable_motion( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[ProtectCamera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Tests generic entity update service.""" - camera[0].__fields__["set_motion_detection"] = Mock() - camera[0].set_motion_detection = AsyncMock() + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + + camera.__fields__["set_motion_detection"] = Mock() + camera.set_motion_detection = AsyncMock() await hass.services.async_call( "camera", "enable_motion_detection", - {ATTR_ENTITY_ID: camera[1]}, + {ATTR_ENTITY_ID: entity_id}, blocking=True, ) - camera[0].set_motion_detection.assert_called_once_with(True) + camera.set_motion_detection.assert_called_once_with(True) async def test_camera_disable_motion( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[ProtectCamera, str], + hass: HomeAssistant, ufp: MockUFPFixture, camera: ProtectCamera ): """Tests generic entity update service.""" - camera[0].__fields__["set_motion_detection"] = Mock() - camera[0].set_motion_detection = AsyncMock() + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.CAMERA, 2, 1) + entity_id = "camera.test_camera_high" + + camera.__fields__["set_motion_detection"] = Mock() + camera.set_motion_detection = AsyncMock() await hass.services.async_call( "camera", "disable_motion_detection", - {ATTR_ENTITY_ID: camera[1]}, + {ATTR_ENTITY_ID: entity_id}, blocking=True, ) - camera[0].set_motion_detection.assert_called_once_with(False) + camera.set_motion_detection.assert_called_once_with(False) diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 75f08acb37c..3d561f2d781 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -6,7 +6,7 @@ import socket from unittest.mock import patch import pytest -from pyunifiprotect import NotAuthorized, NvrError +from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient from pyunifiprotect.data import NVR from homeassistant import config_entries @@ -61,7 +61,7 @@ UNIFI_DISCOVERY_DICT = asdict(UNIFI_DISCOVERY) UNIFI_DISCOVERY_DICT_PARTIAL = asdict(UNIFI_DISCOVERY_PARTIAL) -async def test_form(hass: HomeAssistant, mock_nvr: NVR) -> None: +async def test_form(hass: HomeAssistant, nvr: NVR) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -71,7 +71,7 @@ async def test_form(hass: HomeAssistant, mock_nvr: NVR) -> None: with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - return_value=mock_nvr, + return_value=nvr, ), patch( "homeassistant.components.unifiprotect.async_setup_entry", return_value=True, @@ -99,7 +99,7 @@ async def test_form(hass: HomeAssistant, mock_nvr: NVR) -> None: assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_version_too_old(hass: HomeAssistant, mock_old_nvr: NVR) -> None: +async def test_form_version_too_old(hass: HomeAssistant, old_nvr: NVR) -> None: """Test we handle the version being too old.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -107,7 +107,7 @@ async def test_form_version_too_old(hass: HomeAssistant, mock_old_nvr: NVR) -> N with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - return_value=mock_old_nvr, + return_value=old_nvr, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -168,7 +168,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} -async def test_form_reauth_auth(hass: HomeAssistant, mock_nvr: NVR) -> None: +async def test_form_reauth_auth(hass: HomeAssistant, nvr: NVR) -> None: """Test we handle reauth auth.""" mock_config = MockConfigEntry( domain=DOMAIN, @@ -217,7 +217,7 @@ async def test_form_reauth_auth(hass: HomeAssistant, mock_nvr: NVR) -> None: with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - return_value=mock_nvr, + return_value=nvr, ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -231,7 +231,7 @@ async def test_form_reauth_auth(hass: HomeAssistant, mock_nvr: NVR) -> None: assert result3["reason"] == "reauth_successful" -async def test_form_options(hass: HomeAssistant, mock_client) -> None: +async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -> None: """Test we handle options flows.""" mock_config = MockConfigEntry( domain=DOMAIN, @@ -251,7 +251,7 @@ async def test_form_options(hass: HomeAssistant, mock_client) -> None: with _patch_discovery(), patch( "homeassistant.components.unifiprotect.ProtectApiClient" ) as mock_api: - mock_api.return_value = mock_client + mock_api.return_value = ufp_client await hass.config_entries.async_setup(mock_config.entry_id) await hass.async_block_till_done() @@ -300,7 +300,7 @@ async def test_discovered_by_ssdp_or_dhcp( async def test_discovered_by_unifi_discovery_direct_connect( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, nvr: NVR ) -> None: """Test a discovery from unifi-discovery.""" @@ -324,7 +324,7 @@ async def test_discovered_by_unifi_discovery_direct_connect( with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - return_value=mock_nvr, + return_value=nvr, ), patch( "homeassistant.components.unifiprotect.async_setup_entry", return_value=True, @@ -352,7 +352,7 @@ async def test_discovered_by_unifi_discovery_direct_connect( async def test_discovered_by_unifi_discovery_direct_connect_updated( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery updates the direct connect host.""" mock_config = MockConfigEntry( @@ -384,7 +384,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated( async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_using_direct_connect( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery updates the host but not direct connect if its not in use.""" mock_config = MockConfigEntry( @@ -419,7 +419,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_usin async def test_discovered_by_unifi_discovery_does_not_update_ip_when_console_is_still_online( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery does not update the ip unless the console at the old ip is offline.""" mock_config = MockConfigEntry( @@ -454,7 +454,7 @@ async def test_discovered_by_unifi_discovery_does_not_update_ip_when_console_is_ async def test_discovered_host_not_updated_if_existing_is_a_hostname( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test we only update the host if its an ip address from discovery.""" mock_config = MockConfigEntry( @@ -484,9 +484,7 @@ async def test_discovered_host_not_updated_if_existing_is_a_hostname( assert mock_config.data[CONF_HOST] == "a.hostname" -async def test_discovered_by_unifi_discovery( - hass: HomeAssistant, mock_nvr: NVR -) -> None: +async def test_discovered_by_unifi_discovery(hass: HomeAssistant, nvr: NVR) -> None: """Test a discovery from unifi-discovery.""" with _patch_discovery(): @@ -509,7 +507,7 @@ async def test_discovered_by_unifi_discovery( with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - side_effect=[NotAuthorized, mock_nvr], + side_effect=[NotAuthorized, nvr], ), patch( "homeassistant.components.unifiprotect.async_setup_entry", return_value=True, @@ -537,7 +535,7 @@ async def test_discovered_by_unifi_discovery( async def test_discovered_by_unifi_discovery_partial( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, nvr: NVR ) -> None: """Test a discovery from unifi-discovery partial.""" @@ -561,7 +559,7 @@ async def test_discovered_by_unifi_discovery_partial( with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - return_value=mock_nvr, + return_value=nvr, ), patch( "homeassistant.components.unifiprotect.async_setup_entry", return_value=True, @@ -589,7 +587,7 @@ async def test_discovered_by_unifi_discovery_partial( async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery from an alternate interface.""" mock_config = MockConfigEntry( @@ -619,7 +617,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_ip_matches( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery from an alternate interface when the ip matches.""" mock_config = MockConfigEntry( @@ -649,7 +647,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery from an alternate interface when direct connect domain resolves to host ip.""" mock_config = MockConfigEntry( @@ -687,7 +685,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_fails( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, nvr: NVR ) -> None: """Test we can still configure if the resolver fails.""" mock_config = MockConfigEntry( @@ -730,7 +728,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr", - return_value=mock_nvr, + return_value=nvr, ), patch( "homeassistant.components.unifiprotect.async_setup_entry", return_value=True, @@ -758,7 +756,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_no_result( - hass: HomeAssistant, mock_nvr: NVR + hass: HomeAssistant, ) -> None: """Test a discovery from unifi-discovery from an alternate interface when direct connect domain resolve has no result.""" mock_config = MockConfigEntry( @@ -791,7 +789,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa assert result["reason"] == "already_configured" -async def test_discovery_can_be_ignored(hass: HomeAssistant, mock_nvr: NVR) -> None: +async def test_discovery_can_be_ignored(hass: HomeAssistant) -> None: """Test a discovery can be ignored.""" mock_config = MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/unifiprotect/test_diagnostics.py b/tests/components/unifiprotect/test_diagnostics.py index 2e7f8c0e4b4..a0ed8f0d882 100644 --- a/tests/components/unifiprotect/test_diagnostics.py +++ b/tests/components/unifiprotect/test_diagnostics.py @@ -4,53 +4,44 @@ from pyunifiprotect.data import NVR, Light from homeassistant.core import HomeAssistant -from .conftest import MockEntityFixture, regenerate_device_ids +from .utils import MockUFPFixture, init_entry from tests.components.diagnostics import get_diagnostics_for_config_entry async def test_diagnostics( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light, hass_client + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, hass_client ): """Test generating diagnostics for a config entry.""" - light1 = mock_light.copy() - light1._api = mock_entry.api - light1.name = "Test Light 1" - regenerate_device_ids(light1) + await init_entry(hass, ufp, [light]) - mock_entry.api.bootstrap.lights = { - light1.id: light1, - } - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + diag = await get_diagnostics_for_config_entry(hass, hass_client, ufp.entry) - diag = await get_diagnostics_for_config_entry(hass, hass_client, mock_entry.entry) - - nvr_obj: NVR = mock_entry.api.bootstrap.nvr + nvr: NVR = ufp.api.bootstrap.nvr # validate some of the data assert "nvr" in diag and isinstance(diag["nvr"], dict) - nvr = diag["nvr"] + nvr_dict = diag["nvr"] # should have been anonymized - assert nvr["id"] != nvr_obj.id - assert nvr["mac"] != nvr_obj.mac - assert nvr["host"] != str(nvr_obj.host) + assert nvr_dict["id"] != nvr.id + assert nvr_dict["mac"] != nvr.mac + assert nvr_dict["host"] != str(nvr.host) # should have been kept - assert nvr["firmwareVersion"] == nvr_obj.firmware_version - assert nvr["version"] == str(nvr_obj.version) - assert nvr["type"] == nvr_obj.type + assert nvr_dict["firmwareVersion"] == nvr.firmware_version + assert nvr_dict["version"] == str(nvr.version) + assert nvr_dict["type"] == nvr.type assert ( "lights" in diag and isinstance(diag["lights"], list) and len(diag["lights"]) == 1 ) - light = diag["lights"][0] + light_dict = diag["lights"][0] # should have been anonymized - assert light["id"] != light1.id - assert light["name"] != light1.mac - assert light["mac"] != light1.mac - assert light["host"] != str(light1.host) + assert light_dict["id"] != light.id + assert light_dict["name"] != light.mac + assert light_dict["mac"] != light.mac + assert light_dict["host"] != str(light.host) # should have been kept - assert light["firmwareVersion"] == light1.firmware_version - assert light["type"] == light1.type + assert light_dict["firmwareVersion"] == light.firmware_version + assert light_dict["type"] == light.type diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 5c06eedc4c9..c0ad30ad115 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable from unittest.mock import AsyncMock, patch import aiohttp -from pyunifiprotect import NotAuthorized, NvrError +from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient from pyunifiprotect.data import NVR, Bootstrap, Light from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP, DOMAIN @@ -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 MockEntityFixture, regenerate_device_ids +from .utils import MockUFPFixture, init_entry from tests.common import MockConfigEntry @@ -37,37 +37,36 @@ async def remove_device( return response["success"] -async def test_setup(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def test_setup(hass: HomeAssistant, ufp: MockUFPFixture): """Test working setup of unifiprotect entry.""" - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac async def test_setup_multiple( hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_client, - mock_bootstrap: Bootstrap, + ufp: MockUFPFixture, + bootstrap: Bootstrap, ): """Test working setup of unifiprotect entry.""" - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - nvr = mock_bootstrap.nvr - nvr._api = mock_client + nvr = bootstrap.nvr + nvr._api = ufp.api nvr.mac = "A1E00C826983" nvr.id - mock_client.get_nvr = AsyncMock(return_value=nvr) + ufp.api.get_nvr = AsyncMock(return_value=nvr) with patch("homeassistant.components.unifiprotect.ProtectApiClient") as mock_api: mock_config = MockConfigEntry( @@ -84,148 +83,134 @@ async def test_setup_multiple( ) mock_config.add_to_hass(hass) - mock_api.return_value = mock_client + mock_api.return_value = ufp.api await hass.config_entries.async_setup(mock_config.entry_id) await hass.async_block_till_done() assert mock_config.state == ConfigEntryState.LOADED - assert mock_client.update.called - assert mock_config.unique_id == mock_client.bootstrap.nvr.mac + assert ufp.api.update.called + assert mock_config.unique_id == ufp.api.bootstrap.nvr.mac -async def test_reload(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def test_reload(hass: HomeAssistant, ufp: MockUFPFixture): """Test updating entry reload entry.""" - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.LOADED + assert ufp.entry.state == ConfigEntryState.LOADED - options = dict(mock_entry.entry.options) + options = dict(ufp.entry.options) options[CONF_DISABLE_RTSP] = True - hass.config_entries.async_update_entry(mock_entry.entry, options=options) + hass.config_entries.async_update_entry(ufp.entry, options=options) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.async_disconnect_ws.called + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.async_disconnect_ws.called -async def test_unload(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def test_unload(hass: HomeAssistant, ufp: MockUFPFixture): """Test unloading of unifiprotect entry.""" - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.LOADED + assert ufp.entry.state == ConfigEntryState.LOADED - await hass.config_entries.async_unload(mock_entry.entry.entry_id) - assert mock_entry.entry.state == ConfigEntryState.NOT_LOADED - assert mock_entry.api.async_disconnect_ws.called + await hass.config_entries.async_unload(ufp.entry.entry_id) + assert ufp.entry.state == ConfigEntryState.NOT_LOADED + assert ufp.api.async_disconnect_ws.called -async def test_setup_too_old( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_old_nvr: NVR -): +async def test_setup_too_old(hass: HomeAssistant, ufp: MockUFPFixture, old_nvr: NVR): """Test setup of unifiprotect entry with too old of version of UniFi Protect.""" - mock_entry.api.get_nvr.return_value = mock_old_nvr + ufp.api.get_nvr.return_value = old_nvr - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.SETUP_ERROR - assert not mock_entry.api.update.called + assert ufp.entry.state == ConfigEntryState.SETUP_ERROR + assert not ufp.api.update.called -async def test_setup_failed_update(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def test_setup_failed_update(hass: HomeAssistant, ufp: MockUFPFixture): """Test setup of unifiprotect entry with failed update.""" - mock_entry.api.update = AsyncMock(side_effect=NvrError) + ufp.api.update = AsyncMock(side_effect=NvrError) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.SETUP_RETRY - assert mock_entry.api.update.called + assert ufp.entry.state == ConfigEntryState.SETUP_RETRY + assert ufp.api.update.called -async def test_setup_failed_update_reauth( - hass: HomeAssistant, mock_entry: MockEntityFixture -): +async def test_setup_failed_update_reauth(hass: HomeAssistant, ufp: MockUFPFixture): """Test setup of unifiprotect entry with update that gives unauthroized error.""" - mock_entry.api.update = AsyncMock(side_effect=NotAuthorized) + ufp.api.update = AsyncMock(side_effect=NotAuthorized) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.SETUP_RETRY - assert mock_entry.api.update.called + assert ufp.entry.state == ConfigEntryState.SETUP_RETRY + assert ufp.api.update.called -async def test_setup_failed_error(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def test_setup_failed_error(hass: HomeAssistant, ufp: MockUFPFixture): """Test setup of unifiprotect entry with generic error.""" - mock_entry.api.get_nvr = AsyncMock(side_effect=NvrError) + ufp.api.get_nvr = AsyncMock(side_effect=NvrError) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.SETUP_RETRY - assert not mock_entry.api.update.called + assert ufp.entry.state == ConfigEntryState.SETUP_RETRY + assert not ufp.api.update.called -async def test_setup_failed_auth(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def test_setup_failed_auth(hass: HomeAssistant, ufp: MockUFPFixture): """Test setup of unifiprotect entry with unauthorized error.""" - mock_entry.api.get_nvr = AsyncMock(side_effect=NotAuthorized) + ufp.api.get_nvr = AsyncMock(side_effect=NotAuthorized) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - assert mock_entry.entry.state == ConfigEntryState.SETUP_ERROR - assert not mock_entry.api.update.called + await hass.config_entries.async_setup(ufp.entry.entry_id) + assert ufp.entry.state == ConfigEntryState.SETUP_ERROR + assert not ufp.api.update.called async def test_setup_starts_discovery( - hass: HomeAssistant, mock_ufp_config_entry: ConfigEntry, mock_client + hass: HomeAssistant, ufp_config_entry: ConfigEntry, ufp_client: ProtectApiClient ): """Test setting up will start discovery.""" with _patch_discovery(), patch( "homeassistant.components.unifiprotect.ProtectApiClient" ) as mock_api: - mock_ufp_config_entry.add_to_hass(hass) - mock_api.return_value = mock_client - mock_entry = MockEntityFixture(mock_ufp_config_entry, mock_client) + ufp_config_entry.add_to_hass(hass) + mock_api.return_value = ufp_client + ufp = MockUFPFixture(ufp_config_entry, ufp_client) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - assert mock_entry.entry.state == ConfigEntryState.LOADED + assert ufp.entry.state == ConfigEntryState.LOADED await hass.async_block_till_done() assert len(hass.config_entries.flow.async_progress_by_handler(DOMAIN)) == 1 async def test_device_remove_devices( hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_light: Light, + ufp: MockUFPFixture, + light: Light, hass_ws_client: Callable[ [HomeAssistant], Awaitable[aiohttp.ClientWebSocketResponse] ], ) -> None: """Test we can only remove a device that no longer exists.""" + + await init_entry(hass, ufp, [light]) assert await async_setup_component(hass, "config", {}) - - light1 = mock_light.copy() - light1._api = mock_entry.api - light1.name = "Test Light 1" - regenerate_device_ids(light1) - - mock_entry.api.bootstrap.lights = { - light1.id: light1, - } - - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - light_entity_id = "light.test_light_1" - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - entry_id = mock_entry.entry.entry_id + entity_id = "light.test_light" + entry_id = ufp.entry.entry_id registry: er.EntityRegistry = er.async_get(hass) - entity = registry.entities[light_entity_id] + entity = registry.async_get(entity_id) + assert entity is not None device_registry = dr.async_get(hass) live_device_entry = device_registry.async_get(entity.device_id) @@ -246,7 +231,7 @@ async def test_device_remove_devices( async def test_device_remove_devices_nvr( hass: HomeAssistant, - mock_entry: MockEntityFixture, + ufp: MockUFPFixture, hass_ws_client: Callable[ [HomeAssistant], Awaitable[aiohttp.ClientWebSocketResponse] ], @@ -254,10 +239,10 @@ async def test_device_remove_devices_nvr( """Test we can only remove a NVR device that no longer exists.""" assert await async_setup_component(hass, "config", {}) - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - entry_id = mock_entry.entry.entry_id + entry_id = ufp.entry.entry_id device_registry = dr.async_get(hass) diff --git a/tests/components/unifiprotect/test_light.py b/tests/components/unifiprotect/test_light.py index 3bcca436911..3c575de8d00 100644 --- a/tests/components/unifiprotect/test_light.py +++ b/tests/components/unifiprotect/test_light.py @@ -2,11 +2,10 @@ # pylint: disable=protected-access from __future__ import annotations -from copy import copy from unittest.mock import AsyncMock, Mock -import pytest from pyunifiprotect.data import Light +from pyunifiprotect.data.types import LEDLevel from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION @@ -20,53 +19,19 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import MockEntityFixture, assert_entity_counts, regenerate_device_ids - - -@pytest.fixture(name="light") -async def light_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light -): - """Fixture for a single light for testing the light platform.""" - - # disable pydantic validation so mocking can happen - Light.__config__.validate_assignment = False - - light_obj = mock_light.copy() - light_obj._api = mock_entry.api - light_obj.name = "Test Light" - light_obj.is_light_on = False - regenerate_device_ids(light_obj) - - no_light_obj = mock_light.copy() - no_light_obj._api = mock_entry.api - no_light_obj.name = "Unadopted Light" - no_light_obj.is_adopted = False - regenerate_device_ids(no_light_obj) - - mock_entry.api.bootstrap.lights = { - light_obj.id: light_obj, - no_light_obj.id: no_light_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.LIGHT, 1, 1) - - yield (light_obj, "light.test_light") - - Light.__config__.validate_assignment = True +from .utils import MockUFPFixture, assert_entity_counts, init_entry async def test_light_setup( - hass: HomeAssistant, - light: tuple[Light, str], + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, unadopted_light: Light ): """Test light entity setup.""" - unique_id = light[0].mac - entity_id = light[1] + await init_entry(hass, ufp, [light, unadopted_light]) + assert_entity_counts(hass, Platform.LIGHT, 1, 1) + + unique_id = light.mac + entity_id = "light.test_light" entity_registry = er.async_get(hass) entity = entity_registry.async_get(entity_id) @@ -80,41 +45,42 @@ async def test_light_setup( async def test_light_update( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - light: tuple[Light, str], + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, unadopted_light: Light ): """Test light entity update.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_light = light[0].copy() + await init_entry(hass, ufp, [light, unadopted_light]) + assert_entity_counts(hass, Platform.LIGHT, 1, 1) + + new_light = light.copy() new_light.is_light_on = True - new_light.light_device_settings.led_level = 3 + new_light.light_device_settings.led_level = LEDLevel(3) mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_light - new_bootstrap.lights = {new_light.id: new_light} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.lights = {new_light.id: new_light} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(light[1]) + state = hass.states.get("light.test_light") assert state assert state.state == STATE_ON assert state.attributes[ATTR_BRIGHTNESS] == 128 async def test_light_turn_on( - hass: HomeAssistant, - light: tuple[Light, str], + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, unadopted_light: Light ): """Test light entity turn off.""" - entity_id = light[1] - light[0].__fields__["set_light"] = Mock() - light[0].set_light = AsyncMock() + await init_entry(hass, ufp, [light, unadopted_light]) + assert_entity_counts(hass, Platform.LIGHT, 1, 1) + + entity_id = "light.test_light" + light.__fields__["set_light"] = Mock() + light.set_light = AsyncMock() await hass.services.async_call( "light", @@ -123,18 +89,20 @@ async def test_light_turn_on( blocking=True, ) - light[0].set_light.assert_called_once_with(True, 3) + light.set_light.assert_called_once_with(True, 3) async def test_light_turn_off( - hass: HomeAssistant, - light: tuple[Light, str], + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, unadopted_light: Light ): """Test light entity turn on.""" - entity_id = light[1] - light[0].__fields__["set_light"] = Mock() - light[0].set_light = AsyncMock() + await init_entry(hass, ufp, [light, unadopted_light]) + assert_entity_counts(hass, Platform.LIGHT, 1, 1) + + entity_id = "light.test_light" + light.__fields__["set_light"] = Mock() + light.set_light = AsyncMock() await hass.services.async_call( "light", @@ -143,4 +111,4 @@ async def test_light_turn_off( blocking=True, ) - light[0].set_light.assert_called_once_with(False) + light.set_light.assert_called_once_with(False) diff --git a/tests/components/unifiprotect/test_lock.py b/tests/components/unifiprotect/test_lock.py index 3ebfd2de22f..21b3c77deb5 100644 --- a/tests/components/unifiprotect/test_lock.py +++ b/tests/components/unifiprotect/test_lock.py @@ -2,10 +2,8 @@ # pylint: disable=protected-access from __future__ import annotations -from copy import copy from unittest.mock import AsyncMock, Mock -import pytest from pyunifiprotect.data import Doorlock, LockStatusType from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION @@ -23,53 +21,22 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import MockEntityFixture, assert_entity_counts, regenerate_device_ids - - -@pytest.fixture(name="doorlock") -async def doorlock_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_doorlock: Doorlock -): - """Fixture for a single doorlock for testing the lock platform.""" - - # disable pydantic validation so mocking can happen - Doorlock.__config__.validate_assignment = False - - lock_obj = mock_doorlock.copy() - lock_obj._api = mock_entry.api - lock_obj.name = "Test Lock" - lock_obj.lock_status = LockStatusType.OPEN - regenerate_device_ids(lock_obj) - - no_lock_obj = mock_doorlock.copy() - no_lock_obj._api = mock_entry.api - no_lock_obj.name = "Unadopted Lock" - no_lock_obj.is_adopted = False - regenerate_device_ids(no_lock_obj) - - mock_entry.api.bootstrap.doorlocks = { - lock_obj.id: lock_obj, - no_lock_obj.id: no_lock_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.LOCK, 1, 1) - - yield (lock_obj, "lock.test_lock_lock") - - Doorlock.__config__.validate_assignment = True +from .utils import MockUFPFixture, assert_entity_counts, init_entry async def test_lock_setup( hass: HomeAssistant, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity setup.""" - unique_id = f"{doorlock[0].mac}_lock" - entity_id = doorlock[1] + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + unique_id = f"{doorlock.mac}_lock" + entity_id = "lock.test_lock_lock" entity_registry = er.async_get(hass) entity = entity_registry.async_get(entity_id) @@ -84,166 +51,183 @@ async def test_lock_setup( async def test_lock_locked( hass: HomeAssistant, - mock_entry: MockEntityFixture, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity locked.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_lock = doorlock[0].copy() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + new_lock = doorlock.copy() new_lock.lock_status = LockStatusType.CLOSED mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_lock - new_bootstrap.doorlocks = {new_lock.id: new_lock} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.doorlocks = {new_lock.id: new_lock} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(doorlock[1]) + state = hass.states.get("lock.test_lock_lock") assert state assert state.state == STATE_LOCKED async def test_lock_unlocking( hass: HomeAssistant, - mock_entry: MockEntityFixture, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity unlocking.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_lock = doorlock[0].copy() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + new_lock = doorlock.copy() new_lock.lock_status = LockStatusType.OPENING mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_lock - new_bootstrap.doorlocks = {new_lock.id: new_lock} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.doorlocks = {new_lock.id: new_lock} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(doorlock[1]) + state = hass.states.get("lock.test_lock_lock") assert state assert state.state == STATE_UNLOCKING async def test_lock_locking( hass: HomeAssistant, - mock_entry: MockEntityFixture, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity locking.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_lock = doorlock[0].copy() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + new_lock = doorlock.copy() new_lock.lock_status = LockStatusType.CLOSING mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_lock - new_bootstrap.doorlocks = {new_lock.id: new_lock} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.doorlocks = {new_lock.id: new_lock} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(doorlock[1]) + state = hass.states.get("lock.test_lock_lock") assert state assert state.state == STATE_LOCKING async def test_lock_jammed( hass: HomeAssistant, - mock_entry: MockEntityFixture, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity jammed.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_lock = doorlock[0].copy() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + new_lock = doorlock.copy() new_lock.lock_status = LockStatusType.JAMMED_WHILE_CLOSING mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_lock - new_bootstrap.doorlocks = {new_lock.id: new_lock} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.doorlocks = {new_lock.id: new_lock} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(doorlock[1]) + state = hass.states.get("lock.test_lock_lock") assert state assert state.state == STATE_JAMMED async def test_lock_unavailable( hass: HomeAssistant, - mock_entry: MockEntityFixture, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity unavailable.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_lock = doorlock[0].copy() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + new_lock = doorlock.copy() new_lock.lock_status = LockStatusType.NOT_CALIBRATED mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_lock - new_bootstrap.doorlocks = {new_lock.id: new_lock} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.doorlocks = {new_lock.id: new_lock} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(doorlock[1]) + state = hass.states.get("lock.test_lock_lock") assert state assert state.state == STATE_UNAVAILABLE async def test_lock_do_lock( hass: HomeAssistant, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity lock service.""" - doorlock[0].__fields__["close_lock"] = Mock() - doorlock[0].close_lock = AsyncMock() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + doorlock.__fields__["close_lock"] = Mock() + doorlock.close_lock = AsyncMock() await hass.services.async_call( "lock", "lock", - {ATTR_ENTITY_ID: doorlock[1]}, + {ATTR_ENTITY_ID: "lock.test_lock_lock"}, blocking=True, ) - doorlock[0].close_lock.assert_called_once() + doorlock.close_lock.assert_called_once() async def test_lock_do_unlock( hass: HomeAssistant, - mock_entry: MockEntityFixture, - doorlock: tuple[Doorlock, str], + ufp: MockUFPFixture, + doorlock: Doorlock, + unadopted_doorlock: Doorlock, ): """Test lock entity unlock service.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_lock = doorlock[0].copy() + await init_entry(hass, ufp, [doorlock, unadopted_doorlock]) + assert_entity_counts(hass, Platform.LOCK, 1, 1) + + new_lock = doorlock.copy() new_lock.lock_status = LockStatusType.CLOSED mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_lock - new_bootstrap.doorlocks = {new_lock.id: new_lock} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.doorlocks = {new_lock.id: new_lock} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() new_lock.__fields__["open_lock"] = Mock() @@ -252,7 +236,7 @@ async def test_lock_do_unlock( await hass.services.async_call( "lock", "unlock", - {ATTR_ENTITY_ID: doorlock[1]}, + {ATTR_ENTITY_ID: "lock.test_lock_lock"}, blocking=True, ) diff --git a/tests/components/unifiprotect/test_media_player.py b/tests/components/unifiprotect/test_media_player.py index c18a407eadb..678fa0c9be4 100644 --- a/tests/components/unifiprotect/test_media_player.py +++ b/tests/components/unifiprotect/test_media_player.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from __future__ import annotations -from copy import copy from unittest.mock import AsyncMock, Mock, patch import pytest @@ -26,66 +25,29 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from .conftest import MockEntityFixture, assert_entity_counts, regenerate_device_ids - - -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the media_player platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_speaker = True - regenerate_device_ids(camera_obj) - - no_camera_obj = mock_camera.copy() - no_camera_obj._api = mock_entry.api - no_camera_obj.channels[0]._api = mock_entry.api - no_camera_obj.channels[1]._api = mock_entry.api - no_camera_obj.channels[2]._api = mock_entry.api - no_camera_obj.name = "Unadopted Camera" - no_camera_obj.is_adopted = False - regenerate_device_ids(no_camera_obj) - - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - no_camera_obj.id: no_camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) - - yield (camera_obj, "media_player.test_camera_speaker") - - Camera.__config__.validate_assignment = True +from .utils import MockUFPFixture, assert_entity_counts, init_entry async def test_media_player_setup( hass: HomeAssistant, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity setup.""" - unique_id = f"{camera[0].mac}_speaker" - entity_id = camera[1] + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + unique_id = f"{doorbell.mac}_speaker" + entity_id = "media_player.test_camera_speaker" entity_registry = er.async_get(hass) entity = entity_registry.async_get(entity_id) assert entity assert entity.unique_id == unique_id - expected_volume = float(camera[0].speaker_settings.volume / 100) + expected_volume = float(doorbell.speaker_settings.volume / 100) state = hass.states.get(entity_id) assert state @@ -98,13 +60,16 @@ async def test_media_player_setup( async def test_media_player_update( hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity update.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera[0].copy() + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + new_camera = doorbell.copy() new_camera.talkback_stream = Mock() new_camera.talkback_stream.is_running = True @@ -112,44 +77,51 @@ async def test_media_player_update( mock_msg.changed_data = {} mock_msg.new_obj = new_camera - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() - state = hass.states.get(camera[1]) + state = hass.states.get("media_player.test_camera_speaker") assert state assert state.state == STATE_PLAYING async def test_media_player_set_volume( hass: HomeAssistant, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity test set_volume_level.""" - camera[0].__fields__["set_speaker_volume"] = Mock() - camera[0].set_speaker_volume = AsyncMock() + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + doorbell.__fields__["set_speaker_volume"] = Mock() + doorbell.set_speaker_volume = AsyncMock() await hass.services.async_call( "media_player", "volume_set", - {ATTR_ENTITY_ID: camera[1], "volume_level": 0.5}, + {ATTR_ENTITY_ID: "media_player.test_camera_speaker", "volume_level": 0.5}, blocking=True, ) - camera[0].set_speaker_volume.assert_called_once_with(50) + doorbell.set_speaker_volume.assert_called_once_with(50) async def test_media_player_stop( hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity test media_stop.""" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera[0].copy() + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + new_camera = doorbell.copy() new_camera.talkback_stream = AsyncMock() new_camera.talkback_stream.is_running = True @@ -157,15 +129,14 @@ async def test_media_player_stop( mock_msg.changed_data = {} mock_msg.new_obj = new_camera - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() await hass.services.async_call( "media_player", "media_stop", - {ATTR_ENTITY_ID: camera[1]}, + {ATTR_ENTITY_ID: "media_player.test_camera_speaker"}, blocking=True, ) @@ -174,44 +145,56 @@ async def test_media_player_stop( async def test_media_player_play( hass: HomeAssistant, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity test play_media.""" - camera[0].__fields__["stop_audio"] = Mock() - camera[0].__fields__["play_audio"] = Mock() - camera[0].__fields__["wait_until_audio_completes"] = Mock() - camera[0].stop_audio = AsyncMock() - camera[0].play_audio = AsyncMock() - camera[0].wait_until_audio_completes = AsyncMock() + + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + doorbell.__fields__["stop_audio"] = Mock() + doorbell.__fields__["play_audio"] = Mock() + doorbell.__fields__["wait_until_audio_completes"] = Mock() + doorbell.stop_audio = AsyncMock() + doorbell.play_audio = AsyncMock() + doorbell.wait_until_audio_completes = AsyncMock() await hass.services.async_call( "media_player", "play_media", { - ATTR_ENTITY_ID: camera[1], + ATTR_ENTITY_ID: "media_player.test_camera_speaker", "media_content_id": "http://example.com/test.mp3", "media_content_type": "music", }, blocking=True, ) - camera[0].play_audio.assert_called_once_with( + doorbell.play_audio.assert_called_once_with( "http://example.com/test.mp3", blocking=False ) - camera[0].wait_until_audio_completes.assert_called_once() + doorbell.wait_until_audio_completes.assert_called_once() async def test_media_player_play_media_source( hass: HomeAssistant, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity test play_media.""" - camera[0].__fields__["stop_audio"] = Mock() - camera[0].__fields__["play_audio"] = Mock() - camera[0].__fields__["wait_until_audio_completes"] = Mock() - camera[0].stop_audio = AsyncMock() - camera[0].play_audio = AsyncMock() - camera[0].wait_until_audio_completes = AsyncMock() + + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + doorbell.__fields__["stop_audio"] = Mock() + doorbell.__fields__["play_audio"] = Mock() + doorbell.__fields__["wait_until_audio_completes"] = Mock() + doorbell.stop_audio = AsyncMock() + doorbell.play_audio = AsyncMock() + doorbell.wait_until_audio_completes = AsyncMock() with patch( "homeassistant.components.media_source.async_resolve_media", @@ -221,65 +204,75 @@ async def test_media_player_play_media_source( "media_player", "play_media", { - ATTR_ENTITY_ID: camera[1], + ATTR_ENTITY_ID: "media_player.test_camera_speaker", "media_content_id": "media-source://some_source/some_id", "media_content_type": "audio/mpeg", }, blocking=True, ) - camera[0].play_audio.assert_called_once_with( + doorbell.play_audio.assert_called_once_with( "http://example.com/test.mp3", blocking=False ) - camera[0].wait_until_audio_completes.assert_called_once() + doorbell.wait_until_audio_completes.assert_called_once() async def test_media_player_play_invalid( hass: HomeAssistant, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity test play_media, not music.""" - camera[0].__fields__["play_audio"] = Mock() - camera[0].play_audio = AsyncMock() + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + doorbell.__fields__["play_audio"] = Mock() + doorbell.play_audio = AsyncMock() with pytest.raises(HomeAssistantError): await hass.services.async_call( "media_player", "play_media", { - ATTR_ENTITY_ID: camera[1], + ATTR_ENTITY_ID: "media_player.test_camera_speaker", "media_content_id": "/test.png", "media_content_type": "image", }, blocking=True, ) - assert not camera[0].play_audio.called + assert not doorbell.play_audio.called async def test_media_player_play_error( hass: HomeAssistant, - camera: tuple[Camera, str], + ufp: MockUFPFixture, + doorbell: Camera, + unadopted_camera: Camera, ): """Test media_player entity test play_media, not music.""" - camera[0].__fields__["play_audio"] = Mock() - camera[0].__fields__["wait_until_audio_completes"] = Mock() - camera[0].play_audio = AsyncMock(side_effect=StreamError) - camera[0].wait_until_audio_completes = AsyncMock() + await init_entry(hass, ufp, [doorbell, unadopted_camera]) + assert_entity_counts(hass, Platform.MEDIA_PLAYER, 1, 1) + + doorbell.__fields__["play_audio"] = Mock() + doorbell.__fields__["wait_until_audio_completes"] = Mock() + doorbell.play_audio = AsyncMock(side_effect=StreamError) + doorbell.wait_until_audio_completes = AsyncMock() with pytest.raises(HomeAssistantError): await hass.services.async_call( "media_player", "play_media", { - ATTR_ENTITY_ID: camera[1], + ATTR_ENTITY_ID: "media_player.test_camera_speaker", "media_content_id": "/test.mp3", "media_content_type": "music", }, blocking=True, ) - assert camera[0].play_audio.called - assert not camera[0].wait_until_audio_completes.called + assert doorbell.play_audio.called + assert not doorbell.wait_until_audio_completes.called diff --git a/tests/components/unifiprotect/test_migrate.py b/tests/components/unifiprotect/test_migrate.py index 206c85e3654..64c8384d400 100644 --- a/tests/components/unifiprotect/test_migrate.py +++ b/tests/components/unifiprotect/test_migrate.py @@ -5,7 +5,6 @@ from __future__ import annotations from unittest.mock import AsyncMock from pyunifiprotect.data import Light -from pyunifiprotect.data.bootstrap import ProtectDeviceRef from pyunifiprotect.exceptions import NvrError from homeassistant.components.unifiprotect.const import DOMAIN @@ -14,56 +13,47 @@ from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import MockEntityFixture, generate_random_ids, regenerate_device_ids +from .utils import ( + MockUFPFixture, + generate_random_ids, + init_entry, + regenerate_device_ids, +) async def test_migrate_reboot_button( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test migrating unique ID of reboot button.""" - light1 = mock_light.copy() - light1._api = mock_entry.api + light1 = light.copy() light1.name = "Test Light 1" regenerate_device_ids(light1) - light2 = mock_light.copy() - light2._api = mock_entry.api + light2 = light.copy() light2.name = "Test Light 2" regenerate_device_ids(light2) - mock_entry.api.bootstrap.lights = { - light1.id: light1, - light2.id: light2, - } - mock_entry.api.bootstrap.id_lookup = { - light1.id: ProtectDeviceRef(id=light1.id, model=light1.model), - light2.id: ProtectDeviceRef(id=light2.id, model=light2.model), - } - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - registry = er.async_get(hass) registry.async_get_or_create( - Platform.BUTTON, DOMAIN, light1.id, config_entry=mock_entry.entry + Platform.BUTTON, DOMAIN, light1.id, config_entry=ufp.entry ) registry.async_get_or_create( Platform.BUTTON, DOMAIN, f"{light2.mac}_reboot", - config_entry=mock_entry.entry, + config_entry=ufp.entry, ) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) + await init_entry(hass, ufp, [light1, light2], regenerate_ids=False) - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac buttons = [] - for entity in er.async_entries_for_config_entry( - registry, mock_entry.entry.entry_id - ): + for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id): if entity.domain == Platform.BUTTON.value: buttons.append(entity) assert len(buttons) == 2 @@ -83,29 +73,33 @@ async def test_migrate_reboot_button( assert light.unique_id == f"{light2.mac}_reboot" -async def test_migrate_nvr_mac( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light -): +async def test_migrate_nvr_mac(hass: HomeAssistant, ufp: MockUFPFixture, light: Light): """Test migrating unique ID of NVR to use MAC address.""" - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - nvr = mock_entry.api.bootstrap.nvr - regenerate_device_ids(nvr) + light1 = light.copy() + light1.name = "Test Light 1" + regenerate_device_ids(light1) + light2 = light.copy() + light2.name = "Test Light 2" + regenerate_device_ids(light2) + + nvr = ufp.api.bootstrap.nvr + regenerate_device_ids(nvr) registry = er.async_get(hass) registry.async_get_or_create( Platform.SENSOR, DOMAIN, f"{nvr.id}_storage_utilization", - config_entry=mock_entry.entry, + config_entry=ufp.entry, ) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) + await init_entry(hass, ufp, [light1, light2], regenerate_ids=False) - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac assert registry.async_get(f"{Platform.SENSOR}.{DOMAIN}_storage_utilization") is None assert ( @@ -119,171 +113,123 @@ async def test_migrate_nvr_mac( async def test_migrate_reboot_button_no_device( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test migrating unique ID of reboot button if UniFi Protect device ID changed.""" - light1 = mock_light.copy() - light1._api = mock_entry.api - light1.name = "Test Light 1" - regenerate_device_ids(light1) - light2_id, _ = generate_random_ids() - mock_entry.api.bootstrap.lights = { - light1.id: light1, - } - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - registry = er.async_get(hass) registry.async_get_or_create( - Platform.BUTTON, DOMAIN, light2_id, config_entry=mock_entry.entry + Platform.BUTTON, DOMAIN, light2_id, config_entry=ufp.entry ) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) + await init_entry(hass, ufp, [light], regenerate_ids=False) - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac buttons = [] - for entity in er.async_entries_for_config_entry( - registry, mock_entry.entry.entry_id - ): + for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id): if entity.domain == Platform.BUTTON.value: buttons.append(entity) assert len(buttons) == 2 - light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_{light2_id.lower()}") - assert light is not None - assert light.unique_id == light2_id + entity = registry.async_get(f"{Platform.BUTTON}.unifiprotect_{light2_id.lower()}") + assert entity is not None + assert entity.unique_id == light2_id async def test_migrate_reboot_button_fail( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test migrating unique ID of reboot button.""" - light1 = mock_light.copy() - light1._api = mock_entry.api - light1.name = "Test Light 1" - regenerate_device_ids(light1) - - mock_entry.api.bootstrap.lights = { - light1.id: light1, - } - mock_entry.api.bootstrap.id_lookup = { - light1.id: ProtectDeviceRef(id=light1.id, model=light1.model), - } - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - registry = er.async_get(hass) registry.async_get_or_create( Platform.BUTTON, DOMAIN, - light1.id, - config_entry=mock_entry.entry, - suggested_object_id=light1.name, + light.id, + config_entry=ufp.entry, + suggested_object_id=light.display_name, ) registry.async_get_or_create( Platform.BUTTON, DOMAIN, - f"{light1.id}_reboot", - config_entry=mock_entry.entry, - suggested_object_id=light1.name, + f"{light.id}_reboot", + config_entry=ufp.entry, + suggested_object_id=light.display_name, ) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) + await init_entry(hass, ufp, [light], regenerate_ids=False) - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - light = registry.async_get(f"{Platform.BUTTON}.test_light_1") - assert light is not None - assert light.unique_id == f"{light1.mac}" + entity = registry.async_get(f"{Platform.BUTTON}.test_light") + assert entity is not None + assert entity.unique_id == f"{light.mac}" async def test_migrate_device_mac_button_fail( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test migrating unique ID to MAC format.""" - light1 = mock_light.copy() - light1._api = mock_entry.api - light1.name = "Test Light 1" - regenerate_device_ids(light1) - - mock_entry.api.bootstrap.lights = { - light1.id: light1, - } - mock_entry.api.bootstrap.id_lookup = { - light1.id: ProtectDeviceRef(id=light1.id, model=light1.model) - } - mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap) - registry = er.async_get(hass) registry.async_get_or_create( Platform.BUTTON, DOMAIN, - f"{light1.id}_reboot", - config_entry=mock_entry.entry, - suggested_object_id=light1.name, + f"{light.id}_reboot", + config_entry=ufp.entry, + suggested_object_id=light.display_name, ) registry.async_get_or_create( Platform.BUTTON, DOMAIN, - f"{light1.mac}_reboot", - config_entry=mock_entry.entry, - suggested_object_id=light1.name, + f"{light.mac}_reboot", + config_entry=ufp.entry, + suggested_object_id=light.display_name, ) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) + await init_entry(hass, ufp, [light], regenerate_ids=False) - assert mock_entry.entry.state == ConfigEntryState.LOADED - assert mock_entry.api.update.called - assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac + assert ufp.entry.state == ConfigEntryState.LOADED + assert ufp.api.update.called + assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - light = registry.async_get(f"{Platform.BUTTON}.test_light_1") - assert light is not None - assert light.unique_id == f"{light1.id}_reboot" + entity = registry.async_get(f"{Platform.BUTTON}.test_light") + assert entity is not None + assert entity.unique_id == f"{light.id}_reboot" async def test_migrate_device_mac_bootstrap_fail( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test migrating with a network error.""" - light1 = mock_light.copy() - light1._api = mock_entry.api - light1.name = "Test Light 1" - regenerate_device_ids(light1) - - mock_entry.api.bootstrap.lights = { - light1.id: light1, - } - mock_entry.api.get_bootstrap = AsyncMock(side_effect=NvrError) - registry = er.async_get(hass) registry.async_get_or_create( Platform.BUTTON, DOMAIN, - f"{light1.id}_reboot", - config_entry=mock_entry.entry, - suggested_object_id=light1.name, + f"{light.id}_reboot", + config_entry=ufp.entry, + suggested_object_id=light.name, ) registry.async_get_or_create( Platform.BUTTON, DOMAIN, - f"{light1.mac}_reboot", - config_entry=mock_entry.entry, - suggested_object_id=light1.name, + f"{light.mac}_reboot", + config_entry=ufp.entry, + suggested_object_id=light.name, ) - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + ufp.api.get_bootstrap = AsyncMock(side_effect=NvrError) + await init_entry(hass, ufp, [light], regenerate_ids=False) - assert mock_entry.entry.state == ConfigEntryState.SETUP_RETRY + assert ufp.entry.state == ConfigEntryState.SETUP_RETRY diff --git a/tests/components/unifiprotect/test_number.py b/tests/components/unifiprotect/test_number.py index 043feae7925..656f7d08ba5 100644 --- a/tests/components/unifiprotect/test_number.py +++ b/tests/components/unifiprotect/test_number.py @@ -19,119 +19,23 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import ( - MockEntityFixture, +from .utils import ( + MockUFPFixture, assert_entity_counts, ids_from_device_description, - reset_objects, + init_entry, ) -@pytest.fixture(name="light") -async def light_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light -): - """Fixture for a single light for testing the number platform.""" - - # disable pydantic validation so mocking can happen - Light.__config__.validate_assignment = False - - 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) - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.lights = { - light_obj.id: light_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.NUMBER, 2, 2) - - yield light_obj - - Light.__config__.validate_assignment = True - - -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the number platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.can_optical_zoom = True - camera_obj.feature_flags.has_mic = True - # has_wdr is an the inverse of has HDR - camera_obj.feature_flags.has_hdr = False - camera_obj.isp_settings.wdr = 0 - camera_obj.mic_volume = 0 - camera_obj.isp_settings.zoom_position = 0 - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.NUMBER, 3, 3) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - -@pytest.fixture(name="doorlock") -async def doorlock_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_doorlock: Doorlock -): - """Fixture for a single doorlock for testing the number platform.""" - - # disable pydantic validation so mocking can happen - Doorlock.__config__.validate_assignment = False - - lock_obj = mock_doorlock.copy() - lock_obj._api = mock_entry.api - lock_obj.name = "Test Lock" - lock_obj.auto_close_time = timedelta(seconds=45) - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.doorlocks = { - lock_obj.id: lock_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.NUMBER, 1, 1) - - yield lock_obj - - Doorlock.__config__.validate_assignment = True - - async def test_number_setup_light( - hass: HomeAssistant, - light: Light, + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test number entity setup for light devices.""" - entity_registry = er.async_get(hass) + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.NUMBER, 2, 2) + entity_registry = er.async_get(hass) for description in LIGHT_NUMBERS: unique_id, entity_id = ids_from_device_description( Platform.NUMBER, light, description @@ -148,11 +52,13 @@ async def test_number_setup_light( async def test_number_setup_camera_all( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, camera: Camera ): """Test number entity setup for camera devices (all features).""" + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.NUMBER, 3, 3) + entity_registry = er.async_get(hass) for description in CAMERA_NUMBERS: @@ -171,64 +77,38 @@ async def test_number_setup_camera_all( async def test_number_setup_camera_none( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera + hass: HomeAssistant, ufp: MockUFPFixture, camera: Camera ): """Test number entity setup for camera devices (no features).""" - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.can_optical_zoom = False - camera_obj.feature_flags.has_mic = False + camera.feature_flags.can_optical_zoom = False + camera.feature_flags.has_mic = False # has_wdr is an the inverse of has HDR - camera_obj.feature_flags.has_hdr = True - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + camera.feature_flags.has_hdr = True + await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.NUMBER, 0, 0) async def test_number_setup_camera_missing_attr( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera + hass: HomeAssistant, ufp: MockUFPFixture, camera: Camera ): """Test number entity setup for camera devices (no features, bad attrs).""" - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags = None - - Camera.__config__.validate_assignment = True - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + camera.feature_flags = None + await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.NUMBER, 0, 0) -async def test_number_light_sensitivity(hass: HomeAssistant, light: Light): +async def test_number_light_sensitivity( + hass: HomeAssistant, ufp: MockUFPFixture, light: Light +): """Test sensitivity number entity for lights.""" + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.NUMBER, 2, 2) + description = LIGHT_NUMBERS[0] assert description.ufp_set_method is not None @@ -244,9 +124,14 @@ async def test_number_light_sensitivity(hass: HomeAssistant, light: Light): light.set_sensitivity.assert_called_once_with(15.0) -async def test_number_light_duration(hass: HomeAssistant, light: Light): +async def test_number_light_duration( + hass: HomeAssistant, ufp: MockUFPFixture, light: Light +): """Test auto-shutoff duration number entity for lights.""" + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.NUMBER, 2, 2) + description = LIGHT_NUMBERS[1] light.__fields__["set_duration"] = Mock() @@ -263,10 +148,16 @@ async def test_number_light_duration(hass: HomeAssistant, light: Light): @pytest.mark.parametrize("description", CAMERA_NUMBERS) async def test_number_camera_simple( - hass: HomeAssistant, camera: Camera, description: ProtectNumberEntityDescription + hass: HomeAssistant, + ufp: MockUFPFixture, + camera: Camera, + description: ProtectNumberEntityDescription, ): """Tests all simple numbers for cameras.""" + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.NUMBER, 3, 3) + assert description.ufp_set_method is not None camera.__fields__[description.ufp_set_method] = Mock() @@ -282,9 +173,14 @@ async def test_number_camera_simple( set_method.assert_called_once_with(1.0) -async def test_number_lock_auto_close(hass: HomeAssistant, doorlock: Doorlock): +async def test_number_lock_auto_close( + hass: HomeAssistant, ufp: MockUFPFixture, doorlock: Doorlock +): """Test auto-lock timeout for locks.""" + await init_entry(hass, ufp, [doorlock]) + assert_entity_counts(hass, Platform.NUMBER, 1, 1) + description = DOORLOCK_NUMBERS[0] doorlock.__fields__["set_auto_close_time"] = Mock() diff --git a/tests/components/unifiprotect/test_select.py b/tests/components/unifiprotect/test_select.py index 01263a13cd9..637a0d4ad5d 100644 --- a/tests/components/unifiprotect/test_select.py +++ b/tests/components/unifiprotect/test_select.py @@ -3,7 +3,7 @@ from __future__ import annotations from copy import copy -from datetime import timedelta +from datetime import datetime, timedelta from unittest.mock import AsyncMock, Mock, patch import pytest @@ -38,162 +38,24 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, ATTR_OPTION, P from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from homeassistant.util.dt import utcnow -from .conftest import ( - MockEntityFixture, +from .utils import ( + MockUFPFixture, assert_entity_counts, ids_from_device_description, - reset_objects, + init_entry, ) -@pytest.fixture(name="viewer") -async def viewer_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_viewer: Viewer, - mock_liveview: Liveview, -): - """Fixture for a single viewport for testing the select platform.""" - - # disable pydantic validation so mocking can happen - Viewer.__config__.validate_assignment = False - - viewer_obj = mock_viewer.copy() - viewer_obj._api = mock_entry.api - viewer_obj.name = "Test Viewer" - viewer_obj.liveview_id = mock_liveview.id - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.viewers = { - viewer_obj.id: viewer_obj, - } - mock_entry.api.bootstrap.liveviews = {mock_liveview.id: mock_liveview} - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SELECT, 1, 1) - - yield viewer_obj - - Viewer.__config__.validate_assignment = True - - -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the select platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_lcd_screen = True - camera_obj.feature_flags.has_chime = True - camera_obj.recording_settings.mode = RecordingMode.ALWAYS - camera_obj.isp_settings.ir_led_mode = IRLEDMode.AUTO - camera_obj.lcd_message = None - camera_obj.chime_duration = 0 - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SELECT, 4, 4) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - -@pytest.fixture(name="light") -async def light_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_light: Light, - camera: Camera, -): - """Fixture for a single light for testing the select platform.""" - - # disable pydantic validation so mocking can happen - Light.__config__.validate_assignment = False - - 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 - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = {camera.id: camera} - mock_entry.api.bootstrap.lights = { - light_obj.id: light_obj, - } - - await hass.config_entries.async_reload(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SELECT, 6, 6) - - yield light_obj - - Light.__config__.validate_assignment = True - - -@pytest.fixture(name="camera_none") -async def camera_none_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the select platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_lcd_screen = False - camera_obj.feature_flags.has_chime = False - camera_obj.recording_settings.mode = RecordingMode.ALWAYS - camera_obj.isp_settings.ir_led_mode = IRLEDMode.AUTO - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SELECT, 2, 2) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - async def test_select_setup_light( - hass: HomeAssistant, - light: Light, + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test select entity setup for light devices.""" + light.light_mode_settings.enable_at = LightModeEnableType.DARK + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.SELECT, 2, 2) + entity_registry = er.async_get(hass) expected_values = ("On Motion - When Dark", "Not Paired") @@ -213,11 +75,14 @@ async def test_select_setup_light( async def test_select_setup_viewer( - hass: HomeAssistant, - viewer: Viewer, + hass: HomeAssistant, ufp: MockUFPFixture, viewer: Viewer, liveview: Liveview ): """Test select entity setup for light devices.""" + ufp.api.bootstrap.liveviews = {liveview.id: liveview} + await init_entry(hass, ufp, [viewer]) + assert_entity_counts(hass, Platform.SELECT, 1, 1) + entity_registry = er.async_get(hass) description = VIEWER_SELECTS[0] @@ -236,15 +101,46 @@ async def test_select_setup_viewer( async def test_select_setup_camera_all( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test select entity setup for camera devices (all features).""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + entity_registry = er.async_get(hass) expected_values = ("Always", "Auto", "Default Message (Welcome)", "None") for index, description in enumerate(CAMERA_SELECTS): + unique_id, entity_id = ids_from_device_description( + Platform.SELECT, doorbell, description + ) + + entity = entity_registry.async_get(entity_id) + assert entity + assert entity.unique_id == unique_id + + state = hass.states.get(entity_id) + assert state + assert state.state == expected_values[index] + assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION + + +async def test_select_setup_camera_none( + hass: HomeAssistant, ufp: MockUFPFixture, camera: Camera +): + """Test select entity setup for camera devices (no features).""" + + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.SELECT, 2, 2) + + entity_registry = er.async_get(hass) + expected_values = ("Always", "Auto", "Default Message (Welcome)") + + for index, description in enumerate(CAMERA_SELECTS): + if index == 2: + return + unique_id, entity_id = ids_from_device_description( Platform.SELECT, camera, description ) @@ -259,41 +155,15 @@ async def test_select_setup_camera_all( assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION -async def test_select_setup_camera_none( - hass: HomeAssistant, - camera_none: Camera, -): - """Test select entity setup for camera devices (no features).""" - - entity_registry = er.async_get(hass) - expected_values = ("Always", "Auto", "Default Message (Welcome)") - - for index, description in enumerate(CAMERA_SELECTS): - if index == 2: - return - - unique_id, entity_id = ids_from_device_description( - Platform.SELECT, camera_none, description - ) - - entity = entity_registry.async_get(entity_id) - assert entity - assert entity.unique_id == unique_id - - state = hass.states.get(entity_id) - assert state - assert state.state == expected_values[index] - assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - - async def test_select_update_liveview( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - viewer: Viewer, - mock_liveview: Liveview, + hass: HomeAssistant, ufp: MockUFPFixture, viewer: Viewer, liveview: Liveview ): """Test select entity update (new Liveview).""" + ufp.api.bootstrap.liveviews = {liveview.id: liveview} + await init_entry(hass, ufp, [viewer]) + assert_entity_counts(hass, Platform.SELECT, 1, 1) + _, entity_id = ids_from_device_description( Platform.SELECT, viewer, VIEWER_SELECTS[0] ) @@ -302,17 +172,18 @@ async def test_select_update_liveview( assert state expected_options = state.attributes[ATTR_OPTIONS] - new_bootstrap = copy(mock_entry.api.bootstrap) - new_liveview = copy(mock_liveview) + new_liveview = copy(liveview) new_liveview.id = "test_id" mock_msg = Mock() mock_msg.changed_data = {} mock_msg.new_obj = new_liveview - new_bootstrap.liveviews = {**new_bootstrap.liveviews, new_liveview.id: new_liveview} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.liveviews = { + **ufp.api.bootstrap.liveviews, + new_liveview.id: new_liveview, + } + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -321,16 +192,17 @@ async def test_select_update_liveview( async def test_select_update_doorbell_settings( - hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test select entity update (new Doorbell Message).""" - expected_length = ( - len(mock_entry.api.bootstrap.nvr.doorbell_settings.all_messages) + 1 - ) + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + + expected_length = len(ufp.api.bootstrap.nvr.doorbell_settings.all_messages) + 1 _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) state = hass.states.get(entity_id) @@ -338,7 +210,7 @@ async def test_select_update_doorbell_settings( assert len(state.attributes[ATTR_OPTIONS]) == expected_length expected_length += 1 - new_nvr = copy(mock_entry.api.bootstrap.nvr) + new_nvr = copy(ufp.api.bootstrap.nvr) new_nvr.__fields__["update_all_messages"] = Mock() new_nvr.update_all_messages = Mock() @@ -354,8 +226,8 @@ async def test_select_update_doorbell_settings( mock_msg.changed_data = {"doorbell_settings": {}} mock_msg.new_obj = new_nvr - mock_entry.api.bootstrap.nvr = new_nvr - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.nvr = new_nvr + ufp.ws_msg(mock_msg) await hass.async_block_till_done() new_nvr.update_all_messages.assert_called_once() @@ -366,22 +238,22 @@ async def test_select_update_doorbell_settings( async def test_select_update_doorbell_message( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test select entity update (change doorbell message).""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) state = hass.states.get(entity_id) assert state assert state.state == "Default Message (Welcome)" - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera.copy() + new_camera = doorbell.copy() new_camera.lcd_message = LCDMessage( type=DoorbellMessageType.CUSTOM_MESSAGE, text="Test" ) @@ -390,9 +262,8 @@ async def test_select_update_doorbell_message( mock_msg.changed_data = {} mock_msg.new_obj = new_camera - new_bootstrap.cameras = {new_camera.id: new_camera} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -401,10 +272,13 @@ async def test_select_update_doorbell_message( async def test_select_set_option_light_motion( - hass: HomeAssistant, - light: Light, + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): """Test Light Mode select.""" + + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.SELECT, 2, 2) + _, entity_id = ids_from_device_description(Platform.SELECT, light, LIGHT_SELECTS[0]) light.__fields__["set_light_settings"] = Mock() @@ -423,10 +297,13 @@ async def test_select_set_option_light_motion( async def test_select_set_option_light_camera( - hass: HomeAssistant, - light: Light, + hass: HomeAssistant, ufp: MockUFPFixture, light: Light, camera: Camera ): """Test Paired Camera select.""" + + await init_entry(hass, ufp, [light, camera]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description(Platform.SELECT, light, LIGHT_SELECTS[1]) light.__fields__["set_paired_camera"] = Mock() @@ -454,16 +331,19 @@ async def test_select_set_option_light_camera( async def test_select_set_option_camera_recording( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Recording Mode select.""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[0] + Platform.SELECT, doorbell, CAMERA_SELECTS[0] ) - camera.__fields__["set_recording_mode"] = Mock() - camera.set_recording_mode = AsyncMock() + doorbell.__fields__["set_recording_mode"] = Mock() + doorbell.set_recording_mode = AsyncMock() await hass.services.async_call( "select", @@ -472,20 +352,23 @@ async def test_select_set_option_camera_recording( blocking=True, ) - camera.set_recording_mode.assert_called_once_with(RecordingMode.NEVER) + doorbell.set_recording_mode.assert_called_once_with(RecordingMode.NEVER) async def test_select_set_option_camera_ir( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Infrared Mode select.""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[1] + Platform.SELECT, doorbell, CAMERA_SELECTS[1] ) - camera.__fields__["set_ir_led_model"] = Mock() - camera.set_ir_led_model = AsyncMock() + doorbell.__fields__["set_ir_led_model"] = Mock() + doorbell.set_ir_led_model = AsyncMock() await hass.services.async_call( "select", @@ -494,20 +377,23 @@ async def test_select_set_option_camera_ir( blocking=True, ) - camera.set_ir_led_model.assert_called_once_with(IRLEDMode.ON) + doorbell.set_ir_led_model.assert_called_once_with(IRLEDMode.ON) async def test_select_set_option_camera_doorbell_custom( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Doorbell Text select (user defined message).""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - camera.__fields__["set_lcd_text"] = Mock() - camera.set_lcd_text = AsyncMock() + doorbell.__fields__["set_lcd_text"] = Mock() + doorbell.set_lcd_text = AsyncMock() await hass.services.async_call( "select", @@ -516,22 +402,25 @@ async def test_select_set_option_camera_doorbell_custom( blocking=True, ) - camera.set_lcd_text.assert_called_once_with( + doorbell.set_lcd_text.assert_called_once_with( DoorbellMessageType.CUSTOM_MESSAGE, text="Test" ) async def test_select_set_option_camera_doorbell_unifi( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Doorbell Text select (unifi message).""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - camera.__fields__["set_lcd_text"] = Mock() - camera.set_lcd_text = AsyncMock() + doorbell.__fields__["set_lcd_text"] = Mock() + doorbell.set_lcd_text = AsyncMock() await hass.services.async_call( "select", @@ -543,7 +432,7 @@ async def test_select_set_option_camera_doorbell_unifi( blocking=True, ) - camera.set_lcd_text.assert_called_once_with( + doorbell.set_lcd_text.assert_called_once_with( DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR ) @@ -557,20 +446,23 @@ async def test_select_set_option_camera_doorbell_unifi( blocking=True, ) - camera.set_lcd_text.assert_called_with(None) + doorbell.set_lcd_text.assert_called_with(None) async def test_select_set_option_camera_doorbell_default( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Doorbell Text select (default message).""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - camera.__fields__["set_lcd_text"] = Mock() - camera.set_lcd_text = AsyncMock() + doorbell.__fields__["set_lcd_text"] = Mock() + doorbell.set_lcd_text = AsyncMock() await hass.services.async_call( "select", @@ -582,14 +474,18 @@ async def test_select_set_option_camera_doorbell_default( blocking=True, ) - camera.set_lcd_text.assert_called_once_with(None) + doorbell.set_lcd_text.assert_called_once_with(None) async def test_select_set_option_viewer( - hass: HomeAssistant, - viewer: Viewer, + hass: HomeAssistant, ufp: MockUFPFixture, viewer: Viewer, liveview: Liveview ): """Test Liveview select.""" + + ufp.api.bootstrap.liveviews = {liveview.id: liveview} + await init_entry(hass, ufp, [viewer]) + assert_entity_counts(hass, Platform.SELECT, 1, 1) + _, entity_id = ids_from_device_description( Platform.SELECT, viewer, VIEWER_SELECTS[0] ) @@ -610,16 +506,19 @@ async def test_select_set_option_viewer( async def test_select_service_doorbell_invalid( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Doorbell Text service (invalid).""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[1] + Platform.SELECT, doorbell, CAMERA_SELECTS[1] ) - camera.__fields__["set_lcd_text"] = Mock() - camera.set_lcd_text = AsyncMock() + doorbell.__fields__["set_lcd_text"] = Mock() + doorbell.set_lcd_text = AsyncMock() with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -629,20 +528,23 @@ async def test_select_service_doorbell_invalid( blocking=True, ) - assert not camera.set_lcd_text.called + assert not doorbell.set_lcd_text.called async def test_select_service_doorbell_success( - hass: HomeAssistant, - camera: Camera, + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Test Doorbell Text service (success).""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - camera.__fields__["set_lcd_text"] = Mock() - camera.set_lcd_text = AsyncMock() + doorbell.__fields__["set_lcd_text"] = Mock() + doorbell.set_lcd_text = AsyncMock() await hass.services.async_call( "unifiprotect", @@ -654,7 +556,7 @@ async def test_select_service_doorbell_success( blocking=True, ) - camera.set_lcd_text.assert_called_once_with( + doorbell.set_lcd_text.assert_called_once_with( DoorbellMessageType.CUSTOM_MESSAGE, "Test", reset_at=None ) @@ -663,18 +565,23 @@ async def test_select_service_doorbell_success( async def test_select_service_doorbell_with_reset( mock_now, hass: HomeAssistant, - camera: Camera, + ufp: MockUFPFixture, + doorbell: Camera, + fixed_now: datetime, ): """Test Doorbell Text service (success with reset time).""" - now = utcnow() - mock_now.return_value = now + + mock_now.return_value = fixed_now _, entity_id = ids_from_device_description( - Platform.SELECT, camera, CAMERA_SELECTS[2] + Platform.SELECT, doorbell, CAMERA_SELECTS[2] ) - camera.__fields__["set_lcd_text"] = Mock() - camera.set_lcd_text = AsyncMock() + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SELECT, 4, 4) + + doorbell.__fields__["set_lcd_text"] = Mock() + doorbell.set_lcd_text = AsyncMock() await hass.services.async_call( "unifiprotect", @@ -687,8 +594,8 @@ async def test_select_service_doorbell_with_reset( blocking=True, ) - camera.set_lcd_text.assert_called_once_with( + doorbell.set_lcd_text.assert_called_once_with( DoorbellMessageType.CUSTOM_MESSAGE, "Test", - reset_at=now + timedelta(minutes=60), + reset_at=fixed_now + timedelta(minutes=60), ) diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index 1a84c4f55ca..e204b09b1b0 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -2,11 +2,9 @@ # pylint: disable=protected-access from __future__ import annotations -from copy import copy from datetime import datetime, timedelta from unittest.mock import AsyncMock, Mock -import pytest from pyunifiprotect.data import ( NVR, Camera, @@ -15,7 +13,6 @@ from pyunifiprotect.data import ( Sensor, SmartDetectObjectType, ) -from pyunifiprotect.data.base import WifiConnectionState, WiredConnectionState from pyunifiprotect.data.nvr import EventMetadata from homeassistant.components.unifiprotect.const import ( @@ -42,11 +39,12 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import ( - MockEntityFixture, +from .utils import ( + MockUFPFixture, assert_entity_counts, enable_entity, ids_from_device_description, + init_entry, reset_objects, time_changed, ) @@ -55,136 +53,12 @@ CAMERA_SENSORS_WRITE = CAMERA_SENSORS[:5] SENSE_SENSORS_WRITE = SENSE_SENSORS[:8] -@pytest.fixture(name="sensor") -async def sensor_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_sensor: Sensor, - now: datetime, -): - """Fixture for a single sensor for testing the sensor platform.""" - - # disable pydantic validation so mocking can happen - Sensor.__config__.validate_assignment = False - - sensor_obj = mock_sensor.copy() - sensor_obj._api = mock_entry.api - sensor_obj.name = "Test Sensor" - sensor_obj.battery_status.percentage = 10.0 - sensor_obj.light_settings.is_enabled = True - sensor_obj.humidity_settings.is_enabled = True - sensor_obj.temperature_settings.is_enabled = True - sensor_obj.alarm_settings.is_enabled = True - sensor_obj.stats.light.value = 10.0 - sensor_obj.stats.humidity.value = 10.0 - sensor_obj.stats.temperature.value = 10.0 - sensor_obj.up_since = now - sensor_obj.bluetooth_connection_state.signal_strength = -50.0 - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.sensors = { - sensor_obj.id: sensor_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - yield sensor_obj - - Sensor.__config__.validate_assignment = True - - -@pytest.fixture(name="sensor_none") -async def sensor_none_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_sensor: Sensor, - now: datetime, -): - """Fixture for a single sensor for testing the sensor platform.""" - - # disable pydantic validation so mocking can happen - Sensor.__config__.validate_assignment = False - - sensor_obj = mock_sensor.copy() - sensor_obj._api = mock_entry.api - sensor_obj.name = "Test Sensor" - sensor_obj.battery_status.percentage = 10.0 - sensor_obj.light_settings.is_enabled = False - sensor_obj.humidity_settings.is_enabled = False - sensor_obj.temperature_settings.is_enabled = False - sensor_obj.alarm_settings.is_enabled = False - sensor_obj.up_since = now - sensor_obj.bluetooth_connection_state.signal_strength = -50.0 - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.sensors = { - sensor_obj.id: sensor_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - # 4 from all, 5 from sense, 12 NVR - assert_entity_counts(hass, Platform.SENSOR, 22, 14) - - yield sensor_obj - - Sensor.__config__.validate_assignment = True - - -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_camera: Camera, - now: datetime, -): - """Fixture for a single camera for testing the sensor platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.feature_flags.has_smart_detect = True - camera_obj.feature_flags.has_chime = True - camera_obj.is_smart_detected = False - camera_obj.wired_connection_state = WiredConnectionState(phy_rate=1000) - camera_obj.wifi_connection_state = WifiConnectionState( - signal_quality=100, signal_strength=-50 - ) - camera_obj.stats.rx_bytes = 100.0 - camera_obj.stats.tx_bytes = 100.0 - camera_obj.stats.video.recording_start = now - camera_obj.stats.storage.used = 100.0 - camera_obj.stats.storage.used = 100.0 - camera_obj.stats.storage.rate = 0.1 - camera_obj.voltage = 20.0 - - 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, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - yield camera_obj - - Camera.__config__.validate_assignment = True - - async def test_sensor_setup_sensor( - hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor + hass: HomeAssistant, ufp: MockUFPFixture, sensor_all: Sensor ): """Test sensor entity setup for sensor devices.""" - # 5 from all, 5 from sense, 12 NVR + + await init_entry(hass, ufp, [sensor_all]) assert_entity_counts(hass, Platform.SENSOR, 22, 14) entity_registry = er.async_get(hass) @@ -196,6 +70,57 @@ async def test_sensor_setup_sensor( "10.0", "none", ) + for index, description in enumerate(SENSE_SENSORS_WRITE): + if not description.entity_registry_enabled_default: + continue + unique_id, entity_id = ids_from_device_description( + Platform.SENSOR, sensor_all, description + ) + + entity = entity_registry.async_get(entity_id) + assert entity + assert entity.unique_id == unique_id + + state = hass.states.get(entity_id) + assert state + assert state.state == expected_values[index] + assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION + + # BLE signal + unique_id, entity_id = ids_from_device_description( + Platform.SENSOR, sensor_all, ALL_DEVICES_SENSORS[1] + ) + + entity = entity_registry.async_get(entity_id) + assert entity + assert entity.disabled is True + assert entity.unique_id == unique_id + + await enable_entity(hass, ufp.entry.entry_id, entity_id) + + state = hass.states.get(entity_id) + assert state + assert state.state == "-50" + assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION + + +async def test_sensor_setup_sensor_none( + hass: HomeAssistant, ufp: MockUFPFixture, sensor: Sensor +): + """Test sensor entity setup for sensor devices with no sensors enabled.""" + + await init_entry(hass, ufp, [sensor]) + assert_entity_counts(hass, Platform.SENSOR, 22, 14) + + entity_registry = er.async_get(hass) + + expected_values = ( + "10", + STATE_UNAVAILABLE, + STATE_UNAVAILABLE, + STATE_UNAVAILABLE, + STATE_UNAVAILABLE, + ) for index, description in enumerate(SENSE_SENSORS_WRITE): if not description.entity_registry_enabled_default: continue @@ -212,63 +137,15 @@ async def test_sensor_setup_sensor( assert state.state == expected_values[index] assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - # BLE signal - unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, sensor, ALL_DEVICES_SENSORS[1] - ) - - entity = entity_registry.async_get(entity_id) - assert entity - assert entity.disabled is True - assert entity.unique_id == unique_id - - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - - state = hass.states.get(entity_id) - assert state - assert state.state == "-50" - assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - - -async def test_sensor_setup_sensor_none( - hass: HomeAssistant, mock_entry: MockEntityFixture, sensor_none: Sensor -): - """Test sensor entity setup for sensor devices with no sensors enabled.""" - - entity_registry = er.async_get(hass) - - expected_values = ( - "10", - STATE_UNAVAILABLE, - STATE_UNAVAILABLE, - STATE_UNAVAILABLE, - STATE_UNAVAILABLE, - ) - for index, description in enumerate(SENSE_SENSORS_WRITE): - if not description.entity_registry_enabled_default: - continue - unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, sensor_none, description - ) - - entity = entity_registry.async_get(entity_id) - assert entity - assert entity.unique_id == unique_id - - state = hass.states.get(entity_id) - assert state - assert state.state == expected_values[index] - assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - async def test_sensor_setup_nvr( - hass: HomeAssistant, mock_entry: MockEntityFixture, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, fixed_now: datetime ): """Test sensor entity setup for NVR device.""" - reset_objects(mock_entry.api.bootstrap) - nvr: NVR = mock_entry.api.bootstrap.nvr - nvr.up_since = now + reset_objects(ufp.api.bootstrap) + nvr: NVR = ufp.api.bootstrap.nvr + nvr.up_since = fixed_now nvr.system_info.cpu.average_load = 50.0 nvr.system_info.cpu.temperature = 50.0 nvr.storage_stats.utilization = 50.0 @@ -282,16 +159,15 @@ async def test_sensor_setup_nvr( nvr.storage_stats.storage_distribution.free.percentage = 50.0 nvr.storage_stats.capacity = 50.0 - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - # 2 from all, 4 from sense, 12 NVR assert_entity_counts(hass, Platform.SENSOR, 12, 9) entity_registry = er.async_get(hass) expected_values = ( - now.replace(second=0, microsecond=0).isoformat(), + fixed_now.replace(second=0, microsecond=0).isoformat(), "50.0", "50.0", "50.0", @@ -312,7 +188,7 @@ async def test_sensor_setup_nvr( assert entity.unique_id == unique_id if not description.entity_registry_enabled_default: - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -330,7 +206,7 @@ async def test_sensor_setup_nvr( assert entity.disabled is not description.entity_registry_enabled_default assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -338,22 +214,19 @@ async def test_sensor_setup_nvr( assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION -async def test_sensor_nvr_missing_values( - hass: HomeAssistant, mock_entry: MockEntityFixture, now: datetime -): +async def test_sensor_nvr_missing_values(hass: HomeAssistant, ufp: MockUFPFixture): """Test NVR sensor sensors if no data available.""" - reset_objects(mock_entry.api.bootstrap) - nvr: NVR = mock_entry.api.bootstrap.nvr + reset_objects(ufp.api.bootstrap) + nvr: NVR = ufp.api.bootstrap.nvr nvr.system_info.memory.available = None nvr.system_info.memory.total = None nvr.up_since = None nvr.storage_stats.capacity = None - await hass.config_entries.async_setup(mock_entry.entry.entry_id) + await hass.config_entries.async_setup(ufp.entry.entry_id) await hass.async_block_till_done() - # 2 from all, 4 from sense, 12 NVR assert_entity_counts(hass, Platform.SENSOR, 12, 9) entity_registry = er.async_get(hass) @@ -368,7 +241,7 @@ async def test_sensor_nvr_missing_values( assert entity assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -401,7 +274,7 @@ async def test_sensor_nvr_missing_values( assert entity.disabled is True assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -410,16 +283,17 @@ async def test_sensor_nvr_missing_values( async def test_sensor_setup_camera( - hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera, fixed_now: datetime ): """Test sensor entity setup for camera devices.""" - # 3 from all, 7 from camera, 12 NVR + + await init_entry(hass, ufp, [doorbell]) assert_entity_counts(hass, Platform.SENSOR, 25, 13) entity_registry = er.async_get(hass) expected_values = ( - now.replace(microsecond=0).isoformat(), + fixed_now.replace(microsecond=0).isoformat(), "100", "100.0", "20.0", @@ -428,7 +302,7 @@ async def test_sensor_setup_camera( if not description.entity_registry_enabled_default: continue unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, camera, description + Platform.SENSOR, doorbell, description ) entity = entity_registry.async_get(entity_id) @@ -444,7 +318,7 @@ async def test_sensor_setup_camera( expected_values = ("100", "100") for index, description in enumerate(CAMERA_DISABLED_SENSORS): unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, camera, description + Platform.SENSOR, doorbell, description ) entity = entity_registry.async_get(entity_id) @@ -452,7 +326,7 @@ async def test_sensor_setup_camera( assert entity.disabled is not description.entity_registry_enabled_default assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -461,7 +335,7 @@ async def test_sensor_setup_camera( # Wired signal unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, camera, ALL_DEVICES_SENSORS[2] + Platform.SENSOR, doorbell, ALL_DEVICES_SENSORS[2] ) entity = entity_registry.async_get(entity_id) @@ -469,7 +343,7 @@ async def test_sensor_setup_camera( assert entity.disabled is True assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -478,7 +352,7 @@ async def test_sensor_setup_camera( # WiFi signal unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, camera, ALL_DEVICES_SENSORS[3] + Platform.SENSOR, doorbell, ALL_DEVICES_SENSORS[3] ) entity = entity_registry.async_get(entity_id) @@ -486,7 +360,7 @@ async def test_sensor_setup_camera( assert entity.disabled is True assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -495,7 +369,7 @@ async def test_sensor_setup_camera( # Detected Object unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, camera, MOTION_SENSORS[0] + Platform.SENSOR, doorbell, MOTION_SENSORS[0] ) entity = entity_registry.async_get(entity_id) @@ -512,16 +386,20 @@ async def test_sensor_setup_camera( async def test_sensor_setup_camera_with_last_trip_time( hass: HomeAssistant, entity_registry_enabled_by_default: AsyncMock, - mock_entry: MockEntityFixture, - camera: Camera, - now: datetime, + ufp: MockUFPFixture, + doorbell: Camera, + fixed_now: datetime, ): """Test sensor entity setup for camera devices with last trip time.""" + + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SENSOR, 25, 25) + entity_registry = er.async_get(hass) # Last Trip Time unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, camera, MOTION_TRIP_SENSORS[0] + Platform.SENSOR, doorbell, MOTION_TRIP_SENSORS[0] ) entity = entity_registry.async_get(entity_id) @@ -530,35 +408,38 @@ async def test_sensor_setup_camera_with_last_trip_time( state = hass.states.get(entity_id) assert state - assert state.state == "2021-12-20T17:26:53+00:00" + assert ( + state.state + == (fixed_now - timedelta(hours=1)).replace(microsecond=0).isoformat() + ) assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION async def test_sensor_update_motion( - hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera, fixed_now: datetime ): """Test sensor motion entity.""" - # 3 from all, 7 from camera, 12 NVR + + await init_entry(hass, ufp, [doorbell]) assert_entity_counts(hass, Platform.SENSOR, 25, 13) _, entity_id = ids_from_device_description( - Platform.SENSOR, camera, MOTION_SENSORS[0] + Platform.SENSOR, doorbell, MOTION_SENSORS[0] ) event = Event( id="test_event_id", type=EventType.SMART_DETECT, - start=now - timedelta(seconds=1), + start=fixed_now - timedelta(seconds=1), end=None, score=100, smart_detect_types=[SmartDetectObjectType.PERSON], smart_detect_event_ids=[], - camera_id=camera.id, - api=mock_entry.api, + camera_id=doorbell.id, + api=ufp.api, ) - new_bootstrap = copy(mock_entry.api.bootstrap) - new_camera = camera.copy() + new_camera = doorbell.copy() new_camera.is_smart_detected = True new_camera.last_smart_detect_event_id = event.id @@ -566,10 +447,9 @@ async def test_sensor_update_motion( mock_msg.changed_data = {} mock_msg.new_obj = event - new_bootstrap.cameras = {new_camera.id: new_camera} - new_bootstrap.events = {event.id: event} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.cameras = {new_camera.id: new_camera} + ufp.api.bootstrap.events = {event.id: event} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -580,31 +460,31 @@ async def test_sensor_update_motion( async def test_sensor_update_alarm( - hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor, now: datetime + hass: HomeAssistant, ufp: MockUFPFixture, sensor_all: Sensor, fixed_now: datetime ): """Test sensor motion entity.""" - # 5 from all, 5 from sense, 12 NVR + + await init_entry(hass, ufp, [sensor_all]) assert_entity_counts(hass, Platform.SENSOR, 22, 14) _, entity_id = ids_from_device_description( - Platform.SENSOR, sensor, SENSE_SENSORS_WRITE[4] + Platform.SENSOR, sensor_all, SENSE_SENSORS_WRITE[4] ) - event_metadata = EventMetadata(sensor_id=sensor.id, alarm_type="smoke") + event_metadata = EventMetadata(sensor_id=sensor_all.id, alarm_type="smoke") event = Event( id="test_event_id", type=EventType.SENSOR_ALARM, - start=now - timedelta(seconds=1), + start=fixed_now - timedelta(seconds=1), end=None, score=100, smart_detect_types=[], smart_detect_event_ids=[], metadata=event_metadata, - api=mock_entry.api, + api=ufp.api, ) - new_bootstrap = copy(mock_entry.api.bootstrap) - new_sensor = sensor.copy() + new_sensor = sensor_all.copy() new_sensor.set_alarm_timeout() new_sensor.last_alarm_event_id = event.id @@ -612,10 +492,9 @@ async def test_sensor_update_alarm( mock_msg.changed_data = {} mock_msg.new_obj = event - new_bootstrap.sensors = {new_sensor.id: new_sensor} - new_bootstrap.events = {event.id: event} - mock_entry.api.bootstrap = new_bootstrap - mock_entry.api.ws_subscription(mock_msg) + ufp.api.bootstrap.sensors = {new_sensor.id: new_sensor} + ufp.api.bootstrap.events = {event.id: event} + ufp.ws_msg(mock_msg) await hass.async_block_till_done() state = hass.states.get(entity_id) @@ -627,15 +506,18 @@ async def test_sensor_update_alarm( async def test_sensor_update_alarm_with_last_trip_time( hass: HomeAssistant, entity_registry_enabled_by_default: AsyncMock, - mock_entry: MockEntityFixture, - sensor: Sensor, - now: datetime, + ufp: MockUFPFixture, + sensor_all: Sensor, + fixed_now: datetime, ): """Test sensor motion entity with last trip time.""" + await init_entry(hass, ufp, [sensor_all]) + assert_entity_counts(hass, Platform.SENSOR, 22, 22) + # Last Trip Time unique_id, entity_id = ids_from_device_description( - Platform.SENSOR, sensor, SENSE_SENSORS_WRITE[-3] + Platform.SENSOR, sensor_all, SENSE_SENSORS_WRITE[-3] ) entity_registry = er.async_get(hass) @@ -645,5 +527,8 @@ async def test_sensor_update_alarm_with_last_trip_time( state = hass.states.get(entity_id) assert state - assert state.state == "2022-01-04T04:03:56+00:00" + assert ( + state.state + == (fixed_now - timedelta(hours=1)).replace(microsecond=0).isoformat() + ) assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION diff --git a/tests/components/unifiprotect/test_services.py b/tests/components/unifiprotect/test_services.py index d957fe16b4b..460ba488cb2 100644 --- a/tests/components/unifiprotect/test_services.py +++ b/tests/components/unifiprotect/test_services.py @@ -6,7 +6,6 @@ from unittest.mock import AsyncMock, Mock import pytest 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 @@ -21,15 +20,14 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er -from .conftest import MockEntityFixture, regenerate_device_ids +from .utils import MockUFPFixture, init_entry @pytest.fixture(name="device") -async def device_fixture(hass: HomeAssistant, mock_entry: MockEntityFixture): +async def device_fixture(hass: HomeAssistant, ufp: MockUFPFixture): """Fixture with entry setup to call services with.""" - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + await init_entry(hass, ufp, []) device_registry = dr.async_get(hass) @@ -37,30 +35,20 @@ async def device_fixture(hass: HomeAssistant, mock_entry: MockEntityFixture): @pytest.fixture(name="subdevice") -async def subdevice_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light -): +async def subdevice_fixture(hass: HomeAssistant, ufp: MockUFPFixture, light: Light): """Fixture with entry setup to call services with.""" - mock_light._api = mock_entry.api - mock_entry.api.bootstrap.lights = { - mock_light.id: mock_light, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + await init_entry(hass, ufp, [light]) device_registry = dr.async_get(hass) return [d for d in device_registry.devices.values() if d.name != "UnifiProtect"][0] -async def test_global_service_bad_device( - hass: HomeAssistant, device: dr.DeviceEntry, mock_entry: MockEntityFixture -): +async def test_global_service_bad_device(hass: HomeAssistant, ufp: MockUFPFixture): """Test global service, invalid device ID.""" - nvr = mock_entry.api.bootstrap.nvr + nvr = ufp.api.bootstrap.nvr nvr.__fields__["add_custom_doorbell_message"] = Mock() nvr.add_custom_doorbell_message = AsyncMock() @@ -75,11 +63,11 @@ async def test_global_service_bad_device( async def test_global_service_exception( - hass: HomeAssistant, device: dr.DeviceEntry, mock_entry: MockEntityFixture + hass: HomeAssistant, device: dr.DeviceEntry, ufp: MockUFPFixture ): """Test global service, unexpected error.""" - nvr = mock_entry.api.bootstrap.nvr + nvr = ufp.api.bootstrap.nvr nvr.__fields__["add_custom_doorbell_message"] = Mock() nvr.add_custom_doorbell_message = AsyncMock(side_effect=BadRequest) @@ -94,11 +82,11 @@ async def test_global_service_exception( async def test_add_doorbell_text( - hass: HomeAssistant, device: dr.DeviceEntry, mock_entry: MockEntityFixture + hass: HomeAssistant, device: dr.DeviceEntry, ufp: MockUFPFixture ): """Test add_doorbell_text service.""" - nvr = mock_entry.api.bootstrap.nvr + nvr = ufp.api.bootstrap.nvr nvr.__fields__["add_custom_doorbell_message"] = Mock() nvr.add_custom_doorbell_message = AsyncMock() @@ -112,11 +100,11 @@ async def test_add_doorbell_text( async def test_remove_doorbell_text( - hass: HomeAssistant, subdevice: dr.DeviceEntry, mock_entry: MockEntityFixture + hass: HomeAssistant, subdevice: dr.DeviceEntry, ufp: MockUFPFixture ): """Test remove_doorbell_text service.""" - nvr = mock_entry.api.bootstrap.nvr + nvr = ufp.api.bootstrap.nvr nvr.__fields__["remove_custom_doorbell_message"] = Mock() nvr.remove_custom_doorbell_message = AsyncMock() @@ -130,11 +118,11 @@ async def test_remove_doorbell_text( async def test_set_default_doorbell_text( - hass: HomeAssistant, device: dr.DeviceEntry, mock_entry: MockEntityFixture + hass: HomeAssistant, device: dr.DeviceEntry, ufp: MockUFPFixture ): """Test set_default_doorbell_text service.""" - nvr = mock_entry.api.bootstrap.nvr + nvr = ufp.api.bootstrap.nvr nvr.__fields__["set_default_doorbell_message"] = Mock() nvr.set_default_doorbell_message = AsyncMock() @@ -149,57 +137,21 @@ async def test_set_default_doorbell_text( async def test_set_chime_paired_doorbells( hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_chime: Chime, - mock_camera: Camera, + ufp: MockUFPFixture, + chime: Chime, + doorbell: Camera, ): """Test set_chime_paired_doorbells.""" - mock_entry.api.update_device = AsyncMock() + ufp.api.update_device = AsyncMock() - mock_chime._api = mock_entry.api - mock_chime.name = "Test Chime" - mock_chime._initial_data = mock_chime.dict() - 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 = doorbell.copy() camera1.name = "Test Camera 1" - camera1._api = mock_entry.api - camera1.channels[0]._api = mock_entry.api - camera1.channels[1]._api = mock_entry.api - camera1.channels[2]._api = mock_entry.api - camera1.feature_flags.has_chime = True - regenerate_device_ids(camera1) - camera2 = mock_camera.copy() + camera2 = doorbell.copy() camera2.name = "Test Camera 2" - camera2._api = mock_entry.api - camera2.channels[0]._api = mock_entry.api - camera2.channels[1]._api = mock_entry.api - camera2.channels[2]._api = mock_entry.api - camera2.feature_flags.has_chime = True - regenerate_device_ids(camera2) - mock_entry.api.bootstrap.cameras = { - 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() + await init_entry(hass, ufp, [camera1, camera2, chime]) registry = er.async_get(hass) chime_entry = registry.async_get("button.test_chime_play_chime") @@ -220,6 +172,6 @@ async def test_set_chime_paired_doorbells( blocking=True, ) - mock_entry.api.update_device.assert_called_once_with( - ModelType.CHIME, mock_chime.id, {"cameraIds": sorted([camera1.id, camera2.id])} + ufp.api.update_device.assert_called_once_with( + ModelType.CHIME, chime.id, {"cameraIds": sorted([camera1.id, camera2.id])} ) diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index 6c9340af5d5..0c45ec28b7b 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -5,14 +5,7 @@ from __future__ import annotations from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import ( - Camera, - Light, - Permission, - RecordingMode, - SmartDetectObjectType, - VideoMode, -) +from pyunifiprotect.data import Camera, Light, Permission, RecordingMode, VideoMode from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.switch import ( @@ -24,12 +17,12 @@ from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, STATE_OFF, Pla from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .conftest import ( - MockEntityFixture, +from .utils import ( + MockUFPFixture, assert_entity_counts, enable_entity, ids_from_device_description, - reset_objects, + init_entry, ) CAMERA_SWITCHES_BASIC = [ @@ -44,218 +37,33 @@ CAMERA_SWITCHES_NO_EXTRA = [ ] -@pytest.fixture(name="light") -async def light_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light -): - """Fixture for a single light for testing the switch platform.""" - - # disable pydantic validation so mocking can happen - Light.__config__.validate_assignment = False - - 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 - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.lights = { - light_obj.id: light_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SWITCH, 2, 1) - - yield light_obj - - Light.__config__.validate_assignment = True - - -@pytest.fixture(name="camera") -async def camera_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the switch platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.recording_settings.mode = RecordingMode.DETECTIONS - camera_obj.feature_flags.has_led_status = True - camera_obj.feature_flags.has_hdr = True - camera_obj.feature_flags.video_modes = [VideoMode.DEFAULT, VideoMode.HIGH_FPS] - camera_obj.feature_flags.has_privacy_mask = True - camera_obj.feature_flags.has_speaker = True - camera_obj.feature_flags.has_smart_detect = True - camera_obj.feature_flags.smart_detect_types = [ - SmartDetectObjectType.PERSON, - SmartDetectObjectType.VEHICLE, - ] - camera_obj.is_ssh_enabled = False - camera_obj.led_settings.is_enabled = False - camera_obj.hdr_mode = False - camera_obj.video_mode = VideoMode.DEFAULT - camera_obj.remove_privacy_zone() - camera_obj.speaker_settings.are_system_sounds_enabled = False - camera_obj.osd_settings.is_name_enabled = False - camera_obj.osd_settings.is_date_enabled = False - camera_obj.osd_settings.is_logo_enabled = False - camera_obj.osd_settings.is_debug_enabled = False - camera_obj.smart_detect_settings.object_types = [] - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SWITCH, 13, 12) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - -@pytest.fixture(name="camera_none") -async def camera_none_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the switch platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.recording_settings.mode = RecordingMode.DETECTIONS - camera_obj.feature_flags.has_led_status = False - camera_obj.feature_flags.has_hdr = False - camera_obj.feature_flags.video_modes = [VideoMode.DEFAULT] - camera_obj.feature_flags.has_privacy_mask = False - camera_obj.feature_flags.has_speaker = False - camera_obj.feature_flags.has_smart_detect = False - camera_obj.is_ssh_enabled = False - camera_obj.osd_settings.is_name_enabled = False - camera_obj.osd_settings.is_date_enabled = False - camera_obj.osd_settings.is_logo_enabled = False - camera_obj.osd_settings.is_debug_enabled = False - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SWITCH, 6, 5) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - -@pytest.fixture(name="camera_privacy") -async def camera_privacy_fixture( - hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera -): - """Fixture for a single camera for testing the switch platform.""" - - # disable pydantic validation so mocking can happen - Camera.__config__.validate_assignment = False - - # 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 - camera_obj.channels[2]._api = mock_entry.api - camera_obj.name = "Test Camera" - camera_obj.recording_settings.mode = RecordingMode.NEVER - camera_obj.feature_flags.has_led_status = False - camera_obj.feature_flags.has_hdr = False - camera_obj.feature_flags.video_modes = [VideoMode.DEFAULT] - camera_obj.feature_flags.has_privacy_mask = True - camera_obj.feature_flags.has_speaker = False - camera_obj.feature_flags.has_smart_detect = False - camera_obj.add_privacy_zone() - camera_obj.is_ssh_enabled = False - camera_obj.osd_settings.is_name_enabled = False - camera_obj.osd_settings.is_date_enabled = False - camera_obj.osd_settings.is_logo_enabled = False - camera_obj.osd_settings.is_debug_enabled = False - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() - - assert_entity_counts(hass, Platform.SWITCH, 7, 6) - - yield camera_obj - - Camera.__config__.validate_assignment = True - - async def test_switch_setup_no_perm( hass: HomeAssistant, - mock_entry: MockEntityFixture, - mock_light: Light, - mock_camera: Camera, + ufp: MockUFPFixture, + light: Light, + doorbell: Camera, ): """Test switch entity setup for light devices.""" - light_obj = mock_light.copy() - light_obj._api = mock_entry.api - - 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 - camera_obj.channels[2]._api = mock_entry.api - - reset_objects(mock_entry.api.bootstrap) - mock_entry.api.bootstrap.lights = { - light_obj.id: light_obj, - } - mock_entry.api.bootstrap.cameras = { - camera_obj.id: camera_obj, - } - mock_entry.api.bootstrap.auth_user.all_permissions = [ + ufp.api.bootstrap.auth_user.all_permissions = [ Permission.unifi_dict_to_dict({"rawPermission": "light:read:*"}) ] - await hass.config_entries.async_setup(mock_entry.entry.entry_id) - await hass.async_block_till_done() + await init_entry(hass, ufp, [light, doorbell]) assert_entity_counts(hass, Platform.SWITCH, 0, 0) async def test_switch_setup_light( hass: HomeAssistant, - mock_entry: MockEntityFixture, + ufp: MockUFPFixture, light: Light, ): """Test switch entity setup for light devices.""" + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.SWITCH, 2, 1) + entity_registry = er.async_get(hass) description = LIGHT_SWITCHES[1] @@ -283,7 +91,7 @@ async def test_switch_setup_light( assert entity.disabled is True assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -293,14 +101,67 @@ async def test_switch_setup_light( async def test_switch_setup_camera_all( hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera: Camera, + ufp: MockUFPFixture, + doorbell: Camera, ): """Test switch entity setup for camera devices (all enabled feature flags).""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SWITCH, 13, 12) + entity_registry = er.async_get(hass) for description in CAMERA_SWITCHES_BASIC: + unique_id, entity_id = ids_from_device_description( + Platform.SWITCH, doorbell, description + ) + + entity = entity_registry.async_get(entity_id) + assert entity + assert entity.unique_id == unique_id + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION + + description = CAMERA_SWITCHES[0] + + description_entity_name = ( + description.name.lower().replace(":", "").replace(" ", "_") + ) + unique_id = f"{doorbell.mac}_{description.key}" + entity_id = f"switch.test_camera_{description_entity_name}" + + entity = entity_registry.async_get(entity_id) + assert entity + assert entity.disabled is True + assert entity.unique_id == unique_id + + await enable_entity(hass, ufp.entry.entry_id, entity_id) + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION + + +async def test_switch_setup_camera_none( + hass: HomeAssistant, + ufp: MockUFPFixture, + camera: Camera, +): + """Test switch entity setup for camera devices (no enabled feature flags).""" + + await init_entry(hass, ufp, [camera]) + assert_entity_counts(hass, Platform.SWITCH, 6, 5) + + entity_registry = er.async_get(hass) + + for description in CAMERA_SWITCHES_BASIC: + if description.ufp_required_field is not None: + continue + unique_id, entity_id = ids_from_device_description( Platform.SWITCH, camera, description ) @@ -327,7 +188,7 @@ async def test_switch_setup_camera_all( assert entity.disabled is True assert entity.unique_id == unique_id - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + await enable_entity(hass, ufp.entry.entry_id, entity_id) state = hass.states.get(entity_id) assert state @@ -335,56 +196,14 @@ async def test_switch_setup_camera_all( assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION -async def test_switch_setup_camera_none( - hass: HomeAssistant, - mock_entry: MockEntityFixture, - camera_none: Camera, +async def test_switch_light_status( + hass: HomeAssistant, ufp: MockUFPFixture, light: Light ): - """Test switch entity setup for camera devices (no enabled feature flags).""" - - entity_registry = er.async_get(hass) - - for description in CAMERA_SWITCHES_BASIC: - if description.ufp_required_field is not None: - continue - - unique_id, entity_id = ids_from_device_description( - Platform.SWITCH, camera_none, description - ) - - entity = entity_registry.async_get(entity_id) - assert entity - assert entity.unique_id == unique_id - - state = hass.states.get(entity_id) - assert state - assert state.state == STATE_OFF - assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - - description = CAMERA_SWITCHES[0] - - description_entity_name = ( - description.name.lower().replace(":", "").replace(" ", "_") - ) - unique_id = f"{camera_none.mac}_{description.key}" - entity_id = f"switch.test_camera_{description_entity_name}" - - entity = entity_registry.async_get(entity_id) - assert entity - assert entity.disabled is True - assert entity.unique_id == unique_id - - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) - - state = hass.states.get(entity_id) - assert state - assert state.state == STATE_OFF - assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - - -async def test_switch_light_status(hass: HomeAssistant, light: Light): """Tests status light switch for lights.""" + await init_entry(hass, ufp, [light]) + assert_entity_counts(hass, Platform.SWITCH, 2, 1) + description = LIGHT_SWITCHES[1] light.__fields__["set_status_light"] = Mock() @@ -406,44 +225,53 @@ async def test_switch_light_status(hass: HomeAssistant, light: Light): async def test_switch_camera_ssh( - hass: HomeAssistant, camera: Camera, mock_entry: MockEntityFixture + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Tests SSH switch for cameras.""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SWITCH, 13, 12) + description = CAMERA_SWITCHES[0] - camera.__fields__["set_ssh"] = Mock() - camera.set_ssh = AsyncMock() + doorbell.__fields__["set_ssh"] = Mock() + doorbell.set_ssh = AsyncMock() - _, entity_id = ids_from_device_description(Platform.SWITCH, camera, description) - await enable_entity(hass, mock_entry.entry.entry_id, entity_id) + _, entity_id = ids_from_device_description(Platform.SWITCH, doorbell, description) + await enable_entity(hass, ufp.entry.entry_id, entity_id) await hass.services.async_call( "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera.set_ssh.assert_called_once_with(True) + doorbell.set_ssh.assert_called_once_with(True) await hass.services.async_call( "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera.set_ssh.assert_called_with(False) + doorbell.set_ssh.assert_called_with(False) @pytest.mark.parametrize("description", CAMERA_SWITCHES_NO_EXTRA) async def test_switch_camera_simple( - hass: HomeAssistant, camera: Camera, description: ProtectSwitchEntityDescription + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, + description: ProtectSwitchEntityDescription, ): """Tests all simple switches for cameras.""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SWITCH, 13, 12) + assert description.ufp_set_method is not None - camera.__fields__[description.ufp_set_method] = Mock() - setattr(camera, description.ufp_set_method, AsyncMock()) - set_method = getattr(camera, description.ufp_set_method) + doorbell.__fields__[description.ufp_set_method] = Mock() + setattr(doorbell, description.ufp_set_method, AsyncMock()) + set_method = getattr(doorbell, description.ufp_set_method) - _, entity_id = ids_from_device_description(Platform.SWITCH, camera, description) + _, entity_id = ids_from_device_description(Platform.SWITCH, doorbell, description) await hass.services.async_call( "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True @@ -458,70 +286,82 @@ async def test_switch_camera_simple( set_method.assert_called_with(False) -async def test_switch_camera_highfps(hass: HomeAssistant, camera: Camera): +async def test_switch_camera_highfps( + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera +): """Tests High FPS switch for cameras.""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SWITCH, 13, 12) + description = CAMERA_SWITCHES[3] - camera.__fields__["set_video_mode"] = Mock() - camera.set_video_mode = AsyncMock() + doorbell.__fields__["set_video_mode"] = Mock() + doorbell.set_video_mode = AsyncMock() - _, entity_id = ids_from_device_description(Platform.SWITCH, camera, description) + _, entity_id = ids_from_device_description(Platform.SWITCH, doorbell, description) await hass.services.async_call( "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera.set_video_mode.assert_called_once_with(VideoMode.HIGH_FPS) + doorbell.set_video_mode.assert_called_once_with(VideoMode.HIGH_FPS) await hass.services.async_call( "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera.set_video_mode.assert_called_with(VideoMode.DEFAULT) + doorbell.set_video_mode.assert_called_with(VideoMode.DEFAULT) -async def test_switch_camera_privacy(hass: HomeAssistant, camera: Camera): +async def test_switch_camera_privacy( + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera +): """Tests Privacy Mode switch for cameras.""" + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SWITCH, 13, 12) + description = CAMERA_SWITCHES[4] - camera.__fields__["set_privacy"] = Mock() - camera.set_privacy = AsyncMock() + doorbell.__fields__["set_privacy"] = Mock() + doorbell.set_privacy = AsyncMock() - _, entity_id = ids_from_device_description(Platform.SWITCH, camera, description) + _, entity_id = ids_from_device_description(Platform.SWITCH, doorbell, description) await hass.services.async_call( "switch", "turn_on", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera.set_privacy.assert_called_once_with(True, 0, RecordingMode.NEVER) + doorbell.set_privacy.assert_called_once_with(True, 0, RecordingMode.NEVER) await hass.services.async_call( "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera.set_privacy.assert_called_with( - False, camera.mic_volume, camera.recording_settings.mode + doorbell.set_privacy.assert_called_with( + False, doorbell.mic_volume, doorbell.recording_settings.mode ) async def test_switch_camera_privacy_already_on( - hass: HomeAssistant, camera_privacy: Camera + hass: HomeAssistant, ufp: MockUFPFixture, doorbell: Camera ): """Tests Privacy Mode switch for cameras with privacy mode defaulted on.""" + doorbell.add_privacy_zone() + await init_entry(hass, ufp, [doorbell]) + assert_entity_counts(hass, Platform.SWITCH, 13, 12) + description = CAMERA_SWITCHES[4] - camera_privacy.__fields__["set_privacy"] = Mock() - camera_privacy.set_privacy = AsyncMock() + doorbell.__fields__["set_privacy"] = Mock() + doorbell.set_privacy = AsyncMock() - _, entity_id = ids_from_device_description( - Platform.SWITCH, camera_privacy, description - ) + _, entity_id = ids_from_device_description(Platform.SWITCH, doorbell, description) await hass.services.async_call( "switch", "turn_off", {ATTR_ENTITY_ID: entity_id}, blocking=True ) - camera_privacy.set_privacy.assert_called_once_with(False, 100, RecordingMode.ALWAYS) + doorbell.set_privacy.assert_called_once_with(False, 100, RecordingMode.ALWAYS) diff --git a/tests/components/unifiprotect/utils.py b/tests/components/unifiprotect/utils.py new file mode 100644 index 00000000000..517da9e73c6 --- /dev/null +++ b/tests/components/unifiprotect/utils.py @@ -0,0 +1,168 @@ +"""Test helpers for UniFi Protect.""" +# pylint: disable=protected-access +from __future__ import annotations + +from dataclasses import dataclass +from datetime import timedelta +from typing import Any, Callable, Sequence + +from pyunifiprotect import ProtectApiClient +from pyunifiprotect.data import ( + Bootstrap, + Camera, + ProtectAdoptableDeviceModel, + WSSubscriptionMessage, +) +from pyunifiprotect.data.bootstrap import ProtectDeviceRef +from pyunifiprotect.test_util.anonymize import random_hex + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, split_entity_id +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.entity import EntityDescription +import homeassistant.util.dt as dt_util + +from tests.common import MockConfigEntry, async_fire_time_changed + + +@dataclass +class MockUFPFixture: + """Mock for NVR.""" + + entry: MockConfigEntry + api: ProtectApiClient + ws_subscription: Callable[[WSSubscriptionMessage], None] | None = None + + def ws_msg(self, msg: WSSubscriptionMessage) -> Any: + """Emit WS message for testing.""" + + if self.ws_subscription is not None: + return self.ws_subscription(msg) + + +def reset_objects(bootstrap: Bootstrap): + """Reset bootstrap objects.""" + + bootstrap.cameras = {} + bootstrap.lights = {} + bootstrap.sensors = {} + bootstrap.viewers = {} + bootstrap.events = {} + bootstrap.doorlocks = {} + bootstrap.chimes = {} + + +async def time_changed(hass: HomeAssistant, seconds: int) -> None: + """Trigger time changed.""" + next_update = dt_util.utcnow() + timedelta(seconds) + async_fire_time_changed(hass, next_update) + await hass.async_block_till_done() + + +async def enable_entity( + hass: HomeAssistant, entry_id: str, entity_id: str +) -> er.RegistryEntry: + """Enable a disabled entity.""" + entity_registry = er.async_get(hass) + + updated_entity = entity_registry.async_update_entity(entity_id, disabled_by=None) + assert not updated_entity.disabled + await hass.config_entries.async_reload(entry_id) + await hass.async_block_till_done() + + return updated_entity + + +def assert_entity_counts( + hass: HomeAssistant, platform: Platform, total: int, enabled: int +) -> None: + """Assert entity counts for a given platform.""" + + entity_registry = er.async_get(hass) + + entities = [ + e for e in entity_registry.entities if split_entity_id(e)[0] == platform.value + ] + + assert len(entities) == total + assert len(hass.states.async_all(platform.value)) == enabled + + +def normalize_name(name: str) -> str: + """Normalize name.""" + + return name.lower().replace(":", "").replace(" ", "_").replace("-", "_") + + +def ids_from_device_description( + platform: Platform, + device: ProtectAdoptableDeviceModel, + description: EntityDescription, +) -> tuple[str, str]: + """Return expected unique_id and entity_id for a give platform/device/description combination.""" + + entity_name = normalize_name(device.display_name) + description_entity_name = normalize_name(str(description.name)) + + unique_id = f"{device.mac}_{description.key}" + entity_id = f"{platform.value}.{entity_name}_{description_entity_name}" + + return unique_id, entity_id + + +def generate_random_ids() -> tuple[str, str]: + """Generate random IDs for device.""" + + return random_hex(24).lower(), random_hex(12).upper() + + +def regenerate_device_ids(device: ProtectAdoptableDeviceModel) -> None: + """Regenerate the IDs on UFP device.""" + + device.id, device.mac = generate_random_ids() + + +def add_device_ref(bootstrap: Bootstrap, device: ProtectAdoptableDeviceModel) -> None: + """Manually add device ref to bootstrap for lookup.""" + + ref = ProtectDeviceRef(id=device.id, model=device.model) + bootstrap.id_lookup[device.id] = ref + bootstrap.mac_lookup[device.mac.lower()] = ref + + +def add_device( + bootstrap: Bootstrap, device: ProtectAdoptableDeviceModel, regenerate_ids: bool +) -> None: + """Add test device to bootstrap.""" + + if device.model is None: + return + + device._api = bootstrap.api + if isinstance(device, Camera): + for channel in device.channels: + channel._api = bootstrap.api + + if regenerate_ids: + regenerate_device_ids(device) + device._initial_data = device.dict() + + devices = getattr(bootstrap, f"{device.model.value}s") + devices[device.id] = device + add_device_ref(bootstrap, device) + + +async def init_entry( + hass: HomeAssistant, + ufp: MockUFPFixture, + devices: Sequence[ProtectAdoptableDeviceModel], + regenerate_ids: bool = True, +) -> None: + """Initialize Protect entry with given devices.""" + + reset_objects(ufp.api.bootstrap) + for device in devices: + add_device(ufp.api.bootstrap, device, regenerate_ids) + + await hass.config_entries.async_setup(ufp.entry.entry_id) + await hass.async_block_till_done()