Skip to content

Commit 4785201

Browse files
committed
Merge pull request #756 from dhermes/oauth2client-use-master
Making regression3 pass
2 parents 6d08741 + 73bb5bb commit 4785201

File tree

12 files changed

+142
-44
lines changed

12 files changed

+142
-44
lines changed

gcloud/connection.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import json
1818
from pkg_resources import get_distribution
19+
import six
1920
from six.moves.urllib.parse import urlencode # pylint: disable=F0401
2021

2122
import httplib2
@@ -295,6 +296,8 @@ def api_request(self, method, path, query_params=None,
295296
content_type = response.get('content-type', '')
296297
if not content_type.startswith('application/json'):
297298
raise TypeError('Expected JSON, got %s' % content_type)
299+
if isinstance(content, six.binary_type):
300+
content = content.decode('utf-8')
298301
return json.loads(content)
299302

300303
return content

gcloud/credentials.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ def _get_signed_query_params(credentials, expiration, signature_string):
181181
pem_key = _get_pem_key(credentials)
182182
# Sign the string with the RSA key.
183183
signer = PKCS1_v1_5.new(pem_key)
184+
if not isinstance(signature_string, six.binary_type):
185+
signature_string = signature_string.encode('utf-8')
184186
signature_hash = SHA256.new(signature_string)
185187
signature_bytes = signer.sign(signature_hash)
186188
signature = base64.b64encode(signature_bytes)

gcloud/datastore/test_connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def test__request_not_200(self):
152152
METHOD = 'METHOD'
153153
DATA = 'DATA'
154154
conn = self._makeOne()
155-
conn._http = Http({'status': '400'}, 'Entity value is indexed.')
155+
conn._http = Http({'status': '400'}, b'Entity value is indexed.')
156156
with self.assertRaises(BadRequest) as e:
157157
conn._request(DATASET_ID, METHOD, DATA)
158158
expected_message = '400 Entity value is indexed.'

gcloud/exceptions.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919

2020
import json
21+
import six
2122

2223
_HTTP_CODE_TO_EXCEPTION = {} # populated at end of module
2324

@@ -171,18 +172,18 @@ def make_exception(response, content, use_json=True):
171172
:rtype: instance of :class:`GCloudError`, or a concrete subclass.
172173
:returns: Exception specific to the error response.
173174
"""
174-
message = content
175-
errors = ()
175+
if isinstance(content, six.binary_type):
176+
content = content.decode('utf-8')
176177

177-
if isinstance(content, str):
178+
if isinstance(content, six.string_types):
178179
if use_json:
179180
payload = json.loads(content)
180181
else:
181-
payload = {}
182+
payload = {'message': content}
182183
else:
183184
payload = content
184185

185-
message = payload.get('message', message)
186+
message = payload.get('message', '')
186187
errors = payload.get('error', {}).get('errors', ())
187188

188189
try:

gcloud/storage/batch.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,25 @@ def __exit__(self, exc_type, exc_val, exc_tb):
170170
def _unpack_batch_response(response, content):
171171
"""Convert response, content -> [(status, reason, payload)]."""
172172
parser = Parser()
173-
faux_message = ('Content-Type: %s\nMIME-Version: 1.0\n\n%s' %
174-
(response['content-type'], content))
175-
176-
message = parser.parsestr(faux_message)
173+
# We coerce to bytes to get consitent concat across
174+
# Py2 and Py3. Percent formatting is insufficient since
175+
# it includes the b in Py3.
176+
if not isinstance(content, six.binary_type):
177+
content = content.encode('utf-8')
178+
content_type = response['content-type']
179+
if not isinstance(content_type, six.binary_type):
180+
content_type = content_type.encode('utf-8')
181+
faux_message = b''.join([
182+
b'Content-Type: ',
183+
content_type,
184+
b'\nMIME-Version: 1.0\n\n',
185+
content,
186+
])
187+
188+
if six.PY2:
189+
message = parser.parsestr(faux_message)
190+
else: # pragma: NO COVER Python3
191+
message = parser.parsestr(faux_message.decode('utf-8'))
177192

178193
if not isinstance(message._payload, list):
179194
raise ValueError('Bad response: not multi-part')

gcloud/storage/test_api.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_miss(self):
3434
])
3535
http = conn._http = Http(
3636
{'status': '404', 'content-type': 'application/json'},
37-
'{}',
37+
b'{}',
3838
)
3939
bucket = self._callFUT(NONESUCH, connection=conn)
4040
self.assertEqual(bucket, None)
@@ -56,7 +56,7 @@ def _lookup_bucket_hit_helper(self, use_default=False):
5656
])
5757
http = conn._http = Http(
5858
{'status': '200', 'content-type': 'application/json'},
59-
'{"name": "%s"}' % BLOB_NAME,
59+
'{{"name": "{0}"}}'.format(BLOB_NAME).encode('utf-8'),
6060
)
6161

6262
if use_default:
@@ -96,7 +96,7 @@ def test_empty(self):
9696
])
9797
http = conn._http = Http(
9898
{'status': '200', 'content-type': 'application/json'},
99-
'{}',
99+
b'{}',
100100
)
101101
buckets = list(self._callFUT(PROJECT, conn))
102102
self.assertEqual(len(buckets), 0)
@@ -117,7 +117,8 @@ def _get_all_buckets_non_empty_helper(self, project, use_default=False):
117117
])
118118
http = conn._http = Http(
119119
{'status': '200', 'content-type': 'application/json'},
120-
'{"items": [{"name": "%s"}]}' % BUCKET_NAME,
120+
'{{"items": [{{"name": "{0}"}}]}}'.format(BUCKET_NAME)
121+
.encode('utf-8'),
121122
)
122123

123124
if use_default:
@@ -159,7 +160,7 @@ def test_miss(self):
159160
])
160161
http = conn._http = Http(
161162
{'status': '404', 'content-type': 'application/json'},
162-
'{}',
163+
b'{}',
163164
)
164165
self.assertRaises(NotFound, self._callFUT, NONESUCH, connection=conn)
165166
self.assertEqual(http._called_with['method'], 'GET')
@@ -180,7 +181,7 @@ def _get_bucket_hit_helper(self, use_default=False):
180181
])
181182
http = conn._http = Http(
182183
{'status': '200', 'content-type': 'application/json'},
183-
'{"name": "%s"}' % BLOB_NAME,
184+
'{{"name": "{0}"}}'.format(BLOB_NAME).encode('utf-8'),
184185
)
185186

186187
if use_default:
@@ -224,7 +225,7 @@ def _create_bucket_success_helper(self, project, use_default=False):
224225
])
225226
http = conn._http = Http(
226227
{'status': '200', 'content-type': 'application/json'},
227-
'{"name": "%s"}' % BLOB_NAME,
228+
'{{"name": "{0}"}}'.format(BLOB_NAME).encode('utf-8'),
228229
)
229230

230231
if use_default:

gcloud/storage/test_batch.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,32 @@ def test_as_context_mgr_w_error(self):
347347
self.assertEqual(len(batch._responses), 0)
348348

349349

350-
_THREE_PART_MIME_RESPONSE = """\
350+
class Test__unpack_batch_response(unittest2.TestCase):
351+
352+
def _callFUT(self, response, content):
353+
from gcloud.storage.batch import _unpack_batch_response
354+
return _unpack_batch_response(response, content)
355+
356+
def test_bytes(self):
357+
RESPONSE = {'content-type': b'multipart/mixed; boundary="DEADBEEF="'}
358+
CONTENT = _THREE_PART_MIME_RESPONSE
359+
result = list(self._callFUT(RESPONSE, CONTENT))
360+
self.assertEqual(len(result), 3)
361+
self.assertEqual(result[0], ('200', 'OK', {u'bar': 2, u'foo': 1}))
362+
self.assertEqual(result[1], ('200', 'OK', {u'foo': 1, u'bar': 3}))
363+
self.assertEqual(result[2], ('204', 'No Content', ''))
364+
365+
def test_unicode(self):
366+
RESPONSE = {'content-type': u'multipart/mixed; boundary="DEADBEEF="'}
367+
CONTENT = _THREE_PART_MIME_RESPONSE.decode('utf-8')
368+
result = list(self._callFUT(RESPONSE, CONTENT))
369+
self.assertEqual(len(result), 3)
370+
self.assertEqual(result[0], ('200', 'OK', {u'bar': 2, u'foo': 1}))
371+
self.assertEqual(result[1], ('200', 'OK', {u'foo': 1, u'bar': 3}))
372+
self.assertEqual(result[2], ('204', 'No Content', ''))
373+
374+
375+
_THREE_PART_MIME_RESPONSE = b"""\
351376
--DEADBEEF=
352377
Content-Type: application/http
353378
Content-ID: <response-8a09ca85-8d1d-4f45-9eb0-da8e8b07ec83+1>

gcloud/test_connection.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,12 @@ def test__make_request_no_data_no_content_type_no_headers(self):
161161
URI = 'http://example.com/test'
162162
http = conn._http = _Http(
163163
{'status': '200', 'content-type': 'text/plain'},
164-
'',
164+
b'',
165165
)
166166
headers, content = conn._make_request('GET', URI)
167167
self.assertEqual(headers['status'], '200')
168168
self.assertEqual(headers['content-type'], 'text/plain')
169-
self.assertEqual(content, '')
169+
self.assertEqual(content, b'')
170170
self.assertEqual(http._called_with['method'], 'GET')
171171
self.assertEqual(http._called_with['uri'], URI)
172172
self.assertEqual(http._called_with['body'], None)
@@ -182,7 +182,7 @@ def test__make_request_w_data_no_extra_headers(self):
182182
URI = 'http://example.com/test'
183183
http = conn._http = _Http(
184184
{'status': '200', 'content-type': 'text/plain'},
185-
'',
185+
b'',
186186
)
187187
conn._make_request('GET', URI, {}, 'application/json')
188188
self.assertEqual(http._called_with['method'], 'GET')
@@ -201,7 +201,7 @@ def test__make_request_w_extra_headers(self):
201201
URI = 'http://example.com/test'
202202
http = conn._http = _Http(
203203
{'status': '200', 'content-type': 'text/plain'},
204-
'',
204+
b'',
205205
)
206206
conn._make_request('GET', URI, headers={'X-Foo': 'foo'})
207207
self.assertEqual(http._called_with['method'], 'GET')
@@ -226,7 +226,7 @@ def test_api_request_defaults(self):
226226
])
227227
http = conn._http = _Http(
228228
{'status': '200', 'content-type': 'application/json'},
229-
'{}',
229+
b'{}',
230230
)
231231
self.assertEqual(conn.api_request('GET', PATH), {})
232232
self.assertEqual(http._called_with['method'], 'GET')
@@ -243,7 +243,7 @@ def test_api_request_w_non_json_response(self):
243243
conn = self._makeMockOne()
244244
conn._http = _Http(
245245
{'status': '200', 'content-type': 'text/plain'},
246-
'CONTENT',
246+
b'CONTENT',
247247
)
248248

249249
self.assertRaises(TypeError, conn.api_request, 'GET', '/')
@@ -252,18 +252,18 @@ def test_api_request_wo_json_expected(self):
252252
conn = self._makeMockOne()
253253
conn._http = _Http(
254254
{'status': '200', 'content-type': 'text/plain'},
255-
'CONTENT',
255+
b'CONTENT',
256256
)
257257
self.assertEqual(conn.api_request('GET', '/', expect_json=False),
258-
'CONTENT')
258+
b'CONTENT')
259259

260260
def test_api_request_w_query_params(self):
261261
from six.moves.urllib.parse import parse_qsl
262262
from six.moves.urllib.parse import urlsplit
263263
conn = self._makeMockOne()
264264
http = conn._http = _Http(
265265
{'status': '200', 'content-type': 'application/json'},
266-
'{}',
266+
b'{}',
267267
)
268268
self.assertEqual(conn.api_request('GET', '/', {'foo': 'bar'}), {})
269269
self.assertEqual(http._called_with['method'], 'GET')
@@ -302,7 +302,7 @@ def test_api_request_w_data(self):
302302
])
303303
http = conn._http = _Http(
304304
{'status': '200', 'content-type': 'application/json'},
305-
'{}',
305+
b'{}',
306306
)
307307
self.assertEqual(conn.api_request('POST', '/', data=DATA), {})
308308
self.assertEqual(http._called_with['method'], 'POST')
@@ -321,7 +321,7 @@ def test_api_request_w_404(self):
321321
conn = self._makeMockOne()
322322
conn._http = _Http(
323323
{'status': '404', 'content-type': 'text/plain'},
324-
'{}'
324+
b'{}'
325325
)
326326
self.assertRaises(NotFound, conn.api_request, 'GET', '/')
327327

@@ -330,10 +330,35 @@ def test_api_request_w_500(self):
330330
conn = self._makeMockOne()
331331
conn._http = _Http(
332332
{'status': '500', 'content-type': 'text/plain'},
333-
'{}',
333+
b'{}',
334334
)
335335
self.assertRaises(InternalServerError, conn.api_request, 'GET', '/')
336336

337+
def test_api_request_non_binary_response(self):
338+
conn = self._makeMockOne()
339+
http = conn._http = _Http(
340+
{'status': '200', 'content-type': 'application/json'},
341+
u'{}',
342+
)
343+
result = conn.api_request('GET', '/')
344+
# Intended to emulate self.mock_template
345+
URI = '/'.join([
346+
conn.API_BASE_URL,
347+
'mock',
348+
conn.API_VERSION,
349+
'',
350+
])
351+
self.assertEqual(result, {})
352+
self.assertEqual(http._called_with['method'], 'GET')
353+
self.assertEqual(http._called_with['uri'], URI)
354+
self.assertEqual(http._called_with['body'], None)
355+
expected_headers = {
356+
'Accept-Encoding': 'gzip',
357+
'Content-Length': 0,
358+
'User-Agent': conn.USER_AGENT,
359+
}
360+
self.assertEqual(http._called_with['headers'], expected_headers)
361+
337362

338363
class _Http(object):
339364

gcloud/test_credentials.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,13 @@ def _get_pem_key(credentials):
176176
SIGNATURE_STRING = 'dummy_signature'
177177
with _Monkey(MUT, RSA=rsa, PKCS1_v1_5=pkcs_v1_5,
178178
SHA256=sha256, _get_pem_key=_get_pem_key):
179-
self.assertRaises(NameError, self._callFUT,
179+
self.assertRaises(UnboundLocalError, self._callFUT,
180180
BAD_CREDENTIALS, EXPIRATION, SIGNATURE_STRING)
181181

182-
def _run_test_with_credentials(self, credentials, account_name):
182+
def _run_test_with_credentials(self, credentials, account_name,
183+
signature_string=None):
183184
import base64
185+
import six
184186
from gcloud._testing import _Monkey
185187
from gcloud import credentials as MUT
186188

@@ -190,7 +192,7 @@ def _run_test_with_credentials(self, credentials, account_name):
190192
sha256 = _SHA256()
191193

192194
EXPIRATION = '100'
193-
SIGNATURE_STRING = b'dummy_signature'
195+
SIGNATURE_STRING = signature_string or b'dummy_signature'
194196
with _Monkey(MUT, crypt=crypt, RSA=rsa, PKCS1_v1_5=pkcs_v1_5,
195197
SHA256=sha256):
196198
result = self._callFUT(credentials, EXPIRATION, SIGNATURE_STRING)
@@ -199,7 +201,12 @@ def _run_test_with_credentials(self, credentials, account_name):
199201
self.assertEqual(crypt._private_key_text,
200202
base64.b64encode(b'dummy_private_key_text'))
201203
self.assertEqual(crypt._private_key_password, 'notasecret')
202-
self.assertEqual(sha256._signature_string, SIGNATURE_STRING)
204+
# sha256._signature_string is always bytes.
205+
if isinstance(SIGNATURE_STRING, six.binary_type):
206+
self.assertEqual(sha256._signature_string, SIGNATURE_STRING)
207+
else:
208+
self.assertEqual(sha256._signature_string,
209+
SIGNATURE_STRING.encode('utf-8'))
203210
SIGNED = base64.b64encode(b'DEADBEEF')
204211
expected_query = {
205212
'Expires': EXPIRATION,
@@ -217,6 +224,17 @@ def test_signed_jwt_for_p12(self):
217224
ACCOUNT_NAME, b'dummy_private_key_text', scopes)
218225
self._run_test_with_credentials(credentials, ACCOUNT_NAME)
219226

227+
def test_signature_non_bytes(self):
228+
from oauth2client import client
229+
230+
scopes = []
231+
ACCOUNT_NAME = 'dummy_service_account_name'
232+
SIGNATURE_STRING = u'dummy_signature'
233+
credentials = client.SignedJwtAssertionCredentials(
234+
ACCOUNT_NAME, b'dummy_private_key_text', scopes)
235+
self._run_test_with_credentials(credentials, ACCOUNT_NAME,
236+
signature_string=SIGNATURE_STRING)
237+
220238
def test_service_account_via_json_key(self):
221239
from oauth2client import service_account
222240
from gcloud._testing import _Monkey

gcloud/test_exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def _callFUT(self, response, content):
5555
def test_hit_w_content_as_str(self):
5656
from gcloud.exceptions import NotFound
5757
response = _Response(404)
58-
content = '{"message": "Not Found"}'
58+
content = b'{"message": "Not Found"}'
5959
exception = self._callFUT(response, content)
6060
self.assertTrue(isinstance(exception, NotFound))
6161
self.assertEqual(exception.message, 'Not Found')

0 commit comments

Comments
 (0)