core/tests/components/unifiprotect/conftest.py

283 lines
7.3 KiB
Python

"""Fixtures and test data for UniFi Protect methods."""
# pylint: disable=protected-access
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta
from ipaddress import IPv4Address
import json
from typing import Any
from unittest.mock import AsyncMock, Mock, patch
import pytest
from pyunifiprotect.data import (
NVR,
Camera,
Doorlock,
Light,
Liveview,
Sensor,
Viewer,
WSSubscriptionMessage,
)
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
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
import homeassistant.util.dt as dt_util
from . import _patch_discovery
from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture
MAC_ADDR = "aa:bb:cc:dd:ee:ff"
@dataclass
class MockBootstrap:
"""Mock for Bootstrap."""
nvr: NVR
cameras: dict[str, Any]
lights: dict[str, Any]
sensors: dict[str, Any]
viewers: dict[str, Any]
liveviews: dict[str, Any]
events: dict[str, Any]
doorlocks: dict[str, Any]
def reset_objects(self) -> None:
"""Reset all devices on bootstrap for tests."""
self.cameras = {}
self.lights = {}
self.sensors = {}
self.viewers = {}
self.liveviews = {}
self.events = {}
self.doorlocks = {}
def process_ws_packet(self, msg: WSSubscriptionMessage) -> None:
"""Fake process method for tests."""
pass
@dataclass
class MockEntityFixture:
"""Mock for NVR."""
entry: MockConfigEntry
api: Mock
@pytest.fixture(name="mock_nvr")
def mock_nvr_fixture():
"""Mock UniFi Protect Camera device."""
data = json.loads(load_fixture("sample_nvr.json", integration=DOMAIN))
nvr = NVR.from_unifi_dict(**data)
# disable pydantic validation so mocking can happen
NVR.__config__.validate_assignment = False
yield nvr
NVR.__config__.validate_assignment = True
@pytest.fixture(name="mock_ufp_config_entry")
def mock_ufp_config_entry():
"""Mock the unifiprotect config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
"id": "UnifiProtect",
"port": 443,
"verify_ssl": False,
},
version=2,
)
@pytest.fixture(name="mock_old_nvr")
def mock_old_nvr_fixture():
"""Mock UniFi Protect Camera device."""
data = json.loads(load_fixture("sample_nvr.json", integration=DOMAIN))
data["version"] = "1.19.0"
return NVR.from_unifi_dict(**data)
@pytest.fixture(name="mock_bootstrap")
def mock_bootstrap_fixture(mock_nvr: NVR):
"""Mock Bootstrap fixture."""
return MockBootstrap(
nvr=mock_nvr,
cameras={},
lights={},
sensors={},
viewers={},
liveviews={},
events={},
doorlocks={},
)
@pytest.fixture
def mock_client(mock_bootstrap: MockBootstrap):
"""Mock ProtectApiClient for testing."""
client = Mock()
client.bootstrap = mock_bootstrap
nvr = mock_bootstrap.nvr
nvr._api = client
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.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
def mock_entry(
hass: HomeAssistant,
mock_ufp_config_entry: MockConfigEntry,
mock_client, # pylint: disable=redefined-outer-name
):
"""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)
mock_api.return_value = mock_client
yield MockEntityFixture(mock_ufp_config_entry, mock_client)
@pytest.fixture
def mock_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():
"""Mock UniFi Protect Camera device."""
data = json.loads(load_fixture("sample_camera.json", integration=DOMAIN))
return Camera.from_unifi_dict(**data)
@pytest.fixture
def mock_light():
"""Mock UniFi Protect Light device."""
data = json.loads(load_fixture("sample_light.json", integration=DOMAIN))
return Light.from_unifi_dict(**data)
@pytest.fixture
def mock_viewer():
"""Mock UniFi Protect Viewport device."""
data = json.loads(load_fixture("sample_viewport.json", integration=DOMAIN))
return Viewer.from_unifi_dict(**data)
@pytest.fixture
def mock_sensor():
"""Mock UniFi Protect Sensor device."""
data = json.loads(load_fixture("sample_sensor.json", integration=DOMAIN))
return Sensor.from_unifi_dict(**data)
@pytest.fixture
def mock_doorlock():
"""Mock UniFi Protect Doorlock device."""
data = json.loads(load_fixture("sample_doorlock.json", integration=DOMAIN))
return Doorlock.from_unifi_dict(**data)
@pytest.fixture
def now():
"""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.id}_{description.key}"
entity_id = f"{platform.value}.{entity_name}_{description_entity_name}"
return unique_id, entity_id