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 tests
pull/14631/head
Malte Franken 2018-06-09 15:22:17 +10:00 committed by Sebastian Muszynski
parent d3d9d9ebf2
commit f242418986
2 changed files with 81 additions and 35 deletions

View File

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

View File

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