Skip to content
This repository has been archived by the owner on Dec 11, 2023. It is now read-only.

Commit

Permalink
Merge pull request #132 from jhaegg/bugfix/github-89_value_error_on_a…
Browse files Browse the repository at this point in the history
…uth_failure

(GITHUB-89) Do not throw ValueError on authentication error during POST
  • Loading branch information
drio authored Aug 17, 2016
2 parents aacfc99 + 23d96d2 commit 72a95c7
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 4 deletions.
31 changes: 27 additions & 4 deletions librato/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,7 @@ def _process_response(self, resp, backoff):
not_a_server_error = resp.status < 500

if not_a_server_error:
body = resp.read()
if body:
resp_data = json.loads(body.decode(_getcharset(resp)))
log.info("body(<-): %s" % body)
resp_data = _decode_body(resp)
a_client_error = resp.status >= 400
if a_client_error:
raise exceptions.get(resp.status, resp_data)
Expand Down Expand Up @@ -568,6 +565,25 @@ def connect(username, api_key, hostname=HOSTNAME, base_path=BASE_PATH, sanitizer
return LibratoConnection(username, api_key, hostname, base_path, sanitizer=sanitizer, protocol=protocol)


def _decode_body(resp):
"""
Read and decode HTTPResponse body based on charset and content-type
"""
body = resp.read()
log.info("body(<-): %s" % body)
if not body:
return None

decoded_body = body.decode(_getcharset(resp))
content_type = _get_content_type(resp)

if content_type == "application/json":
resp_data = json.loads(decoded_body)
else:
resp_data = decoded_body

return resp_data

def _getcharset(resp, default='utf-8'):
"""
Extract the charset from an HTTPResponse.
Expand All @@ -581,3 +597,10 @@ def _getcharset(resp, default='utf-8'):
m = email.message.Message()
m['content-type'] = resp.getheader('content-type')
return m.get_content_charset(default)

def _get_content_type(resp):
"""
Get Content-Type header ignoring parameters
"""
parts = resp.getheader('content-type', "application/json").split(";")
return parts[0]
4 changes: 4 additions & 0 deletions tests/mock_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,12 +446,16 @@ class MockResponse(object):
def __init__(self, request, fake_failure=False):
self.request = request
self.status = 500 if fake_failure else 200
self._headers = {'content-type': "application/json;charset=utf-8"}

class headers(object):
@staticmethod
def get_content_charset(default):
return 'utf-8'

def getheader(self, name, default=None):
return self._headers.get(name.lower(), default)

def read(self):
return self._json_body_based_on_request()

Expand Down
46 changes: 46 additions & 0 deletions tests/test_process_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging
import unittest
try:
from unittest.mock import create_autospec, PropertyMock
except ImportError:
from mock import create_autospec, PropertyMock
import librato
from mock_connection import MockConnect, server
from six.moves.http_client import HTTPResponse

#logging.basicConfig(level=logging.DEBUG)
# Mock the server
librato.HTTPSConnection = MockConnect

class TestLibrato(unittest.TestCase):
def setUp(self):
self.conn = librato.connect('user_test', 'key_test')
server.clean()

def test_get_authentication_failure(self):
"""
fails with Unauthorized on 401 during GET
"""
mock_response = create_autospec(HTTPResponse, spec_set=True, instance=True)
mock_response.mock_add_spec(['status'], spec_set=True)
mock_response.status = 401
# GET 401 responds with JSON
mock_response.getheader.return_value = "application/json;charset=utf-8"
mock_response.read.return_value = '{"errors":{"request":["Authorization Required"]}}'.encode('utf-8')

with self.assertRaises(librato.exceptions.Unauthorized):
self.conn._process_response(mock_response, 1)

def test_post_authentication_failure(self):
"""
fails with Unauthorized on 401 during POST
"""
mock_response = create_autospec(HTTPResponse, spec_set=True, instance=True)
mock_response.mock_add_spec(['status'], spec_set=True)
mock_response.status = 401
# POST 401 responds with text
mock_response.getheader.return_value = "text/plain"
mock_response.read.return_value = 'Credentials are required to access this resource.'.encode('utf-8')

with self.assertRaises(librato.exceptions.Unauthorized):
self.conn._process_response(mock_response, 1)

0 comments on commit 72a95c7

Please sign in to comment.