1
mirror of https://github.com/home-assistant/core synced 2024-10-07 10:13:38 +02:00

UniFi Protect test refactoring (#63486)

* UniFi Protect test refactoring

* More pylint fixes

* Use load_fixture helper

* yield to return where able
This commit is contained in:
Christopher Bailey 2022-01-05 13:29:59 -05:00 committed by GitHub
parent 8864492e35
commit d0d5222bf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 419 additions and 109 deletions

View File

@ -1,81 +1,37 @@
"""Fixtures and test data for UniFi Protect methods."""
# pylint: disable=protected-access
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from ipaddress import IPv4Address
import json
from pathlib import Path
from typing import Any, Callable
from unittest.mock import AsyncMock, Mock, patch
import pytest
from pyunifiprotect.data import Camera, Light, Version, WSSubscriptionMessage
from pyunifiprotect.data import Camera, Light, WSSubscriptionMessage
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
from pyunifiprotect.data.devices import Viewer
from pyunifiprotect.data.nvr import DoorbellMessage, Liveview
from pyunifiprotect.data.types import DoorbellMessageType, ModelType
from pyunifiprotect.data.devices import Sensor, Viewer
from pyunifiprotect.data.nvr import NVR, Liveview
from homeassistant.components.unifiprotect.const import DOMAIN, MIN_REQUIRED_PROTECT_V
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 tests.common import MockConfigEntry, async_fire_time_changed
from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture
MAC_ADDR = "aa:bb:cc:dd:ee:ff"
@dataclass
class MockPortData:
"""Mock Port information."""
rtsp: int = 7441
rtsps: int = 7447
@dataclass
class MockDoorbellSettings:
"""Mock Port information."""
default_message_text = "Welcome"
all_messages = [
DoorbellMessage(
type=DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR,
text=DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR.value.replace("_", " "),
),
DoorbellMessage(
type=DoorbellMessageType.DO_NOT_DISTURB,
text=DoorbellMessageType.DO_NOT_DISTURB.value.replace("_", " "),
),
DoorbellMessage(
type=DoorbellMessageType.CUSTOM_MESSAGE,
text="Test",
),
]
@dataclass
class MockNvrData:
"""Mock for NVR."""
version: Version
mac: str
name: str
id: str
ports: MockPortData = MockPortData()
doorbell_settings = MockDoorbellSettings()
update_all_messages = Mock()
model: ModelType = ModelType.NVR
@dataclass
class MockBootstrap:
"""Mock for Bootstrap."""
nvr: MockNvrData
nvr: NVR
cameras: dict[str, Any]
lights: dict[str, Any]
sensors: dict[str, Any]
@ -99,28 +55,51 @@ class MockEntityFixture:
api: Mock
MOCK_NVR_DATA = MockNvrData(
version=MIN_REQUIRED_PROTECT_V, mac=MAC_ADDR, name="UnifiProtect", id="test_id"
)
MOCK_OLD_NVR_DATA = MockNvrData(
version=Version("1.19.0"), mac=MAC_ADDR, name="UnifiProtect", id="test_id"
)
@pytest.fixture(name="mock_nvr")
def mock_nvr_fixture():
"""Mock UniFi Protect Camera device."""
MOCK_BOOTSTRAP = MockBootstrap(
nvr=MOCK_NVR_DATA, cameras={}, lights={}, sensors={}, viewers={}, liveviews={}
)
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_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={}
)
@pytest.fixture
def mock_client():
def mock_client(mock_bootstrap: MockBootstrap):
"""Mock ProtectApiClient for testing."""
client = Mock()
client.bootstrap = MOCK_BOOTSTRAP
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=MOCK_NVR_DATA)
client.update = AsyncMock(return_value=MOCK_BOOTSTRAP)
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:
@ -162,44 +141,40 @@ def mock_entry(
def mock_liveview():
"""Mock UniFi Protect Camera device."""
path = Path(__file__).parent / "sample_data" / "sample_liveview.json"
with open(path, encoding="utf-8") as json_file:
data = json.load(json_file)
yield Liveview.from_unifi_dict(**data)
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."""
path = Path(__file__).parent / "sample_data" / "sample_camera.json"
with open(path, encoding="utf-8") as json_file:
data = json.load(json_file)
yield Camera.from_unifi_dict(**data)
data = json.loads(load_fixture("sample_camera.json", integration=DOMAIN))
return Camera.from_unifi_dict(**data)
@pytest.fixture
def mock_light():
"""Mock UniFi Protect Camera device."""
path = Path(__file__).parent / "sample_data" / "sample_light.json"
with open(path, encoding="utf-8") as json_file:
data = json.load(json_file)
yield Light.from_unifi_dict(**data)
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."""
path = Path(__file__).parent / "sample_data" / "sample_viewport.json"
with open(path, encoding="utf-8") as json_file:
data = json.load(json_file)
data = json.loads(load_fixture("sample_viewport.json", integration=DOMAIN))
return Viewer.from_unifi_dict(**data)
yield 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)
async def time_changed(hass: HomeAssistant, seconds: int) -> None:

