UVC camera platform handling unavailable NVR or cameras better (#14864)
* fixed tests: using correct camera configuration now and error handling tests must be separated out to ensure that the setup_component call is actually executed * better error handling during setup; raising PlatformNotReady in likely recoverable cases; added testspull/14631/head
parent
d3d9d9ebf2
commit
f242418986
|
@ -13,6 +13,7 @@ import voluptuous as vol
|
|||
from homeassistant.const import CONF_PORT
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
|
||||
REQUIREMENTS = ['uvcclient==0.10.1']
|
||||
|
||||
|
@ -41,25 +42,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||
port = config[CONF_PORT]
|
||||
|
||||
from uvcclient import nvr
|
||||
nvrconn = nvr.UVCRemote(addr, port, key)
|
||||
try:
|
||||
# Exceptions may be raised in all method calls to the nvr library.
|
||||
nvrconn = nvr.UVCRemote(addr, port, key)
|
||||
cameras = nvrconn.index()
|
||||
|
||||
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
|
||||
# Filter out airCam models, which are not supported in the latest
|
||||
# version of UnifiVideo and which are EOL by Ubiquiti
|
||||
cameras = [
|
||||
camera for camera in cameras
|
||||
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
|
||||
except nvr.NotAuthorized:
|
||||
_LOGGER.error("Authorization failure while connecting to NVR")
|
||||
return False
|
||||
except nvr.NvrError:
|
||||
_LOGGER.error("NVR refuses to talk to me")
|
||||
return False
|
||||
except nvr.NvrError as ex:
|
||||
_LOGGER.error("NVR refuses to talk to me: %s", str(ex))
|
||||
raise PlatformNotReady
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
|
||||
return False
|
||||
|
||||
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
|
||||
# Filter out airCam models, which are not supported in the latest
|
||||
# version of UnifiVideo and which are EOL by Ubiquiti
|
||||
cameras = [
|
||||
camera for camera in cameras
|
||||
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
|
||||
raise PlatformNotReady
|
||||
|
||||
add_devices([UnifiVideoCamera(nvrconn,
|
||||
camera[identifier],
|
||||
|
|
|
@ -7,6 +7,7 @@ import requests
|
|||
from uvcclient import camera
|
||||
from uvcclient import nvr
|
||||
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.components.camera import uvc
|
||||
from tests.common import get_test_home_assistant
|
||||
|
@ -34,21 +35,21 @@ class TestUVCSetup(unittest.TestCase):
|
|||
'port': 123,
|
||||
'key': 'secret',
|
||||
}
|
||||
fake_cameras = [
|
||||
mock_cameras = [
|
||||
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
||||
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
||||
{'uuid': 'three', 'name': 'Old AirCam', 'id': 'id3'},
|
||||
]
|
||||
|
||||
def fake_get_camera(uuid):
|
||||
"""Create a fake camera."""
|
||||
def mock_get_camera(uuid):
|
||||
"""Create a mock camera."""
|
||||
if uuid == 'id3':
|
||||
return {'model': 'airCam'}
|
||||
else:
|
||||
return {'model': 'UVC'}
|
||||
|
||||
mock_remote.return_value.index.return_value = fake_cameras
|
||||
mock_remote.return_value.get_camera.side_effect = fake_get_camera
|
||||
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)
|
||||
|
||||
assert setup_component(self.hass, 'camera', {'camera': config})
|
||||
|
@ -71,11 +72,11 @@ class TestUVCSetup(unittest.TestCase):
|
|||
'nvr': 'foo',
|
||||
'key': 'secret',
|
||||
}
|
||||
fake_cameras = [
|
||||
mock_cameras = [
|
||||
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
||||
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
||||
]
|
||||
mock_remote.return_value.index.return_value = fake_cameras
|
||||
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)
|
||||
|
||||
|
@ -99,11 +100,11 @@ class TestUVCSetup(unittest.TestCase):
|
|||
'nvr': 'foo',
|
||||
'key': 'secret',
|
||||
}
|
||||
fake_cameras = [
|
||||
mock_cameras = [
|
||||
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
||||
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
||||
]
|
||||
mock_remote.return_value.index.return_value = fake_cameras
|
||||
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)
|
||||
|
||||
|
@ -133,19 +134,62 @@ class TestUVCSetup(unittest.TestCase):
|
|||
|
||||
@mock.patch.object(uvc, 'UnifiVideoCamera')
|
||||
@mock.patch('uvcclient.nvr.UVCRemote')
|
||||
def test_setup_nvr_errors(self, mock_remote, mock_uvc):
|
||||
"""Test for NVR errors."""
|
||||
errors = [nvr.NotAuthorized, nvr.NvrError,
|
||||
requests.exceptions.ConnectionError]
|
||||
def setup_nvr_errors_during_indexing(self, error, mock_remote, mock_uvc):
|
||||
"""Setup test for NVR errors during indexing."""
|
||||
config = {
|
||||
'platform': 'uvc',
|
||||
'nvr': 'foo',
|
||||
'key': 'secret',
|
||||
}
|
||||
for error in errors:
|
||||
mock_remote.return_value.index.side_effect = error
|
||||
assert setup_component(self.hass, 'camera', config)
|
||||
assert not mock_uvc.called
|
||||
mock_remote.return_value.index.side_effect = error
|
||||
assert setup_component(self.hass, 'camera', {'camera': config})
|
||||
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)
|
||||
self.assertRaises(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)
|
||||
self.assertRaises(PlatformNotReady)
|
||||
|
||||
@mock.patch.object(uvc, 'UnifiVideoCamera')
|
||||
@mock.patch('uvcclient.nvr.UVCRemote.__init__')
|
||||
def setup_nvr_errors_during_initialization(self, error, mock_remote,
|
||||
mock_uvc):
|
||||
"""Setup 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})
|
||||
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)
|
||||
self.assertRaises(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)
|
||||
self.assertRaises(PlatformNotReady)
|
||||
|
||||
|
||||
class TestUVC(unittest.TestCase):
|
||||
|
@ -208,8 +252,8 @@ class TestUVC(unittest.TestCase):
|
|||
"""Test the login tries."""
|
||||
responses = [0]
|
||||
|
||||
def fake_login(*a):
|
||||
"""Fake login."""
|
||||
def mock_login(*a):
|
||||
"""Mock login."""
|
||||
try:
|
||||
responses.pop(0)
|
||||
raise socket.error
|
||||
|
@ -217,7 +261,7 @@ class TestUVC(unittest.TestCase):
|
|||
pass
|
||||
|
||||
mock_store.return_value.get_camera_password.return_value = None
|
||||
mock_camera.return_value.login.side_effect = fake_login
|
||||
mock_camera.return_value.login.side_effect = mock_login
|
||||
self.uvc._login()
|
||||
self.assertEqual(2, mock_camera.call_count)
|
||||
self.assertEqual('host-b', self.uvc._connect_addr)
|
||||
|
@ -263,8 +307,8 @@ class TestUVC(unittest.TestCase):
|
|||
"""Test the re-authentication."""
|
||||
responses = [0]
|
||||
|
||||
def fake_snapshot():
|
||||
"""Fake snapshot."""
|
||||
def mock_snapshot():
|
||||
"""Mock snapshot."""
|
||||
try:
|
||||
responses.pop()
|
||||
raise camera.CameraAuthError()
|
||||
|
@ -273,7 +317,7 @@ class TestUVC(unittest.TestCase):
|
|||
return 'image'
|
||||
|
||||
self.uvc._camera = mock.MagicMock()
|
||||
self.uvc._camera.get_snapshot.side_effect = fake_snapshot
|
||||
self.uvc._camera.get_snapshot.side_effect = mock_snapshot
|
||||
with mock.patch.object(self.uvc, '_login') as mock_login:
|
||||
self.assertEqual('image', self.uvc.camera_image())
|
||||
self.assertEqual(mock_login.call_count, 1)
|
||||
|
|
Loading…
Reference in New Issue