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