mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Improve uvc test camera (#41438)
* Improve uvc test camera * Clean setup full config * Clean setup partial config * Set more camera defaults * Clean setup partial config v31x * Clean setup incomplete config * Clean setup nvr errors during indexing * Clean setup nvr errors during initialization * Clean properties * Fix motion recording mode properties * Clean stream * Clean login * Clean login v31x * Clean login tries both addres and caches * Clean login fails both properly * Remove not needed test * Clean camera image error * Test snapshot login retry * Clean up * Test enable and disable motion detection * Times must be UTC Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
e798f415a4
commit
bc0eb9bf32
@ -13,6 +13,7 @@ from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Cam
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_SSL
|
from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_SSL
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.util.dt import utc_from_timestamp
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -259,5 +260,5 @@ class UnifiVideoCamera(Camera):
|
|||||||
def timestamp_ms_to_date(epoch_ms: int) -> datetime | None:
|
def timestamp_ms_to_date(epoch_ms: int) -> datetime | None:
|
||||||
"""Convert millisecond timestamp to datetime."""
|
"""Convert millisecond timestamp to datetime."""
|
||||||
if epoch_ms:
|
if epoch_ms:
|
||||||
return datetime.fromtimestamp(epoch_ms / 1000)
|
return utc_from_timestamp(epoch_ms / 1000)
|
||||||
return None
|
return None
|
||||||
|
@ -1,381 +1,602 @@
|
|||||||
"""The tests for UVC camera module."""
|
"""The tests for UVC camera module."""
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta, timezone
|
||||||
import socket
|
from unittest.mock import call, patch
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
from uvcclient import camera, nvr
|
from uvcclient import camera as camera, nvr
|
||||||
|
|
||||||
from homeassistant.components.camera import SUPPORT_STREAM
|
from homeassistant.components.camera import (
|
||||||
from homeassistant.components.uvc import camera as uvc
|
DEFAULT_CONTENT_TYPE,
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
SERVICE_DISABLE_MOTION,
|
||||||
from homeassistant.setup import setup_component
|
SERVICE_ENABLE_MOTION,
|
||||||
|
STATE_RECORDING,
|
||||||
|
SUPPORT_STREAM,
|
||||||
|
async_get_image,
|
||||||
|
async_get_stream_source,
|
||||||
|
)
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
class TestUVCSetup(unittest.TestCase):
|
@pytest.fixture(name="mock_remote")
|
||||||
"""Test the UVC camera platform."""
|
def mock_remote_fixture(camera_info):
|
||||||
|
"""Mock the nvr.UVCRemote class."""
|
||||||
|
with patch("homeassistant.components.uvc.camera.nvr.UVCRemote") as mock_remote:
|
||||||
|
|
||||||
def setUp(self):
|
def setup(host, port, apikey, ssl=False):
|
||||||
"""Set up things to be run when tests are started."""
|
"""Set instance attributes."""
|
||||||
self.hass = get_test_home_assistant()
|
mock_remote.return_value._host = host
|
||||||
self.addCleanup(self.hass.stop)
|
mock_remote.return_value._port = port
|
||||||
|
mock_remote.return_value._apikey = apikey
|
||||||
|
mock_remote.return_value._ssl = ssl
|
||||||
|
return mock_remote.return_value
|
||||||
|
|
||||||
@mock.patch("uvcclient.nvr.UVCRemote")
|
mock_remote.side_effect = setup
|
||||||
@mock.patch.object(uvc, "UnifiVideoCamera")
|
mock_remote.return_value.get_camera.return_value = camera_info
|
||||||
def test_setup_full_config(self, mock_uvc, mock_remote):
|
|
||||||
"""Test the setup with full configuration."""
|
|
||||||
config = {
|
|
||||||
"platform": "uvc",
|
|
||||||
"nvr": "foo",
|
|
||||||
"password": "bar",
|
|
||||||
"port": 123,
|
|
||||||
"key": "secret",
|
|
||||||
}
|
|
||||||
mock_cameras = [
|
mock_cameras = [
|
||||||
{"uuid": "one", "name": "Front", "id": "id1"},
|
{"uuid": "one", "name": "Front", "id": "id1"},
|
||||||
{"uuid": "two", "name": "Back", "id": "id2"},
|
{"uuid": "two", "name": "Back", "id": "id2"},
|
||||||
{"uuid": "three", "name": "Old AirCam", "id": "id3"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def mock_get_camera(uuid):
|
|
||||||
"""Create a mock camera."""
|
|
||||||
if uuid == "id3":
|
|
||||||
return {"model": "airCam"}
|
|
||||||
return {"model": "UVC"}
|
|
||||||
|
|
||||||
mock_remote.return_value.index.return_value = mock_cameras
|
mock_remote.return_value.index.return_value = mock_cameras
|
||||||
mock_remote.return_value.get_camera.side_effect = mock_get_camera
|
|
||||||
mock_remote.return_value.server_version = (3, 2, 0)
|
mock_remote.return_value.server_version = (3, 2, 0)
|
||||||
|
yield mock_remote
|
||||||
assert setup_component(self.hass, "camera", {"camera": config})
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert mock_remote.call_count == 1
|
|
||||||
assert mock_remote.call_args == mock.call("foo", 123, "secret", ssl=False)
|
|
||||||
mock_uvc.assert_has_calls(
|
|
||||||
[
|
|
||||||
mock.call(mock_remote.return_value, "id1", "Front", "bar"),
|
|
||||||
mock.call(mock_remote.return_value, "id2", "Back", "bar"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch("uvcclient.nvr.UVCRemote")
|
|
||||||
@mock.patch.object(uvc, "UnifiVideoCamera")
|
|
||||||
def test_setup_partial_config(self, mock_uvc, mock_remote):
|
|
||||||
"""Test the setup with partial configuration."""
|
|
||||||
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
|
||||||
mock_cameras = [
|
|
||||||
{"uuid": "one", "name": "Front", "id": "id1"},
|
|
||||||
{"uuid": "two", "name": "Back", "id": "id2"},
|
|
||||||
]
|
|
||||||
mock_remote.return_value.index.return_value = mock_cameras
|
|
||||||
mock_remote.return_value.get_camera.return_value = {"model": "UVC"}
|
|
||||||
mock_remote.return_value.server_version = (3, 2, 0)
|
|
||||||
|
|
||||||
assert setup_component(self.hass, "camera", {"camera": config})
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert mock_remote.call_count == 1
|
|
||||||
assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False)
|
|
||||||
mock_uvc.assert_has_calls(
|
|
||||||
[
|
|
||||||
mock.call(mock_remote.return_value, "id1", "Front", "ubnt"),
|
|
||||||
mock.call(mock_remote.return_value, "id2", "Back", "ubnt"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch("uvcclient.nvr.UVCRemote")
|
|
||||||
@mock.patch.object(uvc, "UnifiVideoCamera")
|
|
||||||
def test_setup_partial_config_v31x(self, mock_uvc, mock_remote):
|
|
||||||
"""Test the setup with a v3.1.x server."""
|
|
||||||
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
|
||||||
mock_cameras = [
|
|
||||||
{"uuid": "one", "name": "Front", "id": "id1"},
|
|
||||||
{"uuid": "two", "name": "Back", "id": "id2"},
|
|
||||||
]
|
|
||||||
mock_remote.return_value.index.return_value = mock_cameras
|
|
||||||
mock_remote.return_value.get_camera.return_value = {"model": "UVC"}
|
|
||||||
mock_remote.return_value.server_version = (3, 1, 3)
|
|
||||||
|
|
||||||
assert setup_component(self.hass, "camera", {"camera": config})
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert mock_remote.call_count == 1
|
|
||||||
assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False)
|
|
||||||
mock_uvc.assert_has_calls(
|
|
||||||
[
|
|
||||||
mock.call(mock_remote.return_value, "one", "Front", "ubnt"),
|
|
||||||
mock.call(mock_remote.return_value, "two", "Back", "ubnt"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch.object(uvc, "UnifiVideoCamera")
|
|
||||||
def test_setup_incomplete_config(self, mock_uvc):
|
|
||||||
"""Test the setup with incomplete configuration."""
|
|
||||||
assert setup_component(self.hass, "camera", {"platform": "uvc", "nvr": "foo"})
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert not mock_uvc.called
|
|
||||||
assert setup_component(
|
|
||||||
self.hass, "camera", {"platform": "uvc", "key": "secret"}
|
|
||||||
)
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert not mock_uvc.called
|
|
||||||
assert setup_component(
|
|
||||||
self.hass, "camera", {"platform": "uvc", "port": "invalid"}
|
|
||||||
)
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert not mock_uvc.called
|
|
||||||
|
|
||||||
@mock.patch.object(uvc, "UnifiVideoCamera")
|
|
||||||
@mock.patch("uvcclient.nvr.UVCRemote")
|
|
||||||
def setup_nvr_errors_during_indexing(self, error, mock_remote, mock_uvc):
|
|
||||||
"""Set up test for NVR errors during indexing."""
|
|
||||||
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
|
||||||
mock_remote.return_value.index.side_effect = error
|
|
||||||
assert setup_component(self.hass, "camera", {"camera": config})
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert not mock_uvc.called
|
|
||||||
|
|
||||||
def test_setup_nvr_error_during_indexing_notauthorized(self):
|
|
||||||
"""Test for error: nvr.NotAuthorized."""
|
|
||||||
self.setup_nvr_errors_during_indexing(nvr.NotAuthorized)
|
|
||||||
|
|
||||||
def test_setup_nvr_error_during_indexing_nvrerror(self):
|
|
||||||
"""Test for error: nvr.NvrError."""
|
|
||||||
self.setup_nvr_errors_during_indexing(nvr.NvrError)
|
|
||||||
pytest.raises(PlatformNotReady)
|
|
||||||
|
|
||||||
def test_setup_nvr_error_during_indexing_connectionerror(self):
|
|
||||||
"""Test for error: requests.exceptions.ConnectionError."""
|
|
||||||
self.setup_nvr_errors_during_indexing(requests.exceptions.ConnectionError)
|
|
||||||
pytest.raises(PlatformNotReady)
|
|
||||||
|
|
||||||
@mock.patch.object(uvc, "UnifiVideoCamera")
|
|
||||||
@mock.patch("uvcclient.nvr.UVCRemote.__init__")
|
|
||||||
def setup_nvr_errors_during_initialization(self, error, mock_remote, mock_uvc):
|
|
||||||
"""Set up test for NVR errors during initialization."""
|
|
||||||
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
|
||||||
mock_remote.return_value = None
|
|
||||||
mock_remote.side_effect = error
|
|
||||||
assert setup_component(self.hass, "camera", {"camera": config})
|
|
||||||
self.hass.block_till_done()
|
|
||||||
|
|
||||||
assert not mock_remote.index.called
|
|
||||||
assert not mock_uvc.called
|
|
||||||
|
|
||||||
def test_setup_nvr_error_during_initialization_notauthorized(self):
|
|
||||||
"""Test for error: nvr.NotAuthorized."""
|
|
||||||
self.setup_nvr_errors_during_initialization(nvr.NotAuthorized)
|
|
||||||
|
|
||||||
def test_setup_nvr_error_during_initialization_nvrerror(self):
|
|
||||||
"""Test for error: nvr.NvrError."""
|
|
||||||
self.setup_nvr_errors_during_initialization(nvr.NvrError)
|
|
||||||
pytest.raises(PlatformNotReady)
|
|
||||||
|
|
||||||
def test_setup_nvr_error_during_initialization_connectionerror(self):
|
|
||||||
"""Test for error: requests.exceptions.ConnectionError."""
|
|
||||||
self.setup_nvr_errors_during_initialization(requests.exceptions.ConnectionError)
|
|
||||||
pytest.raises(PlatformNotReady)
|
|
||||||
|
|
||||||
|
|
||||||
class TestUVC(unittest.TestCase):
|
@pytest.fixture(name="camera_info")
|
||||||
"""Test class for UVC."""
|
def camera_info_fixture():
|
||||||
|
"""Mock the camera info of a camera."""
|
||||||
def setup_method(self, method):
|
return {
|
||||||
"""Set up the mock camera."""
|
"model": "UVC",
|
||||||
self.nvr = mock.MagicMock()
|
"recordingSettings": {
|
||||||
self.uuid = "uuid"
|
"fullTimeRecordEnabled": True,
|
||||||
self.name = "name"
|
"motionRecordEnabled": False,
|
||||||
self.password = "seekret"
|
},
|
||||||
self.uvc = uvc.UnifiVideoCamera(self.nvr, self.uuid, self.name, self.password)
|
"host": "host-a",
|
||||||
self.nvr.get_camera.return_value = {
|
"internalHost": "host-b",
|
||||||
"model": "UVC Fake",
|
"username": "admin",
|
||||||
"uuid": "06e3ff29-8048-31c2-8574-0852d1bd0e03",
|
"lastRecordingStartTime": 1610070992367,
|
||||||
"recordingSettings": {
|
"channels": [
|
||||||
"fullTimeRecordEnabled": True,
|
{
|
||||||
"motionRecordEnabled": False,
|
"id": "0",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080,
|
||||||
|
"fps": 25,
|
||||||
|
"bitrate": 6000000,
|
||||||
|
"isRtspEnabled": True,
|
||||||
|
"rtspUris": [
|
||||||
|
"rtsp://host-a:7447/uuid_rtspchannel_0",
|
||||||
|
"rtsp://foo:7447/uuid_rtspchannel_0",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"host": "host-a",
|
{
|
||||||
"internalHost": "host-b",
|
"id": "1",
|
||||||
"username": "admin",
|
"width": 1024,
|
||||||
"lastRecordingStartTime": 1610070992367,
|
"height": 576,
|
||||||
"channels": [
|
"fps": 15,
|
||||||
{
|
"bitrate": 1200000,
|
||||||
"id": "0",
|
"isRtspEnabled": False,
|
||||||
"width": 1920,
|
"rtspUris": [
|
||||||
"height": 1080,
|
"rtsp://host-a:7447/uuid_rtspchannel_1",
|
||||||
"fps": 25,
|
"rtsp://foo:7447/uuid_rtspchannel_1",
|
||||||
"bitrate": 6000000,
|
],
|
||||||
"isRtspEnabled": True,
|
},
|
||||||
"rtspUris": [
|
],
|
||||||
"rtsp://host-a:7447/uuid_rtspchannel_0",
|
}
|
||||||
"rtsp://foo:7447/uuid_rtspchannel_0",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "1",
|
|
||||||
"width": 1024,
|
|
||||||
"height": 576,
|
|
||||||
"fps": 15,
|
|
||||||
"bitrate": 1200000,
|
|
||||||
"isRtspEnabled": False,
|
|
||||||
"rtspUris": [
|
|
||||||
"rtsp://host-a:7447/uuid_rtspchannel_1",
|
|
||||||
"rtsp://foo:7447/uuid_rtspchannel_1",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
self.nvr.server_version = (3, 2, 0)
|
|
||||||
self.uvc.update()
|
|
||||||
|
|
||||||
def test_properties(self):
|
|
||||||
"""Test the properties."""
|
|
||||||
assert self.name == self.uvc.name
|
|
||||||
assert self.uvc.is_recording
|
|
||||||
assert "Ubiquiti" == self.uvc.brand
|
|
||||||
assert "UVC Fake" == self.uvc.model
|
|
||||||
assert SUPPORT_STREAM == self.uvc.supported_features
|
|
||||||
assert "uuid" == self.uvc.unique_id
|
|
||||||
|
|
||||||
def test_motion_recording_mode_properties(self):
|
@pytest.fixture(name="camera_v320")
|
||||||
"""Test the properties."""
|
def camera_v320_fixture():
|
||||||
self.nvr.get_camera.return_value["recordingSettings"][
|
"""Mock the v320 camera."""
|
||||||
"fullTimeRecordEnabled"
|
with patch(
|
||||||
] = False
|
"homeassistant.components.uvc.camera.uvc_camera.UVCCameraClientV320"
|
||||||
self.nvr.get_camera.return_value["recordingSettings"][
|
) as camera:
|
||||||
"motionRecordEnabled"
|
camera.return_value.get_snapshot.return_value = "test_image"
|
||||||
] = True
|
yield camera
|
||||||
assert not self.uvc.is_recording
|
|
||||||
assert (
|
|
||||||
datetime(2021, 1, 8, 1, 56, 32, 367000)
|
|
||||||
== self.uvc.extra_state_attributes["last_recording_start_time"]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.nvr.get_camera.return_value["recordingIndicator"] = "DISABLED"
|
|
||||||
assert not self.uvc.is_recording
|
|
||||||
|
|
||||||
self.nvr.get_camera.return_value["recordingIndicator"] = "MOTION_INPROGRESS"
|
@pytest.fixture(name="camera_v313")
|
||||||
assert self.uvc.is_recording
|
def camera_v313_fixture():
|
||||||
|
"""Mock the v320 camera."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.uvc.camera.uvc_camera.UVCCameraClient"
|
||||||
|
) as camera:
|
||||||
|
camera.return_value.get_snapshot.return_value = "test_image"
|
||||||
|
yield camera
|
||||||
|
|
||||||
self.nvr.get_camera.return_value["recordingIndicator"] = "MOTION_FINISHED"
|
|
||||||
assert self.uvc.is_recording
|
|
||||||
|
|
||||||
def test_stream(self):
|
async def test_setup_full_config(hass, mock_remote, camera_info):
|
||||||
"""Test the RTSP stream URI."""
|
"""Test the setup with full configuration."""
|
||||||
stream_source = yield from self.uvc.stream_source()
|
config = {
|
||||||
assert stream_source == "rtsp://foo:7447/uuid_rtspchannel_0"
|
"platform": "uvc",
|
||||||
|
"nvr": "foo",
|
||||||
|
"password": "bar",
|
||||||
|
"port": 123,
|
||||||
|
"key": "secret",
|
||||||
|
}
|
||||||
|
|
||||||
@mock.patch("uvcclient.store.get_info_store")
|
def mock_get_camera(uuid):
|
||||||
@mock.patch("uvcclient.camera.UVCCameraClientV320")
|
"""Create a mock camera."""
|
||||||
def test_login(self, mock_camera, mock_store):
|
if uuid == "id3":
|
||||||
"""Test the login."""
|
camera_info["model"] = "airCam"
|
||||||
self.uvc._login()
|
|
||||||
assert mock_camera.call_count == 1
|
|
||||||
assert mock_camera.call_args == mock.call("host-a", "admin", "seekret")
|
|
||||||
assert mock_camera.return_value.login.call_count == 1
|
|
||||||
assert mock_camera.return_value.login.call_args == mock.call()
|
|
||||||
|
|
||||||
@mock.patch("uvcclient.store.get_info_store")
|
return camera_info
|
||||||
@mock.patch("uvcclient.camera.UVCCameraClient")
|
|
||||||
def test_login_v31x(self, mock_camera, mock_store):
|
|
||||||
"""Test login with v3.1.x server."""
|
|
||||||
self.nvr.server_version = (3, 1, 3)
|
|
||||||
self.uvc._login()
|
|
||||||
assert mock_camera.call_count == 1
|
|
||||||
assert mock_camera.call_args == mock.call("host-a", "admin", "seekret")
|
|
||||||
assert mock_camera.return_value.login.call_count == 1
|
|
||||||
assert mock_camera.return_value.login.call_args == mock.call()
|
|
||||||
|
|
||||||
@mock.patch("uvcclient.store.get_info_store")
|
mock_remote.return_value.index.return_value.append(
|
||||||
@mock.patch("uvcclient.camera.UVCCameraClientV320")
|
{"uuid": "three", "name": "Old AirCam", "id": "id3"}
|
||||||
def test_login_tries_both_addrs_and_caches(self, mock_camera, mock_store):
|
)
|
||||||
"""Test the login tries."""
|
mock_remote.return_value.get_camera.side_effect = mock_get_camera
|
||||||
responses = [0]
|
|
||||||
|
|
||||||
def mock_login(*a):
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
"""Mock login."""
|
await hass.async_block_till_done()
|
||||||
try:
|
|
||||||
responses.pop(0)
|
|
||||||
raise OSError
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
mock_store.return_value.get_camera_password.return_value = None
|
assert mock_remote.call_count == 1
|
||||||
mock_camera.return_value.login.side_effect = mock_login
|
assert mock_remote.call_args == call("foo", 123, "secret", ssl=False)
|
||||||
self.uvc._login()
|
|
||||||
assert 2 == mock_camera.call_count
|
|
||||||
assert "host-b" == self.uvc._connect_addr
|
|
||||||
|
|
||||||
mock_camera.reset_mock()
|
camera_states = hass.states.async_all("camera")
|
||||||
self.uvc._login()
|
|
||||||
assert mock_camera.call_count == 1
|
|
||||||
assert mock_camera.call_args == mock.call("host-b", "admin", "seekret")
|
|
||||||
assert mock_camera.return_value.login.call_count == 1
|
|
||||||
assert mock_camera.return_value.login.call_args == mock.call()
|
|
||||||
|
|
||||||
@mock.patch("uvcclient.store.get_info_store")
|
assert len(camera_states) == 2
|
||||||
@mock.patch("uvcclient.camera.UVCCameraClientV320")
|
|
||||||
def test_login_fails_both_properly(self, mock_camera, mock_store):
|
|
||||||
"""Test if login fails properly."""
|
|
||||||
mock_camera.return_value.login.side_effect = socket.error
|
|
||||||
assert self.uvc._login() is None
|
|
||||||
assert self.uvc._connect_addr is None
|
|
||||||
|
|
||||||
def test_camera_image_tries_login_bails_on_failure(self):
|
state = hass.states.get("camera.front")
|
||||||
"""Test retrieving failure."""
|
|
||||||
with mock.patch.object(self.uvc, "_login") as mock_login:
|
|
||||||
mock_login.return_value = False
|
|
||||||
assert self.uvc.camera_image() is None
|
|
||||||
assert mock_login.call_count == 1
|
|
||||||
assert mock_login.call_args == mock.call()
|
|
||||||
|
|
||||||
def test_camera_image_logged_in(self):
|
assert state
|
||||||
"""Test the login state."""
|
assert state.name == "Front"
|
||||||
self.uvc._camera = mock.MagicMock()
|
|
||||||
assert self.uvc._camera.get_snapshot.return_value == self.uvc.camera_image()
|
|
||||||
|
|
||||||
def test_camera_image_error(self):
|
state = hass.states.get("camera.back")
|
||||||
"""Test the camera image error."""
|
|
||||||
self.uvc._camera = mock.MagicMock()
|
|
||||||
self.uvc._camera.get_snapshot.side_effect = camera.CameraConnectError
|
|
||||||
assert self.uvc.camera_image() is None
|
|
||||||
|
|
||||||
def test_camera_image_reauths(self):
|
assert state
|
||||||
"""Test the re-authentication."""
|
assert state.name == "Back"
|
||||||
responses = [0]
|
|
||||||
|
|
||||||
def mock_snapshot():
|
entity_registry = async_get_entity_registry(hass)
|
||||||
"""Mock snapshot."""
|
entity_entry = entity_registry.async_get("camera.front")
|
||||||
try:
|
|
||||||
responses.pop()
|
|
||||||
raise camera.CameraAuthError()
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
return "image"
|
|
||||||
|
|
||||||
self.uvc._camera = mock.MagicMock()
|
assert entity_entry.unique_id == "id1"
|
||||||
self.uvc._camera.get_snapshot.side_effect = mock_snapshot
|
|
||||||
with mock.patch.object(self.uvc, "_login") as mock_login:
|
|
||||||
assert "image" == self.uvc.camera_image()
|
|
||||||
assert mock_login.call_count == 1
|
|
||||||
assert mock_login.call_args == mock.call()
|
|
||||||
assert [] == responses
|
|
||||||
|
|
||||||
def test_camera_image_reauths_only_once(self):
|
entity_entry = entity_registry.async_get("camera.back")
|
||||||
"""Test if the re-authentication only happens once."""
|
|
||||||
self.uvc._camera = mock.MagicMock()
|
assert entity_entry.unique_id == "id2"
|
||||||
self.uvc._camera.get_snapshot.side_effect = camera.CameraAuthError
|
|
||||||
with mock.patch.object(self.uvc, "_login") as mock_login:
|
|
||||||
with pytest.raises(camera.CameraAuthError):
|
async def test_setup_partial_config(hass, mock_remote):
|
||||||
self.uvc.camera_image()
|
"""Test the setup with partial configuration."""
|
||||||
assert mock_login.call_count == 1
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
assert mock_login.call_args == mock.call()
|
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_remote.call_count == 1
|
||||||
|
assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False)
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert len(camera_states) == 2
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.name == "Front"
|
||||||
|
|
||||||
|
state = hass.states.get("camera.back")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.name == "Back"
|
||||||
|
|
||||||
|
entity_registry = async_get_entity_registry(hass)
|
||||||
|
entity_entry = entity_registry.async_get("camera.front")
|
||||||
|
|
||||||
|
assert entity_entry.unique_id == "id1"
|
||||||
|
|
||||||
|
entity_entry = entity_registry.async_get("camera.back")
|
||||||
|
|
||||||
|
assert entity_entry.unique_id == "id2"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_partial_config_v31x(hass, mock_remote):
|
||||||
|
"""Test the setup with a v3.1.x server."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
mock_remote.return_value.server_version = (3, 1, 3)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_remote.call_count == 1
|
||||||
|
assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False)
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert len(camera_states) == 2
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.name == "Front"
|
||||||
|
|
||||||
|
state = hass.states.get("camera.back")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.name == "Back"
|
||||||
|
|
||||||
|
entity_registry = async_get_entity_registry(hass)
|
||||||
|
entity_entry = entity_registry.async_get("camera.front")
|
||||||
|
|
||||||
|
assert entity_entry.unique_id == "one"
|
||||||
|
|
||||||
|
entity_entry = entity_registry.async_get("camera.back")
|
||||||
|
|
||||||
|
assert entity_entry.unique_id == "two"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config",
|
||||||
|
[
|
||||||
|
{"platform": "uvc", "nvr": "foo"},
|
||||||
|
{"platform": "uvc", "key": "secret"},
|
||||||
|
{"platform": "uvc", "nvr": "foo", "key": "secret", "port": "invalid"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_setup_incomplete_config(hass, mock_remote, config):
|
||||||
|
"""Test the setup with incomplete or invalid configuration."""
|
||||||
|
assert await async_setup_component(hass, "camera", config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert not camera_states
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error, ready_states",
|
||||||
|
[
|
||||||
|
(nvr.NotAuthorized, 0),
|
||||||
|
(nvr.NvrError, 2),
|
||||||
|
(requests.exceptions.ConnectionError, 2),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_setup_nvr_errors_during_indexing(hass, mock_remote, error, ready_states):
|
||||||
|
"""Set up test for NVR errors during indexing."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
now = utcnow()
|
||||||
|
mock_remote.return_value.index.side_effect = error
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert not camera_states
|
||||||
|
|
||||||
|
# resolve the error
|
||||||
|
mock_remote.return_value.index.side_effect = None
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, now + timedelta(seconds=31))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert len(camera_states) == ready_states
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error, ready_states",
|
||||||
|
[
|
||||||
|
(nvr.NotAuthorized, 0),
|
||||||
|
(nvr.NvrError, 2),
|
||||||
|
(requests.exceptions.ConnectionError, 2),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_setup_nvr_errors_during_initialization(
|
||||||
|
hass, mock_remote, error, ready_states
|
||||||
|
):
|
||||||
|
"""Set up test for NVR errors during initialization."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
now = utcnow()
|
||||||
|
mock_remote.side_effect = error
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert not mock_remote.index.called
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert not camera_states
|
||||||
|
|
||||||
|
# resolve the error
|
||||||
|
mock_remote.side_effect = None
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, now + timedelta(seconds=31))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert len(camera_states) == ready_states
|
||||||
|
|
||||||
|
|
||||||
|
async def test_properties(hass, mock_remote):
|
||||||
|
"""Test the properties."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
camera_states = hass.states.async_all("camera")
|
||||||
|
|
||||||
|
assert len(camera_states) == 2
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.name == "Front"
|
||||||
|
assert state.state == STATE_RECORDING
|
||||||
|
assert state.attributes["brand"] == "Ubiquiti"
|
||||||
|
assert state.attributes["model_name"] == "UVC"
|
||||||
|
assert state.attributes["supported_features"] == SUPPORT_STREAM
|
||||||
|
|
||||||
|
|
||||||
|
async def test_motion_recording_mode_properties(hass, mock_remote):
|
||||||
|
"""Test the properties."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
now = utcnow()
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_RECORDING
|
||||||
|
|
||||||
|
mock_remote.return_value.get_camera.return_value["recordingSettings"][
|
||||||
|
"fullTimeRecordEnabled"
|
||||||
|
] = False
|
||||||
|
mock_remote.return_value.get_camera.return_value["recordingSettings"][
|
||||||
|
"motionRecordEnabled"
|
||||||
|
] = True
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, now + timedelta(seconds=31))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_RECORDING
|
||||||
|
assert state.attributes["last_recording_start_time"] == datetime(
|
||||||
|
2021, 1, 8, 1, 56, 32, 367000, tzinfo=timezone.utc
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_remote.return_value.get_camera.return_value["recordingIndicator"] = "DISABLED"
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, now + timedelta(seconds=61))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_RECORDING
|
||||||
|
|
||||||
|
mock_remote.return_value.get_camera.return_value[
|
||||||
|
"recordingIndicator"
|
||||||
|
] = "MOTION_INPROGRESS"
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, now + timedelta(seconds=91))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_RECORDING
|
||||||
|
|
||||||
|
mock_remote.return_value.get_camera.return_value[
|
||||||
|
"recordingIndicator"
|
||||||
|
] = "MOTION_FINISHED"
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, now + timedelta(seconds=121))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_RECORDING
|
||||||
|
|
||||||
|
|
||||||
|
async def test_stream(hass, mock_remote):
|
||||||
|
"""Test the RTSP stream URI."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
stream_source = await async_get_stream_source(hass, "camera.front")
|
||||||
|
|
||||||
|
assert stream_source == "rtsp://foo:7447/uuid_rtspchannel_0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_login(hass, mock_remote, camera_v320):
|
||||||
|
"""Test the login."""
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
image = await async_get_image(hass, "camera.front")
|
||||||
|
|
||||||
|
assert camera_v320.call_count == 1
|
||||||
|
assert camera_v320.call_args == call("host-a", "admin", "ubnt")
|
||||||
|
assert camera_v320.return_value.login.call_count == 1
|
||||||
|
assert image.content_type == DEFAULT_CONTENT_TYPE
|
||||||
|
assert image.content == "test_image"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_login_v31x(hass, mock_remote, camera_v313):
|
||||||
|
"""Test login with v3.1.x server."""
|
||||||
|
mock_remote.return_value.server_version = (3, 1, 3)
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
image = await async_get_image(hass, "camera.front")
|
||||||
|
|
||||||
|
assert camera_v313.call_count == 1
|
||||||
|
assert camera_v313.call_args == call("host-a", "admin", "ubnt")
|
||||||
|
assert camera_v313.return_value.login.call_count == 1
|
||||||
|
assert image.content_type == DEFAULT_CONTENT_TYPE
|
||||||
|
assert image.content == "test_image"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"error", [OSError, camera.CameraConnectError, camera.CameraAuthError]
|
||||||
|
)
|
||||||
|
async def test_login_tries_both_addrs_and_caches(hass, mock_remote, camera_v320, error):
|
||||||
|
"""Test the login tries."""
|
||||||
|
responses = [0]
|
||||||
|
|
||||||
|
def mock_login(*a):
|
||||||
|
"""Mock login."""
|
||||||
|
try:
|
||||||
|
responses.pop(0)
|
||||||
|
raise error
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
snapshots = [0]
|
||||||
|
|
||||||
|
def mock_snapshots(*a):
|
||||||
|
"""Mock get snapshots."""
|
||||||
|
try:
|
||||||
|
snapshots.pop(0)
|
||||||
|
raise camera.CameraAuthError()
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
return "test_image"
|
||||||
|
|
||||||
|
camera_v320.return_value.login.side_effect = mock_login
|
||||||
|
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
image = await async_get_image(hass, "camera.front")
|
||||||
|
|
||||||
|
assert camera_v320.call_count == 2
|
||||||
|
assert camera_v320.call_args == call("host-b", "admin", "ubnt")
|
||||||
|
assert image.content_type == DEFAULT_CONTENT_TYPE
|
||||||
|
assert image.content == "test_image"
|
||||||
|
|
||||||
|
camera_v320.reset_mock()
|
||||||
|
camera_v320.return_value.get_snapshot.side_effect = mock_snapshots
|
||||||
|
|
||||||
|
image = await async_get_image(hass, "camera.front")
|
||||||
|
|
||||||
|
assert camera_v320.call_count == 1
|
||||||
|
assert camera_v320.call_args == call("host-b", "admin", "ubnt")
|
||||||
|
assert camera_v320.return_value.login.call_count == 1
|
||||||
|
assert image.content_type == DEFAULT_CONTENT_TYPE
|
||||||
|
assert image.content == "test_image"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_login_fails_both_properly(hass, mock_remote, camera_v320):
|
||||||
|
"""Test if login fails properly."""
|
||||||
|
camera_v320.return_value.login.side_effect = OSError
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError):
|
||||||
|
await async_get_image(hass, "camera.front")
|
||||||
|
|
||||||
|
assert camera_v320.return_value.get_snapshot.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"source_error, raised_error, snapshot_calls",
|
||||||
|
[
|
||||||
|
(camera.CameraConnectError, HomeAssistantError, 1),
|
||||||
|
(camera.CameraAuthError, camera.CameraAuthError, 2),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_camera_image_error(
|
||||||
|
hass, mock_remote, camera_v320, source_error, raised_error, snapshot_calls
|
||||||
|
):
|
||||||
|
"""Test the camera image error."""
|
||||||
|
camera_v320.return_value.get_snapshot.side_effect = source_error
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
with pytest.raises(raised_error):
|
||||||
|
await async_get_image(hass, "camera.front")
|
||||||
|
|
||||||
|
assert camera_v320.return_value.get_snapshot.call_count == snapshot_calls
|
||||||
|
|
||||||
|
|
||||||
|
async def test_enable_disable_motion_detection(hass, mock_remote, camera_info):
|
||||||
|
"""Test enable and disable motion detection."""
|
||||||
|
|
||||||
|
def set_recordmode(uuid, mode):
|
||||||
|
"""Set record mode."""
|
||||||
|
motion_record_enabled = mode == "motion"
|
||||||
|
camera_info["recordingSettings"]["motionRecordEnabled"] = motion_record_enabled
|
||||||
|
|
||||||
|
mock_remote.return_value.set_recordmode.side_effect = set_recordmode
|
||||||
|
config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
|
||||||
|
assert await async_setup_component(hass, "camera", {"camera": config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert "motion_detection" not in state.attributes
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.attributes["motion_detection"]
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"camera", SERVICE_DISABLE_MOTION, {"entity_id": "camera.front"}, True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert "motion_detection" not in state.attributes
|
||||||
|
|
||||||
|
mock_remote.return_value.set_recordmode.side_effect = nvr.NvrError
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert "motion_detection" not in state.attributes
|
||||||
|
|
||||||
|
mock_remote.return_value.set_recordmode.side_effect = set_recordmode
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"camera", SERVICE_ENABLE_MOTION, {"entity_id": "camera.front"}, True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.attributes["motion_detection"]
|
||||||
|
|
||||||
|
mock_remote.return_value.set_recordmode.side_effect = nvr.NvrError
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"camera", SERVICE_DISABLE_MOTION, {"entity_id": "camera.front"}, True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("camera.front")
|
||||||
|
|
||||||
|
assert state
|
||||||
|
assert state.attributes["motion_detection"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user