View File

@ -0,0 +1,236 @@
{
"mac": "A1E00C826924",
"host": "192.168.216.198",
"name": "UnifiProtect",
"canAutoUpdate": true,
"isStatsGatheringEnabled": true,
"timezone": "America/New_York",
"version": "1.21.0-beta.2",
"ucoreVersion": "2.3.26",
"firmwareVersion": "2.3.10",
"uiVersion": null,
"hardwarePlatform": "al324",
"ports": {
"ump": 7449,
"http": 7080,
"https": 7443,
"rtsp": 7447,
"rtsps": 7441,
"rtmp": 1935,
"devicesWss": 7442,
"cameraHttps": 7444,
"cameraTcp": 7877,
"liveWs": 7445,
"liveWss": 7446,
"tcpStreams": 7448,
"playback": 7450,
"emsCLI": 7440,
"emsLiveFLV": 7550,
"cameraEvents": 7551,
"tcpBridge": 7888,
"ucore": 11081,
"discoveryClient": 0
},
"uptime": 1191516000,
"lastSeen": 1641269019283,
"isUpdating": false,
"lastUpdateAt": null,
"isStation": false,
"enableAutomaticBackups": true,
"enableStatsReporting": false,
"isSshEnabled": false,
"errorCode": null,
"releaseChannel": "beta",
"ssoChannel": null,
"hosts": [
"192.168.216.198"
],
"enableBridgeAutoAdoption": true,
"hardwareId": "baf4878d-df21-4427-9fbe-c2ef15301412",
"hardwareRevision": "113-03137-22",
"hostType": 59936,
"hostShortname": "UNVRPRO",
"isHardware": true,
"isWirelessUplinkEnabled": false,
"timeFormat": "24h",
"temperatureUnit": "C",
"recordingRetentionDurationMs": null,
"enableCrashReporting": true,
"disableAudio": false,
"analyticsData": "anonymous",
"anonymousDeviceId": "65257f7d-874c-498a-8f1b-00b2dd0a7ae1",
"cameraUtilization": 30,
"isRecycling": false,
"avgMotions": [
21.29,
14,
5.43,
2.29,
6.43,
7.43,
16.86,
17,
24.71,
36.86,
46.43,
47.57,
51.57,
52.71,
63.86,
80.86,
86.71,
91.71,
96.57,
71.14,
57,
53.71,
39.57,
21.29
],
"disableAutoLink": false,
"skipFirmwareUpdate": false,
"wifiSettings": {
"useThirdPartyWifi": false,
"ssid": null,
"password": null
},
"locationSettings": {
"isAway": true,
"isGeofencingEnabled": false,
"latitude": 41.4519,
"longitude": -81.921,
"radius": 200
},
"featureFlags": {
"beta": false,
"dev": false,
"notificationsV2": true
},
"systemInfo": {
"cpu": {
"averageLoad": 5,
"temperature": 70
},
"memory": {
"available": 6481504,
"free": 87080,
"total": 8163024
},
"storage": {
"available": 21796939214848,
"isRecycling": false,
"size": 31855989432320,
"type": "raid",
"used": 8459815895040,
"devices": [
{
"model": "ST16000VE000-2L2103",
"size": 16000900661248,
"healthy": true
},
{
"model": "ST16000VE000-2L2103",
"size": 16000900661248,
"healthy": true
},
{
"model": "ST16000VE000-2L2103",
"size": 16000900661248,
"healthy": true
}
]
},
"tmpfs": {
"available": 934204,
"total": 1048576,
"used": 114372,
"path": "/var/opt/unifi-protect/tmp"
}
},
"doorbellSettings": {
"defaultMessageText": "Welcome",
"defaultMessageResetTimeoutMs": 60000,
"customMessages": [
"Come In!",
"Use Other Door"
],
"allMessages": [
{
"type": "LEAVE_PACKAGE_AT_DOOR",
"text": "LEAVE PACKAGE AT DOOR"
},
{
"type": "DO_NOT_DISTURB",
"text": "DO NOT DISTURB"
},
{
"type": "CUSTOM_MESSAGE",
"text": "Test"
}
]
},
"smartDetectAgreement": {
"status": "agreed",
"lastUpdateAt": 1606964227734
},
"storageStats": {
"utilization": 26.61384533704469,
"capacity": 5706909122,
"remainingCapacity": 4188081155,
"recordingSpace": {
"total": 31787269955584,
"used": 8459814862848,
"available": 23327455092736
},
"storageDistribution": {
"recordingTypeDistributions": [
{
"recordingType": "rotating",
"size": 7736989099040,
"percentage": 91.47686438351941
},
{
"recordingType": "timelapse",
"size": 21474836480,
"percentage": 0.2539037704709915
},
{
"recordingType": "detections",
"size": 699400412128,
"percentage": 8.269231846009593
}
],
"resolutionDistributions": [
{
"resolution": "HD",
"size": 2896955441152,
"percentage": 9.113571077981481
},
{
"resolution": "4K",
"size": 5560908906496,
"percentage": 17.494138107066746
},
{
"resolution": "free",
"size": 23329405607936,
"percentage": 73.39229081495176
}
]
}
},
"id": "test_id",
"isAway": true,
"isSetup": true,
"network": "Ethernet",
"type": "UNVR-PRO",
"upSince": 1640077503063,
"isRecordingDisabled": false,
"isRecordingMotionOnly": false,
"maxCameraCapacity": {
"4K": 20,
"2K": 30,
"HD": 60
},
"modelKey": "nvr"
}

