diff --git a/abodepy/devices/camera.py b/abodepy/devices/camera.py index 79a82bc..a4bbe6b 100644 --- a/abodepy/devices/camera.py +++ b/abodepy/devices/camera.py @@ -23,8 +23,15 @@ def __init__(self, json_obj, abode): def capture(self): """Request a new camera image.""" - url = str.replace(CONST.CAMS_ID_CAPTURE_URL, - '$DEVID$', self.device_id) + # Abode IP cameras use a different URL for image captures. + if 'control_url_snapshot' in self._json_state: + url = CONST.BASE_URL + self._json_state['control_url_snapshot'] + + elif 'control_url' in self._json_state: + url = CONST.BASE_URL + self._json_state['control_url'] + + else: + raise AbodeException((ERROR.MISSING_CONTROL_URL)) try: response = self._abode.send_request("put", url) @@ -32,6 +39,7 @@ def capture(self): _LOGGER.debug("Capture image response: %s", response.text) return True + except AbodeException as exc: _LOGGER.warning("Failed to capture image: %s", exc) diff --git a/abodepy/helpers/constants.py b/abodepy/helpers/constants.py index ec72c1f..9e5f764 100644 --- a/abodepy/helpers/constants.py +++ b/abodepy/helpers/constants.py @@ -77,9 +77,6 @@ def get_panel_mode_url(area, mode): AUTOMATION_EDIT_URL = AUTOMATION_ID_URL + 'edit' AUTOMATION_APPLY_URL = AUTOMATION_ID_URL + 'apply' -CAMS_ID_CAPTURE_URL = BASE_URL + 'api/v1/cams/$DEVID$/capture' - - TIMELINE_IMAGES_ID_URL = BASE_URL + \ 'api/v1/timeline?device_id=$DEVID$&dir=next' + \ '&event_label=Image+Capture&size=1' diff --git a/abodepy/helpers/errors.py b/abodepy/helpers/errors.py index f0a4239..1a61ce0 100644 --- a/abodepy/helpers/errors.py +++ b/abodepy/helpers/errors.py @@ -78,3 +78,6 @@ SOCKETIO_ERROR = ( 29, "SocketIO Error Packet Received") + +MISSING_CONTROL_URL = ( + 30, "Control URL does not exist in device JSON.") \ No newline at end of file diff --git a/tests/mock/devices/ipcam.py b/tests/mock/devices/ipcam.py new file mode 100644 index 0000000..2ab5ce0 --- /dev/null +++ b/tests/mock/devices/ipcam.py @@ -0,0 +1,206 @@ +"""Mock Abode IP Camera Device.""" +import abodepy.helpers.constants as CONST + +DEVICE_ID = 'ZB:00000305' +CONTROL_URL = 'api/v1/cams/' + DEVICE_ID + '/record' +CONTROL_URL_SNAPSHOT = 'api/v1/cams/' + DEVICE_ID + '/capture' + + +def device(devid=DEVICE_ID, status=CONST.STATUS_ONLINE, + low_battery=False, no_response=False): + """IP camera mock device.""" + return ''' + { + "id":"''' + devid + '''", + "type_tag": "device_type.ipcam", + "type": "IP Cam", + "name": "Living Room Camera", + "area": "1", + "zone": "1", + "sort_order": "", + "is_window": "", + "bypass": "0", + "schar_24hr": "1", + "sresp_24hr": "5", + "sresp_mode_0": "0", + "sresp_entry_0": "0", + "sresp_exit_0": "0", + "group_name": "Streaming Camera", + "group_id": "397974", + "default_group_id": "1", + "sort_id": "10000", + "sresp_mode_1": "0", + "sresp_entry_1": "0", + "sresp_exit_1": "0", + "sresp_mode_2": "0", + "sresp_entry_2": "0", + "sresp_exit_2": "0", + "sresp_mode_3": "0", + "uuid": "123456789", + "sresp_entry_3": "0", + "sresp_exit_3": "0", + "sresp_mode_4": "0", + "sresp_entry_4": "0", + "sresp_exit_4": "0", + "version": "1.0.2.22G_6.8E_homekit_2.0.9_s2 ABODE oz", + "origin": "abode", + "has_subscription": null, + "onboard": "1", + "s2_grnt_keys": "", + "s2_dsk": "", + "s2_propty": "", + "s2_keys_valid": "", + "zwave_secure_protocol": "", + "control_url":"''' + CONTROL_URL + '''", + "deep_link": null, + "status_color": "#5cb85c", + "faults": { + "low_battery":''' + str(int(low_battery)) + ''', + "tempered": 0, + "supervision": 0, + "out_of_order": 0, + "no_response":''' + str(int(no_response)) + ''', + "jammed": 0, + "zwave_fault": 0 + }, + "status":"''' + status + '''", + "status_display": "Online", + "statuses": [], + "status_ex": "", + "actions": [ + { + "label": "Capture Video", + "value": "a=1&z=1&req=vid;" + }, + { + "label": "Turn off Live Video", + "value": "a=1&z=1&privacy=on;" + }, + { + "label": "Turn on Live Video", + "value": "a=1&z=1&privacy=off;" + } + ], + "status_icons": [], + "icon": "assets\/icons\/streaming-camaera-new.svg", + "control_url_snapshot":"''' + CONTROL_URL_SNAPSHOT + '''", + "ptt_supported": true, + "is_new_camera": 1, + "stream_quality": 3, + "camera_mac": "AB:CD:EF:GF:HI", + "privacy": "1", + "enable_audio": "1", + "alarm_video": "25", + "pre_alarm_video": "5", + "mic_volume": "75", + "speaker_volume": "75", + "mic_default_volume": 40, + "speaker_default_volume": 46, + "bandwidth": { + "slider_labels": [ + { + "name": "High", + "value": 3 + }, + { + "name": "Medium", + "value": 2 + }, + { + "name": "Low", + "value": 1 + } + ], + "min": 1, + "max": 3, + "step": 1 + }, + "volume": { + "min": 0, + "max": 100, + "step": 1 + }, + "video_flip": "0", + "hframe": "1080P" + }''' + + +def get_capture_timeout(): + """Mock timeout response.""" + return ''' + { + "code":600, + "message":"Image Capture request has timed out.", + "title":"", + "detail":null + }''' + +FILE_PATH_ID = 'ZB00000305' +FILE_PATH = 'api/storage/' + FILE_PATH_ID + '/2020-01-26/173238/0.jpg' + +LOCATION_HEADER = 'https://www.google.com/images/branding/googlelogo/' + \ + '1x/googlelogo_color_272x92dp.png' + + +def timeline_event(devid=DEVICE_ID, event_code='5001', file_path=FILE_PATH): + """Camera Timeline Event Mockup.""" + return ''' + { + "mac": "B0:C5:CZ:54:12:9A", + "id": "1171272698", + "xml": null, + "date": "01/26/2020", + "time": "05:32 PM", + "event_utc": "1580088758", + "event_cid": "", + "event_code": "''' + event_code + '''", + "device_id": "''' + devid + '''", + "device_type_id": "69", + "device_type": "IP Cam", + "timeline_ha_device": null, + "d_name": "Living Room Camera", + "delete_by_user": null, + "pin_code_user": " ", + "file_del_at": "", + "nest_has_motion": null, + "nest_has_sound": null, + "nest_has_person": null, + "neaz": null, + "hasFaults": "0", + "file_path":"''' + file_path + '''", + "deep_link": null, + "file_name": "48755_b0c5ca37894b_2020-01-26_173238_0-M2+56431.jpg", + "file_size": "197207", + "file_count": "1", + "file_is_del": "0", + "event_type": "Image Capture", + "severity": "6", + "pos": "l", + "color": "#40bbea", + "is_alarm": "0", + "triggered_by_str": null, + "ha_type": null, + "ha_device_name": null, + "ha_location": null, + "ha_mobile": null, + "ha_cond": null, + "h_location": null, + "ha_trigger": null, + "icon": "assets/email/motion-camera.png", + "user_id": "95244", + "user_name": "Shred", + "mobile_name": "", + "parent_tid": "", + "app_type": "WebApp", + "viewed_by_uid": null, + "verified_by_tid": null, + "la_applied_by": null, + "la_event_type": null, + "la_culprit_mobiles": null, + "la_executed": null, + "la_applied_at": null, + "device_name": "Living Room Camera", + "event_name": "Living Room Camera Image Capture", + "event_by": "by Shred using WebApp", + "file_delete_by": "" + }''' diff --git a/tests/test_camera.py b/tests/test_camera.py index f7197ce..033df7f 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -6,281 +6,322 @@ import abodepy import abodepy.helpers.constants as CONST +import abodepy.helpers.errors as ERROR import tests.mock as MOCK +import tests.mock.devices.ipcam as IPCAM import tests.mock.devices.ir_camera as IRCAMERA import tests.mock.login as LOGIN -import tests.mock.oauth_claims as OAUTH_CLAIMS import tests.mock.logout as LOGOUT +import tests.mock.oauth_claims as OAUTH_CLAIMS import tests.mock.panel as PANEL +from abodepy.exceptions import AbodeException + +USERNAME = "foobar" +PASSWORD = "deadbeef" + + +def set_cam_type(device_type): + """Return camera type_tag.""" + if device_type == CONST.DEVICE_IP_CAM: + return IPCAM + elif device_type == CONST.DEVICE_MOTION_CAMERA: + return IRCAMERA -USERNAME = 'foobar' -PASSWORD = 'deadbeef' + return None +@requests_mock.Mocker() class TestCamera(unittest.TestCase): """Test the AbodePy camera.""" - def setUp(self): """Set up Abode module.""" - self.abode = abodepy.Abode(username=USERNAME, - password=PASSWORD, - disable_cache=True) + self.abode = abodepy.Abode( + username=USERNAME, password=PASSWORD, disable_cache=True + ) + + self.all_devices = ( + "[" + + IRCAMERA.device( + devid=IRCAMERA.DEVICE_ID, + status=CONST.STATUS_ONLINE, + low_battery=False, + no_response=False, + ) + + "," + + IPCAM.device( + devid=IPCAM.DEVICE_ID, + status=CONST.STATUS_ONLINE, + low_battery=False, + no_response=False, + ) + + "]" + ) + + # Logout to reset everything + self.abode.logout() def tearDown(self): """Clean up after test.""" self.abode = None - @requests_mock.mock() def tests_camera_properties(self, m): """Tests that camera properties work as expected.""" # Set up URL's m.post(CONST.LOGIN_URL, text=LOGIN.post_response_ok()) m.get(CONST.OAUTH_TOKEN_URL, text=OAUTH_CLAIMS.get_response_ok()) m.post(CONST.LOGOUT_URL, text=LOGOUT.post_response_ok()) - m.get(CONST.PANEL_URL, - text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) - m.get(CONST.DEVICES_URL, - text=IRCAMERA.device(devid=IRCAMERA.DEVICE_ID, - status=CONST.STATUS_ONLINE, - low_battery=False, - no_response=False)) - - # Logout to reset everything - self.abode.logout() + m.get(CONST.PANEL_URL, text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) + m.get(CONST.DEVICES_URL, text=self.all_devices) # Get our camera - device = self.abode.get_device(IRCAMERA.DEVICE_ID) - - # Test our device - self.assertIsNotNone(device) - self.assertEqual(device.status, CONST.STATUS_ONLINE) - self.assertFalse(device.battery_low) - self.assertFalse(device.no_response) - - # Set up our direct device get url - device_url = str.replace(CONST.DEVICE_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - - # Change device properties - m.get(device_url, - text=IRCAMERA.device(devid=IRCAMERA.DEVICE_ID, - status=CONST.STATUS_OFFLINE, - low_battery=True, - no_response=True)) + for device in self.abode.get_devices(): + # Skip alarm devices + if device.type_tag == CONST.DEVICE_ALARM: + continue + + # Specify which device module to use based on type_tag + cam_type = set_cam_type(device.type_tag) + + # Test our device + self.assertIsNotNone(device) + self.assertEqual(device.status, CONST.STATUS_ONLINE) + self.assertFalse(device.battery_low) + self.assertFalse(device.no_response) + + # Set up our direct device get url + device_url = str.replace(CONST.DEVICE_URL, "$DEVID$", device.device_id) + + # Change device properties + m.get( + device_url, + text=cam_type.device( + devid=cam_type.DEVICE_ID, + status=CONST.STATUS_OFFLINE, + low_battery=True, + no_response=True, + ), + ) + + # Refesh device and test changes + device.refresh() + + self.assertEqual(device.status, CONST.STATUS_OFFLINE) + self.assertTrue(device.battery_low) + self.assertTrue(device.no_response) - # Refesh device and test changes - device.refresh() - - self.assertEqual(device.status, CONST.STATUS_OFFLINE) - self.assertTrue(device.battery_low) - self.assertTrue(device.no_response) - - @requests_mock.mock() def tests_camera_capture(self, m): """Tests that camera devices capture new images.""" # Set up URL's m.post(CONST.LOGIN_URL, text=LOGIN.post_response_ok()) m.get(CONST.OAUTH_TOKEN_URL, text=OAUTH_CLAIMS.get_response_ok()) m.post(CONST.LOGOUT_URL, text=LOGOUT.post_response_ok()) - m.get(CONST.PANEL_URL, - text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) - m.get(CONST.DEVICES_URL, - text=IRCAMERA.device(devid=IRCAMERA.DEVICE_ID, - status=CONST.STATUS_ONLINE, - low_battery=False, - no_response=False)) + m.get(CONST.PANEL_URL, text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) + m.get(CONST.DEVICES_URL, text=self.all_devices) - # Logout to reset everything - self.abode.logout() + # Test our camera devices + for device in self.abode.get_devices(): + # Skip alarm devices + if device.type_tag == CONST.DEVICE_ALARM: + continue - # Get our camera - device = self.abode.get_device(IRCAMERA.DEVICE_ID) + # Specify which device module to use based on type_tag + cam_type = set_cam_type(device.type_tag) + + # Test that we have the camera devices + self.assertIsNotNone(device) + self.assertEqual(device.status, CONST.STATUS_ONLINE) + + # Determine URL based on device type + if device.type_tag == CONST.DEVICE_IP_CAM: + url = CONST.BASE_URL + cam_type.CONTROL_URL_SNAPSHOT + + elif device.type_tag == CONST.DEVICE_MOTION_CAMERA: + url = CONST.BASE_URL + cam_type.CONTROL_URL + + # Set up capture URL response + m.put(url, text=MOCK.generic_response_ok()) - # Test that we have our device - self.assertIsNotNone(device) - self.assertEqual(device.status, CONST.STATUS_ONLINE) + # Capture an image + self.assertTrue(device.capture()) - # Set up capture url response - url = str.replace(CONST.CAMS_ID_CAPTURE_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - m.put(url, text=MOCK.generic_response_ok()) + # Change capture URL responses + m.put(url, text=cam_type.get_capture_timeout(), status_code=600) - # Capture the image - self.assertTrue(device.capture()) + # Capture an image with a failure + self.assertFalse(device.capture()) - # Change response - m.put(url, text=IRCAMERA.get_capture_timeout(), status_code=600) + # Remove control URLs from JSON to test if Abode makes changes to JSON + for key in list(device._json_state.keys()): + if key.startswith("control_url"): + del device._json_state[key] - # Capture the image with failure - self.assertFalse(device.capture()) + # Test that AbodeException is raised with no control URLs + with self.assertRaises(AbodeException) as exc: + device.capture() + self.assertEqual(str(exc.exception), ERROR.MISSING_CONTROL_URL) - @requests_mock.mock() def tests_camera_image_update(self, m): """Tests that camera devices update correctly via timeline request.""" # Set up URL's m.post(CONST.LOGIN_URL, text=LOGIN.post_response_ok()) m.get(CONST.OAUTH_TOKEN_URL, text=OAUTH_CLAIMS.get_response_ok()) m.post(CONST.LOGOUT_URL, text=LOGOUT.post_response_ok()) - m.get(CONST.PANEL_URL, - text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) - m.get(CONST.DEVICES_URL, - text=IRCAMERA.device(devid=IRCAMERA.DEVICE_ID, - status=CONST.STATUS_ONLINE, - low_battery=False, - no_response=False)) + m.get(CONST.PANEL_URL, text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) + m.get(CONST.DEVICES_URL, text=self.all_devices) + + # Test our camera devices + for device in self.abode.get_devices(): + # Skip alarm devices + if device.type_tag == CONST.DEVICE_ALARM: + continue + + # Specify which device module to use based on type_tag + cam_type = set_cam_type(device.type_tag) + + # Test that we have our device + self.assertIsNotNone(device) + self.assertEqual(device.status, CONST.STATUS_ONLINE) + + # Set up timeline response + url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, "$DEVID$", device.device_id) + + m.get(url, text="[" + cam_type.timeline_event(device.device_id) + "]") + # Set up our file path response + file_path = CONST.BASE_URL + cam_type.FILE_PATH + m.head( + file_path, + status_code=302, + headers={"Location": cam_type.LOCATION_HEADER}, + ) + + # Refresh the image + self.assertTrue(device.refresh_image()) + + # Verify the image location + self.assertEqual(device.image_url, cam_type.LOCATION_HEADER) + + # Test that a bad file_path response header results in an exception + file_path = CONST.BASE_URL + cam_type.FILE_PATH + m.head(file_path, status_code=302) + + with self.assertRaises(abodepy.AbodeException): + device.refresh_image() + + # Test that a bad file_path response code results in an exception + file_path = CONST.BASE_URL + cam_type.FILE_PATH + m.head( + file_path, + status_code=200, + headers={"Location": cam_type.LOCATION_HEADER}, + ) + + with self.assertRaises(abodepy.AbodeException): + device.refresh_image() + + # Test that an an empty timeline event throws exception + url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, "$DEVID$", device.device_id) + m.get( + url, + text="[" + + cam_type.timeline_event(device.device_id, file_path="") + + "]", + ) + + with self.assertRaises(abodepy.AbodeException): + device.refresh_image() + + # Test that an unexpected timeline event throws exception + url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, "$DEVID$", device.device_id) + m.get( + url, + text="[" + + cam_type.timeline_event(device.device_id, event_code="1234") + + "]", + ) + + with self.assertRaises(abodepy.AbodeException): + device.refresh_image() - # Logout to reset everything - self.abode.logout() - - # Get our camera - device = self.abode.get_device(IRCAMERA.DEVICE_ID) - - # Test that we have our device - self.assertIsNotNone(device) - self.assertEqual(device.status, CONST.STATUS_ONLINE) - - # Set up timeline response - url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - m.get(url, text='[' + - IRCAMERA.timeline_event(IRCAMERA.DEVICE_ID) + ']') - - # Set up our file path response - file_path = CONST.BASE_URL + IRCAMERA.FILE_PATH - m.head(file_path, - status_code=302, headers={'Location': IRCAMERA.LOCATION_HEADER}) - - # Refresh the image - self.assertTrue(device.refresh_image()) - - # Verify the image location - self.assertEqual(device.image_url, IRCAMERA.LOCATION_HEADER) - - # Test that a bad file_path response header results in an exception - file_path = CONST.BASE_URL + IRCAMERA.FILE_PATH - m.head(file_path, - status_code=302) - - with self.assertRaises(abodepy.AbodeException): - device.refresh_image() - - # Test that a bad file_path response code results in an exception - file_path = CONST.BASE_URL + IRCAMERA.FILE_PATH - m.head(file_path, - status_code=200, headers={'Location': IRCAMERA.LOCATION_HEADER}) - - with self.assertRaises(abodepy.AbodeException): - device.refresh_image() - - # Test that an an empty timeline event throws exception - url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - m.get(url, text='[' + - IRCAMERA.timeline_event(IRCAMERA.DEVICE_ID, file_path='') + - ']') - - with self.assertRaises(abodepy.AbodeException): - device.refresh_image() - - # Test that an unexpected timeline event throws exception - url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - m.get(url, text='[' + - IRCAMERA.timeline_event(IRCAMERA.DEVICE_ID, event_code='1234') + - ']') - - with self.assertRaises(abodepy.AbodeException): - device.refresh_image() - - @requests_mock.mock() def tests_camera_no_image_update(self, m): """Tests that camera updates correctly with no timeline events.""" # Set up URL's m.post(CONST.LOGIN_URL, text=LOGIN.post_response_ok()) m.get(CONST.OAUTH_TOKEN_URL, text=OAUTH_CLAIMS.get_response_ok()) m.post(CONST.LOGOUT_URL, text=LOGOUT.post_response_ok()) - m.get(CONST.PANEL_URL, - text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) - m.get(CONST.DEVICES_URL, - text=IRCAMERA.device(devid=IRCAMERA.DEVICE_ID, - status=CONST.STATUS_ONLINE, - low_battery=False, - no_response=False)) + m.get(CONST.PANEL_URL, text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) + m.get(CONST.DEVICES_URL, text=self.all_devices) - # Logout to reset everything - self.abode.logout() + # Test our camera devices + for device in self.abode.get_devices(): + # Skip alarm devices + if device.type_tag == CONST.DEVICE_ALARM: + continue - # Get our camera - device = self.abode.get_device(IRCAMERA.DEVICE_ID) + # Test that we have our device + self.assertIsNotNone(device) + self.assertEqual(device.status, CONST.STATUS_ONLINE) - # Test that we have our device - self.assertIsNotNone(device) - self.assertEqual(device.status, CONST.STATUS_ONLINE) + # Set up timeline response + url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, "$DEVID$", device.device_id) + m.get(url, text="[]") - # Set up timeline response - url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - m.get(url, text='[]') + # Refresh the image + self.assertFalse(device.refresh_image()) + self.assertIsNone(device.image_url) - # Refresh the image - self.assertFalse(device.refresh_image()) - self.assertIsNone(device.image_url) - - @requests_mock.mock() def tests_camera_image_write(self, m): """Tests that camera images will write to a file.""" # Set up URL's m.post(CONST.LOGIN_URL, text=LOGIN.post_response_ok()) m.get(CONST.OAUTH_TOKEN_URL, text=OAUTH_CLAIMS.get_response_ok()) m.post(CONST.LOGOUT_URL, text=LOGOUT.post_response_ok()) - m.get(CONST.PANEL_URL, - text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) - m.get(CONST.DEVICES_URL, - text=IRCAMERA.device(devid=IRCAMERA.DEVICE_ID, - status=CONST.STATUS_ONLINE, - low_battery=False, - no_response=False)) - - # Logout to reset everything - self.abode.logout() - - # Get our camera - device = self.abode.get_device(IRCAMERA.DEVICE_ID) - - # Test that we have our device - self.assertIsNotNone(device) - self.assertEqual(device.status, CONST.STATUS_ONLINE) - - # Set up timeline response - url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, - '$DEVID$', IRCAMERA.DEVICE_ID) - m.get(url, text='[' + - IRCAMERA.timeline_event(IRCAMERA.DEVICE_ID) + ']') - - # Set up our file path response - file_path = CONST.BASE_URL + IRCAMERA.FILE_PATH - m.head(file_path, - status_code=302, headers={'Location': IRCAMERA.LOCATION_HEADER}) - - # Set up our image response - image_response = "this is a beautiful jpeg image" - m.get(IRCAMERA.LOCATION_HEADER, text=image_response) - - # Refresh the image - path = "test.jpg" - self.assertTrue(device.image_to_file(path, get_image=True)) - - # Test the file written and cleanup - image_data = open(path, 'r').read() - self.assertTrue(image_response, image_data) - os.remove(path) - - # Test that bad response returns False - m.get(IRCAMERA.LOCATION_HEADER, status_code=400) - with self.assertRaises(abodepy.AbodeException): - device.image_to_file(path, get_image=True) - - # Test that the image fails to update returns False - m.get(url, text='[]') - self.assertFalse(device.image_to_file(path, get_image=True)) + m.get(CONST.PANEL_URL, text=PANEL.get_response_ok(mode=CONST.MODE_STANDBY)) + m.get(CONST.DEVICES_URL, text=self.all_devices) + + # Test our camera devices + for device in self.abode.get_devices(): + # Skip alarm devices + if device.type_tag == CONST.DEVICE_ALARM: + continue + + # Specify which device module to use based on type_tag + cam_type = set_cam_type(device.type_tag) + + # Test that we have our device + self.assertIsNotNone(device) + self.assertEqual(device.status, CONST.STATUS_ONLINE) + + # Set up timeline response + url = str.replace(CONST.TIMELINE_IMAGES_ID_URL, "$DEVID$", device.device_id) + m.get(url, text="[" + cam_type.timeline_event(device.device_id) + "]") + + # Set up our file path response + file_path = CONST.BASE_URL + cam_type.FILE_PATH + m.head( + file_path, + status_code=302, + headers={"Location": cam_type.LOCATION_HEADER}, + ) + + # Set up our image response + image_response = "this is a beautiful jpeg image" + m.get(cam_type.LOCATION_HEADER, text=image_response) + + # Refresh the image + path = "test.jpg" + self.assertTrue(device.image_to_file(path, get_image=True)) + + # Test the file written and cleanup + image_data = open(path, "r").read() + self.assertTrue(image_response, image_data) + os.remove(path) + + # Test that bad response returns False + m.get(cam_type.LOCATION_HEADER, status_code=400) + with self.assertRaises(abodepy.AbodeException): + device.image_to_file(path, get_image=True) + + # Test that the image fails to update returns False + m.get(url, text="[]") + self.assertFalse(device.image_to_file(path, get_image=True))