Refactor UniFi Protect tests (#73971)

Co-authored-by: J. Nick Koston <nick@koston.org>
pull/73984/head
Christopher Bailey 2022-06-25 11:15:38 -04:00 committed by GitHub
parent 85fdc56240
commit e67f8720e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1535 additions and 2294 deletions

View File

@ -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()

View File

@ -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,

View File

@ -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,

View File

@ -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
},

View File

@ -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,

View File

@ -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",

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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),
)

View File

@ -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

View File

@ -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])}
)

View File

@ -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)

View File

@ -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()