View File

@ -0,0 +1,92 @@
{
"mac": "26DBAFF133A4",
"connectionHost": "192.168.216.198",
"type": "UFP-SENSE",
"name": "Egdczv Urg",
"upSince": 1641256963255,
"uptime": null,
"lastSeen": 1641259127934,
"connectedSince": 1641259139255,
"state": "CONNECTED",
"hardwareRevision": 6,
"firmwareVersion": "1.0.2",
"latestFirmwareVersion": "1.0.2",
"firmwareBuild": null,
"isUpdating": false,
"isAdopting": false,
"isAdopted": true,
"isAdoptedByOther": false,
"isProvisioned": false,
"isRebooting": false,
"isSshEnabled": false,
"canAdopt": false,
"isAttemptingToConnect": false,
"isMotionDetected": false,
"mountType": "door",
"leakDetectedAt": null,
"tamperingDetectedAt": null,
"isOpened": true,
"openStatusChangedAt": 1641269036582,
"alarmTriggeredAt": null,
"motionDetectedAt": 1641269044824,
"wiredConnectionState": {
"phyRate": null
},
"stats": {
"light": {
"value": 0,
"status": "neutral"
},
"humidity": {
"value": 35,
"status": "neutral"
},
"temperature": {
"value": 17.23,
"status": "neutral"
}
},
"bluetoothConnectionState": {
"signalQuality": 15,
"signalStrength": -84
},
"batteryStatus": {
"percentage": 100,
"isLow": false
},
"alarmSettings": {
"isEnabled": false
},
"lightSettings": {
"isEnabled": true,
"lowThreshold": null,
"highThreshold": null,
"margin": 10
},
"motionSettings": {
"isEnabled": true,
"sensitivity": 100
},
"temperatureSettings": {
"isEnabled": true,
"lowThreshold": null,
"highThreshold": null,
"margin": 0.1
},
"humiditySettings": {
"isEnabled": true,
"lowThreshold": null,
"highThreshold": null,
"margin": 1
},
"ledSettings": {
"isEnabled": true
},
"bridge": "61b3f5c90050a703e700042a",
"camera": "2f9beb2e6f79af3c32c22d49",
"bridgeCandidates": [],
"id": "f6ecbe829f81cc79ad6e0c9a",
"isConnected": true,
"marketName": "UP Sense",
"modelKey": "sensor"
}

