2021-12-26 06:12:57 +00:00
|
|
|
"""Test the UniFi Protect camera platform."""
|
2021-12-29 04:36:18 +00:00
|
|
|
# pylint: disable=protected-access
|
2021-12-26 06:12:57 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from copy import copy
|
2021-12-29 04:36:18 +00:00
|
|
|
from unittest.mock import AsyncMock, Mock, patch
|
2021-12-26 06:12:57 +00:00
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
import pytest
|
2021-12-26 06:12:57 +00:00
|
|
|
from pyunifiprotect.data import Camera as ProtectCamera
|
2021-12-27 11:39:24 +00:00
|
|
|
from pyunifiprotect.data.devices import CameraChannel
|
2021-12-29 22:39:55 +00:00
|
|
|
from pyunifiprotect.data.types import StateType
|
2021-12-26 06:12:57 +00:00
|
|
|
from pyunifiprotect.exceptions import NvrError
|
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
from homeassistant.components.camera import (
|
|
|
|
SUPPORT_STREAM,
|
|
|
|
Camera,
|
|
|
|
async_get_image,
|
|
|
|
async_get_stream_source,
|
|
|
|
)
|
2021-12-26 06:12:57 +00:00
|
|
|
from homeassistant.components.unifiprotect.const import (
|
|
|
|
ATTR_BITRATE,
|
|
|
|
ATTR_CHANNEL_ID,
|
|
|
|
ATTR_FPS,
|
|
|
|
ATTR_HEIGHT,
|
|
|
|
ATTR_WIDTH,
|
|
|
|
DEFAULT_ATTRIBUTION,
|
|
|
|
DEFAULT_SCAN_INTERVAL,
|
|
|
|
)
|
2021-12-27 11:39:24 +00:00
|
|
|
from homeassistant.const import (
|
|
|
|
ATTR_ATTRIBUTION,
|
|
|
|
ATTR_ENTITY_ID,
|
|
|
|
ATTR_SUPPORTED_FEATURES,
|
2021-12-29 04:36:18 +00:00
|
|
|
Platform,
|
2021-12-27 11:39:24 +00:00
|
|
|
)
|
2021-12-26 06:12:57 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.helpers import entity_registry as er
|
|
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
from .conftest import MockEntityFixture, enable_entity, time_changed
|
2021-12-26 06:12:57 +00:00
|
|
|
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
@pytest.fixture(name="camera")
|
|
|
|
async def camera_fixture(
|
|
|
|
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: Camera
|
|
|
|
):
|
|
|
|
"""Fixture for a single camera, no extra setup."""
|
|
|
|
|
|
|
|
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.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,
|
|
|
|
}
|
|
|
|
|
|
|
|
with patch("homeassistant.components.unifiprotect.PLATFORMS", [Platform.CAMERA]):
|
|
|
|
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()) == 1
|
|
|
|
assert len(entity_registry.entities) == 2
|
|
|
|
|
|
|
|
yield (camera_obj, "camera.test_camera_high")
|
|
|
|
|
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
def validate_default_camera_entity(
|
2021-12-26 06:12:57 +00:00
|
|
|
hass: HomeAssistant,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera_obj: ProtectCamera,
|
2021-12-26 06:12:57 +00:00
|
|
|
channel_id: int,
|
2021-12-27 11:39:24 +00:00
|
|
|
) -> str:
|
2021-12-26 06:12:57 +00:00
|
|
|
"""Validate a camera entity."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
channel = camera_obj.channels[channel_id]
|
2021-12-26 06:12:57 +00:00
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
entity_name = f"{camera_obj.name} {channel.name}"
|
|
|
|
unique_id = f"{camera_obj.id}_{channel.id}"
|
2021-12-26 06:12:57 +00:00
|
|
|
entity_id = f"camera.{entity_name.replace(' ', '_').lower()}"
|
|
|
|
|
|
|
|
entity_registry = er.async_get(hass)
|
|
|
|
entity = entity_registry.async_get(entity_id)
|
|
|
|
assert entity
|
2021-12-27 11:39:24 +00:00
|
|
|
assert entity.disabled is False
|
|
|
|
assert entity.unique_id == unique_id
|
|
|
|
|
|
|
|
return entity_id
|
|
|
|
|
|
|
|
|
|
|
|
def validate_rtsps_camera_entity(
|
|
|
|
hass: HomeAssistant,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera_obj: ProtectCamera,
|
2021-12-27 11:39:24 +00:00
|
|
|
channel_id: int,
|
|
|
|
) -> str:
|
|
|
|
"""Validate a disabled RTSPS camera entity."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
channel = camera_obj.channels[channel_id]
|
2021-12-27 11:39:24 +00:00
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
entity_name = f"{camera_obj.name} {channel.name}"
|
|
|
|
unique_id = f"{camera_obj.id}_{channel.id}"
|
2021-12-27 11:39:24 +00:00
|
|
|
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,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera_obj: ProtectCamera,
|
2021-12-27 11:39:24 +00:00
|
|
|
channel_id: int,
|
|
|
|
) -> str:
|
|
|
|
"""Validate a disabled RTSP camera entity."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
channel = camera_obj.channels[channel_id]
|
2021-12-27 11:39:24 +00:00
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
entity_name = f"{camera_obj.name} {channel.name} Insecure"
|
|
|
|
unique_id = f"{camera_obj.id}_{channel.id}_insecure"
|
2021-12-27 11:39:24 +00:00
|
|
|
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
|
2021-12-26 06:12:57 +00:00
|
|
|
assert entity.unique_id == unique_id
|
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
return entity_id
|
|
|
|
|
2021-12-26 06:12:57 +00:00
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
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."""
|
2021-12-26 06:12:57 +00:00
|
|
|
entity_state = hass.states.get(entity_id)
|
|
|
|
assert entity_state
|
|
|
|
assert entity_state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
2021-12-27 11:39:24 +00:00
|
|
|
assert entity_state.attributes[ATTR_SUPPORTED_FEATURES] == features
|
2021-12-26 06:12:57 +00:00
|
|
|
assert entity_state.attributes[ATTR_WIDTH] == channel.width
|
|
|
|
assert entity_state.attributes[ATTR_HEIGHT] == channel.height
|
|
|
|
assert entity_state.attributes[ATTR_FPS] == channel.fps
|
|
|
|
assert entity_state.attributes[ATTR_BITRATE] == channel.bitrate
|
|
|
|
assert entity_state.attributes[ATTR_CHANNEL_ID] == channel.id
|
|
|
|
|
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
async def validate_rtsps_camera_state(
|
|
|
|
hass: HomeAssistant,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera_obj: ProtectCamera,
|
2021-12-27 11:39:24 +00:00
|
|
|
channel_id: int,
|
|
|
|
entity_id: str,
|
|
|
|
features: int = SUPPORT_STREAM,
|
|
|
|
):
|
|
|
|
"""Validate a camera's state."""
|
2021-12-29 04:36:18 +00:00
|
|
|
channel = camera_obj.channels[channel_id]
|
2021-12-27 11:39:24 +00:00
|
|
|
|
|
|
|
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,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera_obj: ProtectCamera,
|
2021-12-27 11:39:24 +00:00
|
|
|
channel_id: int,
|
|
|
|
entity_id: str,
|
|
|
|
features: int = SUPPORT_STREAM,
|
|
|
|
):
|
|
|
|
"""Validate a camera's state."""
|
2021-12-29 04:36:18 +00:00
|
|
|
channel = camera_obj.channels[channel_id]
|
2021-12-27 11:39:24 +00:00
|
|
|
|
|
|
|
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,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera_obj: ProtectCamera,
|
2021-12-27 11:39:24 +00:00
|
|
|
channel_id: int,
|
|
|
|
entity_id: str,
|
|
|
|
features: int = SUPPORT_STREAM,
|
|
|
|
):
|
|
|
|
"""Validate a camera's state."""
|
2021-12-29 04:36:18 +00:00
|
|
|
channel = camera_obj.channels[channel_id]
|
2021-12-27 11:39:24 +00:00
|
|
|
|
|
|
|
assert await async_get_stream_source(hass, entity_id) is None
|
|
|
|
validate_common_camera_state(hass, channel, entity_id, features)
|
|
|
|
|
|
|
|
|
2021-12-26 06:12:57 +00:00
|
|
|
async def test_basic_setup(
|
|
|
|
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: ProtectCamera
|
|
|
|
):
|
|
|
|
"""Test working setup of unifiprotect entry."""
|
|
|
|
|
|
|
|
camera_high_only = mock_camera.copy(deep=True)
|
|
|
|
camera_high_only._api = mock_entry.api
|
|
|
|
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.name = "Test Camera 1"
|
|
|
|
camera_high_only.id = "test_high"
|
|
|
|
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
|
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
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
|
|
|
|
|
2021-12-26 06:12:57 +00:00
|
|
|
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
|
2021-12-27 11:39:24 +00:00
|
|
|
camera_all_channels.name = "Test Camera 3"
|
2021-12-26 06:12:57 +00:00
|
|
|
camera_all_channels.id = "test_all"
|
|
|
|
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"
|
|
|
|
|
|
|
|
camera_no_channels = mock_camera.copy(deep=True)
|
|
|
|
camera_no_channels._api = mock_entry.api
|
|
|
|
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
|
2021-12-27 11:39:24 +00:00
|
|
|
camera_no_channels.name = "Test Camera 4"
|
2021-12-26 06:12:57 +00:00
|
|
|
camera_no_channels.id = "test_none"
|
|
|
|
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
|
|
|
|
|
|
|
|
mock_entry.api.bootstrap.cameras = {
|
|
|
|
camera_high_only.id: camera_high_only,
|
2021-12-27 11:39:24 +00:00
|
|
|
camera_medium_only.id: camera_medium_only,
|
2021-12-26 06:12:57 +00:00
|
|
|
camera_all_channels.id: camera_all_channels,
|
|
|
|
camera_no_channels.id: camera_no_channels,
|
|
|
|
}
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
with patch("homeassistant.components.unifiprotect.PLATFORMS", [Platform.CAMERA]):
|
|
|
|
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
2021-12-26 06:12:57 +00:00
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
entity_registry = er.async_get(hass)
|
2021-12-26 06:12:57 +00:00
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
assert len(hass.states.async_all()) == 4
|
|
|
|
assert len(entity_registry.entities) == 11
|
|
|
|
|
|
|
|
# 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)
|
2021-12-26 06:12:57 +00:00
|
|
|
|
2021-12-27 11:39:24 +00:00
|
|
|
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
|
2021-12-26 06:12:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_missing_channels(
|
|
|
|
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_camera: ProtectCamera
|
|
|
|
):
|
|
|
|
"""Test setting up camera with no camera channels."""
|
|
|
|
|
|
|
|
camera = mock_camera.copy(deep=True)
|
|
|
|
camera.channels = []
|
|
|
|
|
|
|
|
mock_entry.api.bootstrap.cameras = {camera.id: camera}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_image(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
mock_entry: MockEntityFixture,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera: tuple[Camera, str],
|
2021-12-26 06:12:57 +00:00
|
|
|
):
|
|
|
|
"""Test retrieving camera image."""
|
|
|
|
|
|
|
|
mock_entry.api.get_camera_snapshot = AsyncMock()
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
await async_get_image(hass, camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
mock_entry.api.get_camera_snapshot.assert_called_once()
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_generic_update(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
mock_entry: MockEntityFixture,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera: tuple[ProtectCamera, str],
|
2021-12-26 06:12:57 +00:00
|
|
|
):
|
|
|
|
"""Tests generic entity update service."""
|
|
|
|
|
|
|
|
assert await async_setup_component(hass, "homeassistant", {})
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
mock_entry.api.update = AsyncMock(return_value=None)
|
|
|
|
await hass.services.async_call(
|
|
|
|
"homeassistant",
|
|
|
|
"update_entity",
|
2021-12-29 04:36:18 +00:00
|
|
|
{ATTR_ENTITY_ID: camera[1]},
|
2021-12-26 06:12:57 +00:00
|
|
|
blocking=True,
|
|
|
|
)
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_interval_update(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
mock_entry: MockEntityFixture,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera: tuple[ProtectCamera, str],
|
2021-12-26 06:12:57 +00:00
|
|
|
):
|
|
|
|
"""Interval updates updates camera entity."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
2021-12-29 04:36:18 +00:00
|
|
|
new_camera = camera[0].copy()
|
2021-12-26 06:12:57 +00:00
|
|
|
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
|
|
|
|
await time_changed(hass, DEFAULT_SCAN_INTERVAL)
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "recording"
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_bad_interval_update(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
mock_entry: MockEntityFixture,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera: tuple[Camera, str],
|
2021-12-26 06:12:57 +00:00
|
|
|
):
|
|
|
|
"""Interval updates marks camera unavailable."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
# update fails
|
|
|
|
mock_entry.api.update = AsyncMock(side_effect=NvrError)
|
|
|
|
await time_changed(hass, DEFAULT_SCAN_INTERVAL)
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "unavailable"
|
|
|
|
|
|
|
|
# next update succeeds
|
|
|
|
mock_entry.api.update = AsyncMock(return_value=mock_entry.api.bootstrap)
|
|
|
|
await time_changed(hass, DEFAULT_SCAN_INTERVAL)
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_ws_update(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
mock_entry: MockEntityFixture,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera: tuple[ProtectCamera, str],
|
2021-12-26 06:12:57 +00:00
|
|
|
):
|
|
|
|
"""WS update updates camera entity."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
2021-12-29 04:36:18 +00:00
|
|
|
new_camera = camera[0].copy()
|
2021-12-26 06:12:57 +00:00
|
|
|
new_camera.is_recording = True
|
|
|
|
|
|
|
|
mock_msg = Mock()
|
|
|
|
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)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "recording"
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_ws_update_offline(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
mock_entry: MockEntityFixture,
|
2021-12-29 04:36:18 +00:00
|
|
|
camera: tuple[ProtectCamera, str],
|
2021-12-26 06:12:57 +00:00
|
|
|
):
|
|
|
|
"""WS updates marks camera unavailable."""
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|
|
|
|
|
|
|
|
# camera goes offline
|
|
|
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
2021-12-29 04:36:18 +00:00
|
|
|
new_camera = camera[0].copy()
|
2021-12-29 22:39:55 +00:00
|
|
|
new_camera.state = StateType.DISCONNECTED
|
2021-12-26 06:12:57 +00:00
|
|
|
|
|
|
|
mock_msg = Mock()
|
|
|
|
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)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "unavailable"
|
|
|
|
|
|
|
|
# camera comes back online
|
2021-12-29 22:39:55 +00:00
|
|
|
new_camera.state = StateType.CONNECTED
|
2021-12-26 06:12:57 +00:00
|
|
|
|
|
|
|
mock_msg = Mock()
|
|
|
|
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)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2021-12-29 04:36:18 +00:00
|
|
|
state = hass.states.get(camera[1])
|
2021-12-26 06:12:57 +00:00
|
|
|
assert state and state.state == "idle"
|