299 lines
9.2 KiB
Python
299 lines
9.2 KiB
Python
"""Test the UniFi Protect number platform."""
|
|
# pylint: disable=protected-access
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from unittest.mock import AsyncMock, Mock
|
|
|
|
import pytest
|
|
from pyunifiprotect.data import Camera, Doorlock, Light
|
|
|
|
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
|
from homeassistant.components.unifiprotect.number import (
|
|
CAMERA_NUMBERS,
|
|
DOORLOCK_NUMBERS,
|
|
LIGHT_NUMBERS,
|
|
ProtectNumberEntityDescription,
|
|
)
|
|
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,
|
|
ids_from_device_description,
|
|
)
|
|
|
|
|
|
@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(deep=True)
|
|
light_obj._api = mock_entry.api
|
|
light_obj.name = "Test Light"
|
|
light_obj.light_device_settings.pir_sensitivity = 45
|
|
light_obj.light_device_settings.pir_duration = timedelta(seconds=45)
|
|
|
|
mock_entry.api.bootstrap.reset_objects()
|
|
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(deep=True)
|
|
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
|
|
|
|
mock_entry.api.bootstrap.reset_objects()
|
|
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(deep=True)
|
|
lock_obj._api = mock_entry.api
|
|
lock_obj.name = "Test Lock"
|
|
lock_obj.auto_close_time = timedelta(seconds=45)
|
|
|
|
mock_entry.api.bootstrap.reset_objects()
|
|
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,
|
|
):
|
|
"""Test number entity setup for light devices."""
|
|
|
|
entity_registry = er.async_get(hass)
|
|
|
|
for description in LIGHT_NUMBERS:
|
|
unique_id, entity_id = ids_from_device_description(
|
|
Platform.NUMBER, light, 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 == "45"
|
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
|
|
|
|
|
async def test_number_setup_camera_all(
|
|
hass: HomeAssistant,
|
|
camera: Camera,
|
|
):
|
|
"""Test number entity setup for camera devices (all features)."""
|
|
|
|
entity_registry = er.async_get(hass)
|
|
|
|
for description in CAMERA_NUMBERS:
|
|
unique_id, entity_id = ids_from_device_description(
|
|
Platform.NUMBER, camera, 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 == "0"
|
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
|
|
|
|
|
async def test_number_setup_camera_none(
|
|
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera
|
|
):
|
|
"""Test number entity setup for camera devices (no features)."""
|
|
|
|
camera_obj = mock_camera.copy(deep=True)
|
|
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
|
|
# has_wdr is an the inverse of has HDR
|
|
camera_obj.feature_flags.has_hdr = True
|
|
|
|
mock_entry.api.bootstrap.reset_objects()
|
|
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, 0, 0)
|
|
|
|
|
|
async def test_number_setup_camera_missing_attr(
|
|
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_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(deep=True)
|
|
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
|
|
|
|
mock_entry.api.bootstrap.reset_objects()
|
|
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, 0, 0)
|
|
|
|
|
|
async def test_number_light_sensitivity(hass: HomeAssistant, light: Light):
|
|
"""Test sensitivity number entity for lights."""
|
|
|
|
description = LIGHT_NUMBERS[0]
|
|
assert description.ufp_set_method is not None
|
|
|
|
light.__fields__["set_sensitivity"] = Mock()
|
|
light.set_sensitivity = AsyncMock()
|
|
|
|
_, entity_id = ids_from_device_description(Platform.NUMBER, light, description)
|
|
|
|
await hass.services.async_call(
|
|
"number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True
|
|
)
|
|
|
|
light.set_sensitivity.assert_called_once_with(15.0)
|
|
|
|
|
|
async def test_number_light_duration(hass: HomeAssistant, light: Light):
|
|
"""Test auto-shutoff duration number entity for lights."""
|
|
|
|
description = LIGHT_NUMBERS[1]
|
|
|
|
light.__fields__["set_duration"] = Mock()
|
|
light.set_duration = AsyncMock()
|
|
|
|
_, entity_id = ids_from_device_description(Platform.NUMBER, light, description)
|
|
|
|
await hass.services.async_call(
|
|
"number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True
|
|
)
|
|
|
|
light.set_duration.assert_called_once_with(timedelta(seconds=15.0))
|
|
|
|
|
|
@pytest.mark.parametrize("description", CAMERA_NUMBERS)
|
|
async def test_number_camera_simple(
|
|
hass: HomeAssistant, camera: Camera, description: ProtectNumberEntityDescription
|
|
):
|
|
"""Tests all simple numbers for cameras."""
|
|
|
|
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)
|
|
|
|
_, entity_id = ids_from_device_description(Platform.NUMBER, camera, description)
|
|
|
|
await hass.services.async_call(
|
|
"number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 1.0}, blocking=True
|
|
)
|
|
|
|
set_method.assert_called_once_with(1.0)
|
|
|
|
|
|
async def test_number_lock_auto_close(hass: HomeAssistant, doorlock: Doorlock):
|
|
"""Test auto-lock timeout for locks."""
|
|
|
|
description = DOORLOCK_NUMBERS[0]
|
|
|
|
doorlock.__fields__["set_auto_close_time"] = Mock()
|
|
doorlock.set_auto_close_time = AsyncMock()
|
|
|
|
_, entity_id = ids_from_device_description(Platform.NUMBER, doorlock, description)
|
|
|
|
await hass.services.async_call(
|
|
"number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True
|
|
)
|
|
|
|
doorlock.set_auto_close_time.assert_called_once_with(timedelta(seconds=15.0))
|