View File

@ -37,7 +37,7 @@ async def camera_fixture(
assert_entity_counts(hass, Platform.BUTTON, 1, 0)
yield (camera_obj, "button.test_camera_reboot_device")
return (camera_obj, "button.test_camera_reboot_device")
async def test_button(

View File

@ -70,7 +70,7 @@ async def camera_fixture(
assert_entity_counts(hass, Platform.CAMERA, 2, 1)
yield (camera_obj, "camera.test_camera_high")
return (camera_obj, "camera.test_camera_high")
def validate_default_camera_entity(

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from unittest.mock import patch
from pyunifiprotect import NotAuthorized, NvrError
from pyunifiprotect.data.nvr import NVR
from homeassistant import config_entries
from homeassistant.components.unifiprotect.const import (
@ -20,12 +21,12 @@ from homeassistant.data_entry_flow import (
)
from homeassistant.helpers import device_registry as dr
from .conftest import MAC_ADDR, MOCK_NVR_DATA, MOCK_OLD_NVR_DATA
from .conftest import MAC_ADDR
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant) -> None:
async def test_form(hass: HomeAssistant, mock_nvr: NVR) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -35,7 +36,7 @@ async def test_form(hass: HomeAssistant) -> None:
with patch(
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
return_value=MOCK_NVR_DATA,
return_value=mock_nvr,
), patch(
"homeassistant.components.unifiprotect.async_setup_entry",
return_value=True,
@ -63,7 +64,7 @@ async def test_form(hass: HomeAssistant) -> None:
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_version_too_old(hass: HomeAssistant) -> None:
async def test_form_version_too_old(hass: HomeAssistant, mock_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}
@ -71,7 +72,7 @@ async def test_form_version_too_old(hass: HomeAssistant) -> None:
with patch(
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
return_value=MOCK_OLD_NVR_DATA,
return_value=mock_old_nvr,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@ -132,7 +133,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_reauth_auth(hass: HomeAssistant) -> None:
async def test_form_reauth_auth(hass: HomeAssistant, mock_nvr: NVR) -> None:
"""Test we handle reauth auth."""
mock_config = MockConfigEntry(
domain=DOMAIN,
@ -176,7 +177,7 @@ async def test_form_reauth_auth(hass: HomeAssistant) -> None:
with patch(
"homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_nvr",
return_value=MOCK_NVR_DATA,
return_value=mock_nvr,
):
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],

View File

@ -4,12 +4,13 @@ from __future__ import annotations
from unittest.mock import AsyncMock
from pyunifiprotect import NotAuthorized, NvrError
from pyunifiprotect.data import NVR
from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from .conftest import MOCK_OLD_NVR_DATA, MockEntityFixture
from .conftest import MockEntityFixture
async def test_setup(hass: HomeAssistant, mock_entry: MockEntityFixture):
@ -51,10 +52,12 @@ async def test_unload(hass: HomeAssistant, mock_entry: MockEntityFixture):
assert mock_entry.api.async_disconnect_ws.called
async def test_setup_too_old(hass: HomeAssistant, mock_entry: MockEntityFixture):
async def test_setup_too_old(
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_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_DATA
mock_entry.api.get_nvr.return_value = mock_old_nvr
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done()

View File

@ -200,10 +200,10 @@ async def test_number_setup_camera_missing_attr(
@pytest.mark.parametrize("description", LIGHT_NUMBERS)
async def test_switch_light_simple(
async def test_number_light_simple(
hass: HomeAssistant, light: Light, description: ProtectNumberEntityDescription
):
"""Tests all simple switches for lights."""
"""Tests all simple numbers for lights."""
assert description.ufp_set_function is not None
@ -224,10 +224,10 @@ async def test_switch_light_simple(
@pytest.mark.parametrize("description", CAMERA_NUMBERS)
async def test_switch_camera_simple(
async def test_number_camera_simple(
hass: HomeAssistant, camera: Camera, description: ProtectNumberEntityDescription
):
"""Tests all simple switches for cameras."""
"""Tests all simple numbers for cameras."""
assert description.ufp_set_function is not None

View File

@ -1,4 +1,4 @@
"""Test the UniFi Protect number platform."""
"""Test the UniFi Protect select platform."""
# pylint: disable=protected-access
from __future__ import annotations
@ -51,7 +51,7 @@ async def viewer_fixture(
mock_viewer: Viewer,
mock_liveview: Liveview,
):
"""Fixture for a single viewport for testing the number platform."""
"""Fixture for a single viewport for testing the select platform."""
# disable pydantic validation so mocking can happen
Viewer.__config__.validate_assignment = False
@ -81,7 +81,7 @@ async def viewer_fixture(
async def camera_fixture(
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera
):
"""Fixture for a single camera for testing the switch platform."""
"""Fixture for a single camera for testing the select platform."""
# disable pydantic validation so mocking can happen
Camera.__config__.validate_assignment = False
@ -119,7 +119,7 @@ async def light_fixture(
mock_light: Light,
camera: Camera,
):
"""Fixture for a single light for testing the number platform."""
"""Fixture for a single light for testing the select platform."""
# disable pydantic validation so mocking can happen
Light.__config__.validate_assignment = False
@ -151,7 +151,7 @@ async def light_fixture(
async def camera_none_fixture(
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera
):
"""Fixture for a single camera for testing the switch platform."""
"""Fixture for a single camera for testing the select platform."""
# disable pydantic validation so mocking can happen
Camera.__config__.validate_assignment = False
@ -228,11 +228,11 @@ async def test_select_setup_viewer(
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
async def test_number_setup_camera_all(
async def test_select_setup_camera_all(
hass: HomeAssistant,
camera: Camera,
):
"""Test number entity setup for camera devices (all features)."""
"""Test select entity setup for camera devices (all features)."""
entity_registry = er.async_get(hass)
expected_values = ("Always", "Auto", "Default Message (Welcome)")
@ -252,11 +252,11 @@ async def test_number_setup_camera_all(
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
async def test_number_setup_camera_none(
async def test_select_setup_camera_none(
hass: HomeAssistant,
camera_none: Camera,
):
"""Test number entity setup for camera devices (no features)."""
"""Test select entity setup for camera devices (no features)."""
entity_registry = er.async_get(hass)
expected_values = ("Always", "Auto", "Default Message (Welcome)")
@ -332,6 +332,9 @@ async def test_select_update_doorbell_settings(
expected_length += 1
new_nvr = copy(mock_entry.api.bootstrap.nvr)
new_nvr.__fields__["update_all_messages"] = Mock()
new_nvr.update_all_messages = Mock()
new_nvr.doorbell_settings.all_messages = [
*new_nvr.doorbell_settings.all_messages,
DoorbellMessage(
@ -615,7 +618,7 @@ async def test_select_service_doorbell_invalid(
blocking=True,
)
camera.set_lcd_text.assert_not_called
assert not camera.set_lcd_text.called
async def test_select_service_doorbell_success(