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:
sycx2 2021-03-19 15:54:07 +01:00 committed by GitHub
parent e798f415a4
commit bc0eb9bf32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 565 additions and 343 deletions

View File

@ -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

View File

@ -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"]