diff --git a/moto/core/models.py b/moto/core/models.py index 6e93f911a..c6fb72ffa 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals from __future__ import absolute_import @@ -176,16 +177,49 @@ class ServerModeMockAWS(BaseMockAWS): if 'endpoint_url' not in kwargs: kwargs['endpoint_url'] = "http://localhost:5000" return real_boto3_resource(*args, **kwargs) + + def fake_httplib_send_output(self, message_body=None, *args, **kwargs): + def _convert_to_bytes(mixed_buffer): + bytes_buffer = [] + for chunk in mixed_buffer: + if isinstance(chunk, six.text_type): + bytes_buffer.append(chunk.encode('utf-8')) + else: + bytes_buffer.append(chunk) + msg = b"\r\n".join(bytes_buffer) + return msg + + self._buffer.extend((b"", b"")) + msg = _convert_to_bytes(self._buffer) + del self._buffer[:] + if isinstance(message_body, bytes): + msg += message_body + message_body = None + self.send(msg) + # if self._expect_header_set: + # read, write, exc = select.select([self.sock], [], [self.sock], 1) + # if read: + # self._handle_expect_response(message_body) + # return + if message_body is not None: + self.send(message_body) + self._client_patcher = mock.patch('boto3.client', fake_boto3_client) - self._resource_patcher = mock.patch( - 'boto3.resource', fake_boto3_resource) + self._resource_patcher = mock.patch('boto3.resource', fake_boto3_resource) + if six.PY2: + self._httplib_patcher = mock.patch('httplib.HTTPConnection._send_output', fake_httplib_send_output) + self._client_patcher.start() self._resource_patcher.start() + if six.PY2: + self._httplib_patcher.start() def disable_patching(self): if self._client_patcher: self._client_patcher.stop() self._resource_patcher.stop() + if six.PY2: + self._httplib_patcher.stop() class Model(type): diff --git a/moto/s3/responses.py b/moto/s3/responses.py index d5e15aead..fb1735a5c 100755 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -8,6 +8,7 @@ from six.moves.urllib.parse import parse_qs, urlparse import xmltodict +from moto.packages.httpretty.core import HTTPrettyRequest from moto.core.responses import _TemplateEnvironmentMixin from moto.s3bucket_path.utils import bucket_name_from_url as bucketpath_bucket_name_from_url, parse_key_name as bucketpath_parse_key_name, is_delete_keys as bucketpath_is_delete_keys @@ -113,7 +114,10 @@ class ResponseObject(_TemplateEnvironmentMixin): return 200, {}, response.encode("utf-8") else: status_code, headers, response_content = response - return status_code, headers, response_content.encode("utf-8") + if not isinstance(response_content, six.binary_type): + response_content = response_content.encode("utf-8") + + return status_code, headers, response_content def _bucket_response(self, request, full_url, headers): parsed_url = urlparse(full_url) @@ -139,6 +143,7 @@ class ResponseObject(_TemplateEnvironmentMixin): body = b'' if isinstance(body, six.binary_type): body = body.decode('utf-8') + body = u'{0}'.format(body).encode('utf-8') if method == 'HEAD': return self._bucket_response_head(bucket_name, headers) @@ -209,7 +214,7 @@ class ResponseObject(_TemplateEnvironmentMixin): if not website_configuration: template = self.response_template(S3_NO_BUCKET_WEBSITE_CONFIG) return 404, {}, template.render(bucket_name=bucket_name) - return website_configuration + return 200, {}, website_configuration elif 'acl' in querystring: bucket = self.backend.get_bucket(bucket_name) template = self.response_template(S3_OBJECT_ACL_RESPONSE) @@ -355,7 +360,7 @@ class ResponseObject(_TemplateEnvironmentMixin): if not request.headers.get('Content-Length'): return 411, {}, "Content-Length required" if 'versioning' in querystring: - ver = re.search('([A-Za-z]+)', body) + ver = re.search('([A-Za-z]+)', body.decode()) if ver: self.backend.set_bucket_versioning(bucket_name, ver.group(1)) template = self.response_template(S3_BUCKET_VERSIONING) @@ -444,7 +449,12 @@ class ResponseObject(_TemplateEnvironmentMixin): def _bucket_response_post(self, request, body, bucket_name, headers): if not request.headers.get('Content-Length'): return 411, {}, "Content-Length required" - path = request.path if hasattr(request, 'path') else request.path_url + + if isinstance(request, HTTPrettyRequest): + path = request.path + else: + path = request.full_path if hasattr(request, 'full_path') else request.path_url + if self.is_delete_keys(request, path, bucket_name): return self._bucket_response_delete_keys(request, body, bucket_name, headers) @@ -454,6 +464,8 @@ class ResponseObject(_TemplateEnvironmentMixin): form = request.form else: # HTTPretty, build new form object + body = body.decode() + form = {} for kv in body.split('&'): k, v = kv.split('=') diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 32ccd1b9c..829941d79 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - from __future__ import unicode_literals import datetime @@ -1865,7 +1864,7 @@ def test_boto3_list_object_versions(): def test_boto3_delete_markers(): s3 = boto3.client('s3', region_name='us-east-1') bucket_name = 'mybucket' - key = 'key-with-versions' + key = u'key-with-versions-and-unicode-ó' s3.create_bucket(Bucket=bucket_name) s3.put_bucket_versioning( Bucket=bucket_name, @@ -1880,10 +1879,9 @@ def test_boto3_delete_markers(): Key=key, Body=body ) - s3.delete_object( - Bucket=bucket_name, - Key=key - ) + + s3.delete_objects(Bucket=bucket_name, Delete={'Objects': [{'Key': key}]}) + with assert_raises(ClientError) as e: s3.get_object( Bucket=bucket_name, @@ -1905,12 +1903,18 @@ def test_boto3_delete_markers(): Bucket=bucket_name ) response['Versions'].should.have.length_of(2) - response['Versions'][-1]['IsLatest'].should.be.true - response['Versions'][0]['IsLatest'].should.be.false - [(key_metadata['Key'], key_metadata['VersionId']) - for key_metadata in response['Versions']].should.equal( - [('key-with-versions', '0'), ('key-with-versions', '1')] - ) + + # We've asserted there is only 2 records so one is newest, one is oldest + latest = list(filter(lambda item: item['IsLatest'], response['Versions']))[0] + oldest = list(filter(lambda item: not item['IsLatest'], response['Versions']))[0] + + # Double check ordering of version ID's + latest['VersionId'].should.equal('1') + oldest['VersionId'].should.equal('0') + + # Double check the name is still unicode + latest['Key'].should.equal('key-with-versions-and-unicode-ó') + oldest['Key'].should.equal('key-with-versions-and-unicode-ó') @mock_s3