More robust picking up JSON error responses.
Fixes issue #169. Another CL towards fixing 169, by making sure we parse the JSON error response in more cases. Reviewed in http://codereview.appspot.com/6447045/.
This commit is contained in:
@@ -41,14 +41,12 @@ class HttpError(Error):
|
|||||||
|
|
||||||
def _get_reason(self):
|
def _get_reason(self):
|
||||||
"""Calculate the reason for the error from the response content."""
|
"""Calculate the reason for the error from the response content."""
|
||||||
if self.resp.get('content-type', '').startswith('application/json'):
|
reason = self.resp.reason
|
||||||
try:
|
try:
|
||||||
data = simplejson.loads(self.content)
|
data = simplejson.loads(self.content)
|
||||||
reason = data['error']['message']
|
reason = data['error']['message']
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
reason = self.content
|
pass
|
||||||
else:
|
|
||||||
reason = self.resp.reason
|
|
||||||
return reason
|
return reason
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
@@ -45,8 +45,10 @@ JSON_ERROR_CONTENT = """
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def fake_response(data, headers):
|
def fake_response(data, headers, reason='Ok'):
|
||||||
return httplib2.Response(headers), data
|
response = httplib2.Response(headers)
|
||||||
|
response.reason = reason
|
||||||
|
return response, data
|
||||||
|
|
||||||
|
|
||||||
class Error(unittest.TestCase):
|
class Error(unittest.TestCase):
|
||||||
@@ -55,33 +57,37 @@ class Error(unittest.TestCase):
|
|||||||
def test_json_body(self):
|
def test_json_body(self):
|
||||||
"""Test a nicely formed, expected error response."""
|
"""Test a nicely formed, expected error response."""
|
||||||
resp, content = fake_response(JSON_ERROR_CONTENT,
|
resp, content = fake_response(JSON_ERROR_CONTENT,
|
||||||
{'status':'400', 'content-type': 'application/json'})
|
{'status':'400', 'content-type': 'application/json'},
|
||||||
error = HttpError(resp, content)
|
reason='Failed')
|
||||||
self.assertEqual(str(error), '<HttpError 400 "country is required">')
|
error = HttpError(resp, content, 'http://example.org')
|
||||||
|
self.assertEqual(str(error), '<HttpError 400 when requesting http://example.org returned "country is required">')
|
||||||
|
|
||||||
def test_bad_json_body(self):
|
def test_bad_json_body(self):
|
||||||
"""Test handling of bodies with invalid json."""
|
"""Test handling of bodies with invalid json."""
|
||||||
resp, content = fake_response('{',
|
resp, content = fake_response('{',
|
||||||
{'status':'400', 'content-type': 'application/json'})
|
{ 'status':'400', 'content-type': 'application/json'},
|
||||||
|
reason='Failed')
|
||||||
error = HttpError(resp, content)
|
error = HttpError(resp, content)
|
||||||
self.assertEqual(str(error), '<HttpError 400 "{">')
|
self.assertEqual(str(error), '<HttpError 400 "Failed">')
|
||||||
|
|
||||||
def test_with_uri(self):
|
def test_with_uri(self):
|
||||||
"""Test handling of passing in the request uri."""
|
"""Test handling of passing in the request uri."""
|
||||||
resp, content = fake_response('{',
|
resp, content = fake_response('{',
|
||||||
{'status':'400', 'content-type': 'application/json'})
|
{'status':'400', 'content-type': 'application/json'},
|
||||||
|
reason='Failure')
|
||||||
error = HttpError(resp, content, 'http://example.org')
|
error = HttpError(resp, content, 'http://example.org')
|
||||||
self.assertEqual(str(error), '<HttpError 400 when requesting http://example.org returned "{">')
|
self.assertEqual(str(error), '<HttpError 400 when requesting http://example.org returned "Failure">')
|
||||||
|
|
||||||
def test_missing_message_json_body(self):
|
def test_missing_message_json_body(self):
|
||||||
"""Test handling of bodies with missing expected 'message' element."""
|
"""Test handling of bodies with missing expected 'message' element."""
|
||||||
resp, content = fake_response('{}',
|
resp, content = fake_response('{}',
|
||||||
{'status':'400', 'content-type': 'application/json'})
|
{'status':'400', 'content-type': 'application/json'},
|
||||||
|
reason='Failed')
|
||||||
error = HttpError(resp, content)
|
error = HttpError(resp, content)
|
||||||
self.assertEqual(str(error), '<HttpError 400 "{}">')
|
self.assertEqual(str(error), '<HttpError 400 "Failed">')
|
||||||
|
|
||||||
def test_non_json(self):
|
def test_non_json(self):
|
||||||
"""Test handling of non-JSON bodies"""
|
"""Test handling of non-JSON bodies"""
|
||||||
resp, content = fake_response('NOT OK', {'status':'400'})
|
resp, content = fake_response('}NOT OK', {'status':'400'})
|
||||||
error = HttpError(resp, content)
|
error = HttpError(resp, content)
|
||||||
self.assertEqual(str(error), '<HttpError 400 "Ok">')
|
self.assertEqual(str(error), '<HttpError 400 "Ok">')
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ content-length: 0\r\n\r\n"""
|
|||||||
|
|
||||||
|
|
||||||
RESPONSE = """HTTP/1.1 200 OK
|
RESPONSE = """HTTP/1.1 200 OK
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
|
ETag: "etag/pony"\r\n\r\n{"answer": 42}"""
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+1>
|
Content-ID: <randomness+1>
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/pony"\r\n\r\n{"foo": 42}
|
ETag: "etag/pony"\r\n\r\n{"foo": 42}
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+2>
|
Content-ID: <randomness+2>
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
|
ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
|
||||||
--batch_foobarbaz--"""
|
--batch_foobarbaz--"""
|
||||||
@@ -387,7 +387,7 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+1>
|
Content-ID: <randomness+1>
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/pony"\r\n\r\n{"foo": 42}
|
ETag: "etag/pony"\r\n\r\n{"foo": 42}
|
||||||
|
|
||||||
@@ -397,11 +397,9 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+2>
|
Content-ID: <randomness+2>
|
||||||
|
|
||||||
HTTP/1.1 403 Access Not Configured
|
HTTP/1.1 403 Access Not Configured
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 245
|
||||||
ETag: "etag/sheep"\r\n\r\nContent-Length: 245
|
ETag: "etag/sheep"\r\n\r\n{
|
||||||
|
|
||||||
{
|
|
||||||
"error": {
|
"error": {
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
@@ -425,7 +423,7 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+1>
|
Content-ID: <randomness+1>
|
||||||
|
|
||||||
HTTP/1.1 401 Authorization Required
|
HTTP/1.1 401 Authorization Required
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/pony"\r\n\r\n{"error": {"message":
|
ETag: "etag/pony"\r\n\r\n{"error": {"message":
|
||||||
"Authorizaton failed."}}
|
"Authorizaton failed."}}
|
||||||
@@ -436,7 +434,7 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+2>
|
Content-ID: <randomness+2>
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
|
ETag: "etag/sheep"\r\n\r\n{"baz": "qux"}
|
||||||
--batch_foobarbaz--"""
|
--batch_foobarbaz--"""
|
||||||
@@ -448,7 +446,7 @@ Content-Transfer-Encoding: binary
|
|||||||
Content-ID: <randomness+1>
|
Content-ID: <randomness+1>
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type application/json
|
Content-Type: application/json
|
||||||
Content-Length: 14
|
Content-Length: 14
|
||||||
ETag: "etag/pony"\r\n\r\n{"foo": 42}
|
ETag: "etag/pony"\r\n\r\n{"foo": 42}
|
||||||
--batch_foobarbaz--"""
|
--batch_foobarbaz--"""
|
||||||
@@ -708,7 +706,7 @@ class TestBatch(unittest.TestCase):
|
|||||||
self.assertEqual({'foo': 42}, callbacks.responses['1'])
|
self.assertEqual({'foo': 42}, callbacks.responses['1'])
|
||||||
self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
|
self.assertEqual({'baz': 'qux'}, callbacks.responses['2'])
|
||||||
|
|
||||||
def test_execute_http_error(self):
|
def test_execute_batch_http_error(self):
|
||||||
callbacks = Callbacks()
|
callbacks = Callbacks()
|
||||||
batch = BatchHttpRequest(callback=callbacks.f)
|
batch = BatchHttpRequest(callback=callbacks.f)
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class Model(unittest.TestCase):
|
|||||||
content = model.response(resp, content)
|
content = model.response(resp, content)
|
||||||
self.fail('Should have thrown an exception')
|
self.fail('Should have thrown an exception')
|
||||||
except HttpError, e:
|
except HttpError, e:
|
||||||
self.assertTrue('Unauthorized' in str(e))
|
self.assertTrue('not authorized' in str(e))
|
||||||
|
|
||||||
resp['content-type'] = 'application/json'
|
resp['content-type'] = 'application/json'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user