Followup PR for UniFi Protect integration (#62806)

* Followup improvements from initial PR

* Update tests/components/unifiprotect/conftest.py

Co-authored-by: J. Nick Koston <nick@koston.org>

* Update translations

* Fixes log message

* Fixes log message

* Unknown to cannot connect

* Update tests/components/unifiprotect/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Fixes camera coverage

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
pull/62847/head
Christopher Bailey 2021-12-27 06:39:24 -05:00 committed by GitHub
parent cf6fb7bf39
commit dc3f21dd1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 227 additions and 148 deletions

View File

@ -29,6 +29,7 @@ from .const import (
DEVICES_FOR_SUBSCRIBE,
DOMAIN,
MIN_REQUIRED_PROTECT_V,
OUTDATED_LOG_MESSAGE,
PLATFORMS,
)
from .data import ProtectData
@ -60,15 +61,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
nvr_info = await protect.get_nvr()
except NotAuthorized as err:
raise ConfigEntryAuthFailed(err) from err
except (asyncio.TimeoutError, NvrError, ServerDisconnectedError) as notreadyerror:
raise ConfigEntryNotReady from notreadyerror
except (asyncio.TimeoutError, NvrError, ServerDisconnectedError) as err:
raise ConfigEntryNotReady from err
if nvr_info.version < MIN_REQUIRED_PROTECT_V:
_LOGGER.error(
(
"You are running v%s of UniFi Protect. Minimum required version is v%s. "
"Please upgrade UniFi Protect and then retry"
),
OUTDATED_LOG_MESSAGE,
nvr_info.version,
MIN_REQUIRED_PROTECT_V,
)

View File

@ -1,9 +1,8 @@
"""Support for Ubiquiti's UniFi Protect NVR."""
from __future__ import annotations
from collections.abc import Callable, Generator, Sequence
from collections.abc import Generator
import logging
from typing import Any
from pyunifiprotect.api import ProtectApiClient
from pyunifiprotect.data import Camera as UFPCamera
@ -12,7 +11,7 @@ from pyunifiprotect.data.devices import CameraChannel
from homeassistant.components.camera import SUPPORT_STREAM, Camera
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
ATTR_BITRATE,
@ -33,7 +32,7 @@ def get_camera_channels(
) -> Generator[tuple[UFPCamera, CameraChannel, bool], None, None]:
"""Get all the camera channels."""
for camera in protect.bootstrap.cameras.values():
if len(camera.channels) == 0:
if not camera.channels:
_LOGGER.warning(
"Camera does not have any channels: %s (id: %s)", camera.name, camera.id
)
@ -53,7 +52,7 @@ def get_camera_channels(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[Sequence[Entity]], None],
async_add_entities: AddEntitiesCallback,
) -> None:
"""Discover cameras on a UniFi Protect NVR."""
data: ProtectData = hass.data[DOMAIN][entry.entry_id]
@ -143,12 +142,7 @@ class ProtectCamera(ProtectDeviceEntity, Camera):
self._attr_is_recording = self.device.is_connected and self.device.is_recording
self._async_set_stream_source()
@callback
def _async_update_extra_attrs_from_protect(self) -> dict[str, Any]:
"""Add additional Attributes to Camera."""
return {
**super()._async_update_extra_attrs_from_protect(),
self._attr_extra_state_attributes = {
ATTR_WIDTH: self.channel.width,
ATTR_HEIGHT: self.channel.height,
ATTR_FPS: self.channel.fps,

View File

@ -30,6 +30,7 @@ from .const import (
DEFAULT_VERIFY_SSL,
DOMAIN,
MIN_REQUIRED_PROTECT_V,
OUTDATED_LOG_MESSAGE,
)
_LOGGER = logging.getLogger(__name__)
@ -55,7 +56,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return OptionsFlowHandler(config_entry)
@callback
async def _async_create_entry(self, title: str, data: dict[str, Any]) -> FlowResult:
def _async_create_entry(self, title: str, data: dict[str, Any]) -> FlowResult:
return self.async_create_entry(
title=title,
data={**data, CONF_ID: title},
@ -66,7 +67,6 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
},
)
@callback
async def _async_get_nvr_data(
self,
user_input: dict[str, Any],
@ -97,10 +97,14 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors[CONF_PASSWORD] = "invalid_auth"
except NvrError as ex:
_LOGGER.debug(ex)
errors["base"] = "nvr_error"
errors["base"] = "cannot_connect"
else:
if nvr_data.version < MIN_REQUIRED_PROTECT_V:
_LOGGER.debug("UniFi Protect Version not supported")
_LOGGER.debug(
OUTDATED_LOG_MESSAGE,
nvr_data.version,
MIN_REQUIRED_PROTECT_V,
)
errors["base"] = "protect_version"
return nvr_data, errors
@ -156,7 +160,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(nvr_data.mac)
self._abort_if_unique_id_configured()
return await self._async_create_entry(nvr_data.name, user_input)
return self._async_create_entry(nvr_data.name, user_input)
user_input = user_input or {}
return self.async_show_form(

View File

@ -1,25 +1,18 @@
"""Constant definitions for UniFi Protect Integration."""
# from typing_extensions import Required
from datetime import timedelta
from pyunifiprotect.data.types import ModelType, Version
from homeassistant.const import Platform
DOMAIN = "unifiprotect"
ATTR_EVENT_SCORE = "event_score"
ATTR_EVENT_OBJECT = "event_object"
ATTR_EVENT_THUMB = "event_thumbnail"
ATTR_WIDTH = "width"
ATTR_HEIGHT = "height"
ATTR_FPS = "fps"
ATTR_BITRATE = "bitrate"
ATTR_CHANNEL_ID = "channel_id"
CONF_DOORBELL_TEXT = "doorbell_text"
CONF_DISABLE_RTSP = "disable_rtsp"
CONF_MESSAGE = "message"
CONF_DURATION = "duration"
CONF_ALL_UPDATES = "all_updates"
CONF_OVERRIDE_CHOST = "override_connection_host"
@ -32,11 +25,9 @@ CONFIG_OPTIONS = [
DEFAULT_PORT = 443
DEFAULT_ATTRIBUTION = "Powered by UniFi Protect Server"
DEFAULT_BRAND = "Ubiquiti"
DEFAULT_SCAN_INTERVAL = 2
DEFAULT_SCAN_INTERVAL = 5
DEFAULT_VERIFY_SSL = False
RING_INTERVAL = timedelta(seconds=3)
DEVICE_TYPE_CAMERA = "camera"
DEVICES_THAT_ADOPT = {
ModelType.CAMERA,
@ -48,7 +39,6 @@ DEVICES_WITH_ENTITIES = DEVICES_THAT_ADOPT | {ModelType.NVR}
DEVICES_FOR_SUBSCRIBE = DEVICES_WITH_ENTITIES | {ModelType.EVENT}
MIN_REQUIRED_PROTECT_V = Version("1.20.0")
OUTDATED_LOG_MESSAGE = "You are running v%s of UniFi Protect. Minimum required version is v%s. Please upgrade UniFi Protect and then retry"
PLATFORMS = [
"camera",
]
PLATFORMS = [Platform.CAMERA]

View File

@ -1,11 +1,8 @@
"""Shared Entity definition for UniFi Protect Integration."""
from __future__ import annotations
from typing import Any
from pyunifiprotect.data import ProtectAdoptableDeviceModel
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import callback
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
@ -45,6 +42,7 @@ class ProtectDeviceEntity(Entity):
name = description.name or ""
self._attr_name = f"{self.device.name} {name.title()}"
self._attr_attribution = DEFAULT_ATTRIBUTION
self._async_set_device_info()
self._async_update_device_from_protect()
@ -55,16 +53,6 @@ class ProtectDeviceEntity(Entity):
"""
await self.data.async_refresh()
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return UniFi Protect device attributes."""
attrs = super().extra_state_attributes or {}
return {
**attrs,
ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION,
**self._extra_state_attributes,
}
@callback
def _async_set_device_info(self) -> None:
self._attr_device_info = DeviceInfo(
@ -77,13 +65,6 @@ class ProtectDeviceEntity(Entity):
configuration_url=self.device.protect_url,
)
@callback
def _async_update_extra_attrs_from_protect( # pylint: disable=no-self-use
self,
) -> dict[str, Any]:
"""Calculate extra state attributes. Primarily for subclass to override."""
return {}
@callback
def _async_update_device_from_protect(self) -> None:
"""Update Entity object from Protect device."""
@ -95,7 +76,6 @@ class ProtectDeviceEntity(Entity):
self._attr_available = (
self.data.last_update_success and self.device.is_connected
)
self._extra_state_attributes = self._async_update_extra_attrs_from_protect()
@callback
def _async_updated_event(self) -> None:

View File

@ -12,7 +12,7 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
"protect_version": "Minimum required version is v1.20.0. Please upgrade UniFi Protect and then retry."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
@ -22,7 +22,7 @@
"step": {
"init": {
"title": "UniFi Protect Options",
"description": "Realtime metrics option should only be enabled if you have enabled the diagnostics sensors and want them updated in realtime. If if not enabled, they will only update once every 15 minutes.",
"description": "Realtime metrics option should only be enabled if you have enabled the diagnostics sensors and want them updated in realtime. If not enabled, they will only update once every 15 minutes.",
"data": {
"disable_rtsp": "Disable the RTSP stream",
"all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)",

View File

@ -6,7 +6,7 @@
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"unknown": "Unexpected error"
"protect_version": "Minimum required version is v1.20.0. Please upgrade UniFi Protect and then retry."
},
"step": {
"user": {
@ -26,7 +26,7 @@
"disable_rtsp": "Disable the RTSP stream",
"override_connection_host": "Override Connection Host"
},
"description": "Realtime metrics option should only be enabled if you have enabled the diagnostics sensors and want them updated in realtime. If if not enabled, they will only update once every 15 minutes.",
"description": "Realtime metrics option should only be enabled if you have enabled the diagnostics sensors and want them updated in realtime. If not enabled, they will only update once every 15 minutes.",
"title": "UniFi Protect Options"
}
}

View File

@ -161,8 +161,22 @@ async def simple_camera(
yield (camera, "camera.test_camera_high")
async def time_changed(hass, seconds):
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

View File

@ -2,13 +2,18 @@
from __future__ import annotations
from copy import copy
from typing import cast
from unittest.mock import AsyncMock, Mock
from pyunifiprotect.data import Camera as ProtectCamera
from pyunifiprotect.data.devices import CameraChannel
from pyunifiprotect.exceptions import NvrError
from homeassistant.components.camera import Camera, async_get_image
from homeassistant.components.camera import (
SUPPORT_STREAM,
Camera,
async_get_image,
async_get_stream_source,
)
from homeassistant.components.unifiprotect.const import (
ATTR_BITRATE,
ATTR_CHANNEL_ID,
@ -17,60 +22,96 @@ from homeassistant.components.unifiprotect.const import (
ATTR_WIDTH,
DEFAULT_ATTRIBUTION,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
)
from homeassistant.components.unifiprotect.data import ProtectData
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from .conftest import MockEntityFixture, time_changed
from .conftest import MockEntityFixture, enable_entity, time_changed
async def validate_camera_entity(
def validate_default_camera_entity(
hass: HomeAssistant,
camera: ProtectCamera,
channel_id: int,
secure: bool,
rtsp_enabled: bool,
enabled: bool,
):
) -> str:
"""Validate a camera entity."""
channel = camera.channels[channel_id]
entity_name = f"{camera.name} {channel.name}"
unique_id = f"{camera.id}_{channel.id}"
if not secure:
entity_name += " Insecure"
unique_id += "_insecure"
entity_id = f"camera.{entity_name.replace(' ', '_').lower()}"
entity_registry = er.async_get(hass)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is (not enabled)
assert entity.disabled is False
assert entity.unique_id == unique_id
if not enabled:
return
return entity_id
camera_platform = hass.data.get("camera")
assert camera_platform
ha_camera = cast(Camera, camera_platform.get_entity(entity_id))
assert ha_camera
if rtsp_enabled:
if secure:
assert await ha_camera.stream_source() == channel.rtsps_url
else:
assert await ha_camera.stream_source() == channel.rtsp_url
else:
assert await ha_camera.stream_source() is None
def validate_rtsps_camera_entity(
hass: HomeAssistant,
camera: ProtectCamera,
channel_id: int,
) -> str:
"""Validate a disabled RTSPS camera entity."""
channel = camera.channels[channel_id]
entity_name = f"{camera.name} {channel.name}"
unique_id = f"{camera.id}_{channel.id}"
entity_id = f"camera.{entity_name.replace(' ', '_').lower()}"
entity_registry = er.async_get(hass)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is True
assert entity.unique_id == unique_id
return entity_id
def validate_rtsp_camera_entity(
hass: HomeAssistant,
camera: ProtectCamera,
channel_id: int,
) -> str:
"""Validate a disabled RTSP camera entity."""
channel = camera.channels[channel_id]
entity_name = f"{camera.name} {channel.name} Insecure"
unique_id = f"{camera.id}_{channel.id}_insecure"
entity_id = f"camera.{entity_name.replace(' ', '_').lower()}"
entity_registry = er.async_get(hass)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is True
assert entity.unique_id == unique_id
return entity_id
def validate_common_camera_state(
hass: HomeAssistant,
channel: CameraChannel,
entity_id: str,
features: int = SUPPORT_STREAM,
):
"""Validate state that is common to all camera entity, regradless of type."""
entity_state = hass.states.get(entity_id)
assert entity_state
assert entity_state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
assert entity_state.attributes[ATTR_SUPPORTED_FEATURES] == features
assert entity_state.attributes[ATTR_WIDTH] == channel.width
assert entity_state.attributes[ATTR_HEIGHT] == channel.height
assert entity_state.attributes[ATTR_FPS] == channel.fps
@ -78,6 +119,48 @@ async def validate_camera_entity(
assert entity_state.attributes[ATTR_CHANNEL_ID] == channel.id
async def validate_rtsps_camera_state(
hass: HomeAssistant,
camera: ProtectCamera,
channel_id: int,
entity_id: str,
features: int = SUPPORT_STREAM,
):
"""Validate a camera's state."""
channel = camera.channels[channel_id]
assert await async_get_stream_source(hass, entity_id) == channel.rtsps_url
validate_common_camera_state(hass, channel, entity_id, features)
async def validate_rtsp_camera_state(
hass: HomeAssistant,
camera: ProtectCamera,
channel_id: int,
entity_id: str,
features: int = SUPPORT_STREAM,
):
"""Validate a camera's state."""
channel = camera.channels[channel_id]
assert await async_get_stream_source(hass, entity_id) == channel.rtsp_url
validate_common_camera_state(hass, channel, entity_id, features)
async def validate_no_stream_camera_state(
hass: HomeAssistant,
camera: ProtectCamera,
channel_id: int,
entity_id: str,
features: int = SUPPORT_STREAM,
):
"""Validate a camera's state."""
channel = camera.channels[channel_id]
assert await async_get_stream_source(hass, entity_id) is None
validate_common_camera_state(hass, channel, entity_id, features)
async def test_basic_setup(
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: ProtectCamera
):
@ -96,12 +179,25 @@ async def test_basic_setup(
camera_high_only.channels[1].is_rtsp_enabled = False
camera_high_only.channels[2].is_rtsp_enabled = False
camera_medium_only = mock_camera.copy(deep=True)
camera_medium_only._api = mock_entry.api
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.name = "Test Camera 2"
camera_medium_only.id = "test_medium"
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
camera_all_channels = mock_camera.copy(deep=True)
camera_all_channels._api = mock_entry.api
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 2"
camera_all_channels.name = "Test Camera 3"
camera_all_channels.id = "test_all"
camera_all_channels.channels[0].is_rtsp_enabled = True
camera_all_channels.channels[0].name = "High"
@ -118,7 +214,7 @@ async def test_basic_setup(
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.name = "Test Camera 3"
camera_no_channels.name = "Test Camera 4"
camera_no_channels.id = "test_none"
camera_no_channels.channels[0].is_rtsp_enabled = False
camera_no_channels.channels[0].name = "High"
@ -127,6 +223,7 @@ async def test_basic_setup(
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,
}
@ -134,34 +231,55 @@ async def test_basic_setup(
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done()
await validate_camera_entity(
hass, camera_high_only, 0, secure=True, rtsp_enabled=True, enabled=True
)
await validate_camera_entity(
hass, camera_high_only, 0, secure=False, rtsp_enabled=True, enabled=False
)
entity_registry = er.async_get(hass)
await validate_camera_entity(
hass, camera_all_channels, 0, secure=True, rtsp_enabled=True, enabled=True
)
await validate_camera_entity(
hass, camera_all_channels, 0, secure=False, rtsp_enabled=True, enabled=False
)
await validate_camera_entity(
hass, camera_all_channels, 1, secure=True, rtsp_enabled=True, enabled=False
)
await validate_camera_entity(
hass, camera_all_channels, 1, secure=False, rtsp_enabled=True, enabled=False
)
await validate_camera_entity(
hass, camera_all_channels, 2, secure=True, rtsp_enabled=True, enabled=False
)
await validate_camera_entity(
hass, camera_all_channels, 2, secure=False, rtsp_enabled=True, enabled=False
)
assert len(hass.states.async_all()) == 4
assert len(entity_registry.entities) == 11
await validate_camera_entity(
hass, camera_no_channels, 0, secure=True, rtsp_enabled=False, enabled=True
# test camera 1
entity_id = validate_default_camera_entity(hass, camera_high_only, 0)
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 validate_rtsp_camera_state(hass, camera_high_only, 0, entity_id)
# test camera 2
entity_id = validate_default_camera_entity(hass, camera_medium_only, 1)
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 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_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_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_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_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_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)
# test camera 4
entity_id = validate_default_camera_entity(hass, camera_no_channels, 0)
await validate_no_stream_camera_state(
hass, camera_no_channels, 0, entity_id, features=0
)
@ -206,10 +324,6 @@ async def test_camera_generic_update(
assert await async_setup_component(hass, "homeassistant", {})
data: ProtectData = hass.data[DOMAIN][mock_entry.entry.entry_id]
assert data
assert data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "idle"
@ -232,10 +346,6 @@ async def test_camera_interval_update(
):
"""Interval updates updates camera entity."""
data: ProtectData = hass.data[DOMAIN][mock_entry.entry.entry_id]
assert data
assert data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "idle"
@ -259,10 +369,6 @@ async def test_camera_bad_interval_update(
):
"""Interval updates marks camera unavailable."""
data: ProtectData = hass.data[DOMAIN][mock_entry.entry.entry_id]
assert data
assert data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "idle"
@ -270,7 +376,6 @@ async def test_camera_bad_interval_update(
mock_entry.api.update = AsyncMock(side_effect=NvrError)
await time_changed(hass, DEFAULT_SCAN_INTERVAL)
assert not data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "unavailable"
@ -278,7 +383,6 @@ async def test_camera_bad_interval_update(
mock_entry.api.update = AsyncMock(return_value=mock_entry.api.bootstrap)
await time_changed(hass, DEFAULT_SCAN_INTERVAL)
assert data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "idle"
@ -290,10 +394,6 @@ async def test_camera_ws_update(
):
"""WS update updates camera entity."""
data: ProtectData = hass.data[DOMAIN][mock_entry.entry.entry_id]
assert data
assert data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "idle"
@ -320,10 +420,6 @@ async def test_camera_ws_update_offline(
):
"""WS updates marks camera unavailable."""
data: ProtectData = hass.data[DOMAIN][mock_entry.entry.entry_id]
assert data
assert data.last_update_success
state = hass.states.get(simple_camera[1])
assert state and state.state == "idle"

View File

@ -129,7 +129,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
)
assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "nvr_error"}
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_reauth_auth(hass: HomeAssistant) -> None:
@ -190,7 +190,7 @@ async def test_form_reauth_auth(hass: HomeAssistant) -> None:
assert result3["reason"] == "reauth_successful"
async def test_form_options(hass: HomeAssistant) -> None:
async def test_form_options(hass: HomeAssistant, mock_client) -> None:
"""Test we handle options flows."""
mock_config = MockConfigEntry(
domain=DOMAIN,
@ -207,7 +207,12 @@ async def test_form_options(hass: HomeAssistant) -> None:
)
mock_config.add_to_hass(hass)
# Integration not setup, since we are only flipping bits in options entry
with patch("homeassistant.components.unifiprotect.ProtectApiClient") as mock_api:
mock_api.return_value = mock_client
await hass.config_entries.async_setup(mock_config.entry_id)
await hass.async_block_till_done()
assert mock_config.state == config_entries.ConfigEntryState.LOADED
result = await hass.config_entries.options.async_init(mock_config.entry_id)
assert result["type"] == RESULT_TYPE_FORM

View File

@ -5,7 +5,7 @@ from unittest.mock import AsyncMock
from pyunifiprotect import NotAuthorized, NvrError
from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP, DOMAIN
from homeassistant.components.unifiprotect.const import CONF_DISABLE_RTSP
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
@ -21,7 +21,6 @@ async def test_setup(hass: HomeAssistant, mock_entry: MockEntityFixture):
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 mock_entry.entry.entry_id in hass.data[DOMAIN]
async def test_reload(hass: HomeAssistant, mock_entry: MockEntityFixture):
@ -37,7 +36,6 @@ async def test_reload(hass: HomeAssistant, mock_entry: MockEntityFixture):
await hass.async_block_till_done()
assert mock_entry.entry.state == ConfigEntryState.LOADED
assert mock_entry.entry.entry_id in hass.data[DOMAIN]
assert mock_entry.api.async_disconnect_ws.called