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:
Joe Gregorio
2012-07-26 09:59:35 -04:00
parent 3fb9367a7b
commit 20b54fbefa
4 changed files with 36 additions and 34 deletions

View File

@@ -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):

View File

@@ -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">')

View File

@@ -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)

View File

@@ -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'