Repository: fronzbot/blinkpy

Issue:
When no cameras on server, issue 'info' rather than 'warning' in log
**Describe the bug**
When a camera has no saved videos on the server, we print a 'Warning' to the log.  This really should be an 'Info' statement instead (line 165 of `blinkpy.cameras`)

**`blinkpy` version:** 0.10.0

**Log Output/Additional Information**
If using home-assistant, please paste the output of the log showing your error below.  If not, please include any additional useful information.

```
2018-10-18 15:04:24 WARNING (SyncWorker_7) [blinkpy.camera] Could not extract clip info from camera Blink 3
```


Comments:

Patch:
diff --git a/.gitignore b/.gitignore
index 0bd45aa..9a182cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ dist/*
 .sh
 build/*
 docs/_build
+*.log
diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py
index e5fe32c..7124f1a 100644
--- a/blinkpy/blinkpy.py
+++ b/blinkpy/blinkpy.py
@@ -21,7 +21,7 @@ from blinkpy.helpers.util import (
     create_session, BlinkURLHandler,
     BlinkAuthenticationException)
 from blinkpy.helpers.constants import (
-    BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL)
+    BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL, PROJECT_URL)
 
 REFRESH_RATE = 30
 
@@ -146,8 +146,7 @@ class Blink():
         if all_networks:
             _LOGGER.error(("More than one unboarded network. "
                            "Platform may not work as intended. "
-                           "Please open an issue on "
-                           "https://github.com/fronzbot/blinkpy."))
+                           "Please open an issue on %s"), PROJECT_URL)
 
     def refresh(self, force_cache=False):
         """
diff --git a/blinkpy/camera.py b/blinkpy/camera.py
index cb2666e..d36a5da 100644
--- a/blinkpy/camera.py
+++ b/blinkpy/camera.py
@@ -162,8 +162,8 @@ class BlinkCamera():
             else:
                 self.motion_detected = False
         except KeyError:
-            _LOGGER.warning("Could not extract clip info from camera %s",
-                            self.name)
+            _LOGGER.info("Could not extract clip info from camera %s",
+                         self.name)
 
     def image_to_file(self, path):
         """
@@ -187,6 +187,9 @@ class BlinkCamera():
         """
         _LOGGER.debug("Writing video from %s to %s", self.name, path)
         response = self._cached_video
+        if response is None:
+            _LOGGER.error("No saved video exist for %s.", self.name)
+            return
         with open(path, 'wb') as vidfile:
             copyfileobj(response.raw, vidfile)
 
diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py
index 23fb58d..253220e 100644
--- a/blinkpy/helpers/util.py
+++ b/blinkpy/helpers/util.py
@@ -2,7 +2,7 @@
 
 import logging
 from requests import Request, Session, exceptions
-from blinkpy.helpers.constants import BLINK_URL
+from blinkpy.helpers.constants import BLINK_URL, PROJECT_URL
 import blinkpy.helpers.errors as ERROR
 
 
@@ -17,7 +17,7 @@ def create_session():
 
 def attempt_reauthorization(blink):
     """Attempt to refresh auth token and links."""
-    _LOGGER.debug("Auth token expired, attempting reauthorization.")
+    _LOGGER.info("Auth token expired, attempting reauthorization.")
     headers = blink.get_auth_token()
     return headers
 
@@ -48,20 +48,25 @@ def http_req(blink, url='http://example.com', data=None, headers=None,
 
     try:
         response = blink.session.send(prepped, stream=stream)
+        if json_resp and 'code' in response.json():
+            if is_retry:
+                _LOGGER.error(("Cannot obtain new token for server auth. "
+                               "Please report this issue on %s"), PROJECT_URL)
+                return None
+            else:
+                headers = attempt_reauthorization(blink)
+                return http_req(blink, url=url, data=data, headers=headers,
+                                reqtype=reqtype, stream=stream,
+                                json_resp=json_resp, is_retry=True)
     except (exceptions.ConnectionError, exceptions.Timeout):
-        _LOGGER.error("Cannot connect to server. Possible outage.")
-        return None
-
-    if json_resp and 'code' in response.json():
-        if is_retry:
-            _LOGGER.error("Cannot authenticate with server.")
-            raise BlinkAuthenticationException(
-                (response.json()['code'], response.json()['message']))
-        else:
+        _LOGGER.error("Cannot connect to server with url %s.", url)
+        if not is_retry:
             headers = attempt_reauthorization(blink)
             return http_req(blink, url=url, data=data, headers=headers,
                             reqtype=reqtype, stream=stream,
                             json_resp=json_resp, is_retry=True)
+        _LOGGER.error("Possible issue with Blink servers.")
+        return None
 
     if json_resp:
         return response.json()


Test patch:
diff --git a/tests/test_api.py b/tests/test_api.py
index d50a64e..103367d 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,6 +1,7 @@
 """Test various api functions."""
 
 import unittest
+from unittest import mock
 from blinkpy import api
 from blinkpy.blinkpy import Blink
 from blinkpy.helpers.util import create_session
@@ -20,13 +21,21 @@ class TestBlinkAPI(unittest.TestCase):
         """Tear down blink module."""
         self.blink = None
 
-    def test_http_req_connect_error(self):
+    @mock.patch('blinkpy.blinkpy.Blink.get_auth_token')
+    def test_http_req_connect_error(self, mock_auth):
         """Test http_get error condition."""
-        expected = ("ERROR:blinkpy.helpers.util:"
-                    "Cannot connect to server. Possible outage.")
+        mock_auth.return_value = {'foo': 'bar'}
+        firstlog = ("ERROR:blinkpy.helpers.util:"
+                    "Cannot connect to server with url {}").format(
+                        'http://notreal.fake.')
+        nextlog = ("INFO:blinkpy.helpers.util:"
+                   "Auth token expired, attempting reauthorization.")
+        lastlog = ("ERROR:blinkpy.helpers.util:"
+                   "Possible issue with Blink servers.")
+        expected = [firstlog, nextlog, firstlog, lastlog]
         with self.assertLogs() as getlog:
             api.http_get(self.blink, 'http://notreal.fake')
         with self.assertLogs() as postlog:
             api.http_post(self.blink, 'http://notreal.fake')
-        self.assertEqual(getlog.output, [expected])
-        self.assertEqual(postlog.output, [expected])
+        self.assertEqual(getlog.output, expected)
+        self.assertEqual(postlog.output, expected)
diff --git a/tests/test_blinkpy.py b/tests/test_blinkpy.py
index 0f5dadd..577d177 100644
--- a/tests/test_blinkpy.py
+++ b/tests/test_blinkpy.py
@@ -14,6 +14,7 @@ from blinkpy.sync_module import BlinkSyncModule
 from blinkpy.helpers.util import (
     http_req, create_session, BlinkAuthenticationException,
     BlinkException, BlinkURLHandler)
+from blinkpy.helpers.constants import PROJECT_URL
 import tests.mock_responses as mresp
 
 USERNAME = 'foobar'
@@ -77,11 +78,15 @@ class TestBlinkSetup(unittest.TestCase):
     def test_bad_request(self, mock_sess):
         """Check that we raise an Exception with a bad request."""
         self.blink.session = create_session()
+        explog = ("ERROR:blinkpy.helpers.util:"
+                  "Cannot obtain new token for server auth. "
+                  "Please report this issue on {}").format(PROJECT_URL)
         with self.assertRaises(BlinkException):
             http_req(self.blink, reqtype='bad')
 
-        with self.assertRaises(BlinkAuthenticationException):
+        with self.assertLogs() as logrecord:
             http_req(self.blink, reqtype='post', is_retry=True)
+        self.assertEqual(logrecord.output, [explog])
 
     def test_authentication(self, mock_sess):
         """Check that we can authenticate Blink up properly."""