Path: blob/develop/tests/unit/customizations/s3/test_utils.py
1569 views
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0/7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.12from awscli.testutils import mock, unittest, temporary_file13import argparse14import errno15import os16import tempfile17import shutil18import ntpath19import time20import datetime2122import pytest23from dateutil.tz import tzlocal24from s3transfer.futures import TransferMeta, TransferFuture25from s3transfer.compat import seekable26from botocore.hooks import HierarchicalEmitter2728from awscli.compat import queue29from awscli.compat import StringIO30from awscli.testutils import FileCreator31from awscli.customizations.s3.utils import (32find_bucket_key,33guess_content_type, relative_path, block_unsupported_resources,34StablePriorityQueue, BucketLister, get_file_stat, AppendFilter,35create_warning, human_readable_size, human_readable_to_bytes,36set_file_utime, SetFileUtimeError, RequestParamsMapper, StdoutBytesWriter,37ProvideSizeSubscriber, ProvideETagSubscriber, OnDoneFilteredSubscriber,38ProvideUploadContentTypeSubscriber, ProvideCopyContentTypeSubscriber,39ProvideLastModifiedTimeSubscriber, DirectoryCreatorSubscriber,40DeleteSourceObjectSubscriber, DeleteSourceFileSubscriber,41DeleteCopySourceObjectSubscriber, NonSeekableStream, CreateDirectoryError,42S3PathResolver)43from awscli.customizations.s3.results import WarningResult44from tests.unit.customizations.s3 import FakeTransferFuture45from tests.unit.customizations.s3 import FakeTransferFutureMeta46from tests.unit.customizations.s3 import FakeTransferFutureCallArgs474849@pytest.fixture50def s3control_client():51client = mock.MagicMock()52client.get_access_point.return_value = {53"Bucket": "mybucket"54}55client.list_multi_region_access_points.return_value = {56"AccessPoints": [{57"Alias": "myalias.mrap",58"Regions": [{"Bucket": "mybucket"}]59}]60}61return client626364@pytest.fixture65def sts_client():66client = mock.MagicMock()67client.get_caller_identity.return_value = {68"Account": "123456789012"69}70return client717273@pytest.fixture74def s3_path_resolver(s3control_client, sts_client):75return S3PathResolver(s3control_client, sts_client)767778@pytest.mark.parametrize(79'bytes_int, expected',80(81(1, '1 Byte'),82(10, '10 Bytes'),83(1000, '1000 Bytes'),84(1024, '1.0 KiB'),85(1024 ** 2, '1.0 MiB'),86(1024 ** 3, '1.0 GiB'),87(1024 ** 4, '1.0 TiB'),88(1024 ** 5, '1.0 PiB'),89(1024 ** 6, '1.0 EiB'),90(1024 ** 2 - 1, '1.0 MiB'),91(1024 ** 3 - 1, '1.0 GiB'),92)93)94def test_human_readable_size(bytes_int, expected):95assert human_readable_size(bytes_int) == expected969798@pytest.mark.parametrize(99'size_str, expected',100(101("1", 1),102("1024", 1024),103("1KB", 1024),104("1kb", 1024),105("1MB", 1024 ** 2),106("1GB", 1024 ** 3),107("1TB", 1024 ** 4),108("1KiB", 1024),109("1kib", 1024),110("1MiB", 1024 ** 2),111("1GiB", 1024 ** 3),112("1TiB", 1024 ** 4),113)114)115def test_convert_human_readable_to_bytes(size_str, expected):116assert human_readable_to_bytes(size_str) == expected117118119class AppendFilterTest(unittest.TestCase):120def test_call(self):121parser = argparse.ArgumentParser()122123parser.add_argument('--include', action=AppendFilter, nargs=1,124dest='path')125parser.add_argument('--exclude', action=AppendFilter, nargs=1,126dest='path')127parsed_args = parser.parse_args(['--include', 'a', '--exclude', 'b'])128self.assertEqual(parsed_args.path, [['--include', 'a'],129['--exclude', 'b']])130131132class TestFindBucketKey(unittest.TestCase):133def test_unicode(self):134s3_path = '\u1234' + u'/' + '\u5678'135bucket, key = find_bucket_key(s3_path)136self.assertEqual(bucket, '\u1234')137self.assertEqual(key, '\u5678')138139def test_bucket(self):140bucket, key = find_bucket_key('bucket')141self.assertEqual(bucket, 'bucket')142self.assertEqual(key, '')143144def test_bucket_with_slash(self):145bucket, key = find_bucket_key('bucket/')146self.assertEqual(bucket, 'bucket')147self.assertEqual(key, '')148149def test_bucket_with_key(self):150bucket, key = find_bucket_key('bucket/key')151self.assertEqual(bucket, 'bucket')152self.assertEqual(key, 'key')153154def test_bucket_with_key_and_prefix(self):155bucket, key = find_bucket_key('bucket/prefix/key')156self.assertEqual(bucket, 'bucket')157self.assertEqual(key, 'prefix/key')158159def test_accesspoint_arn(self):160bucket, key = find_bucket_key(161'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint')162self.assertEqual(163bucket, 'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint')164self.assertEqual(key, '')165166def test_accesspoint_arn_with_slash(self):167bucket, key = find_bucket_key(168'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint/')169self.assertEqual(170bucket, 'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint')171self.assertEqual(key, '')172173def test_accesspoint_arn_with_key(self):174bucket, key = find_bucket_key(175'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint/key')176self.assertEqual(177bucket, 'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint')178self.assertEqual(key, 'key')179180def test_accesspoint_arn_with_key_and_prefix(self):181bucket, key = find_bucket_key(182'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint/pre/key')183self.assertEqual(184bucket, 'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint')185self.assertEqual(key, 'pre/key')186187def test_outpost_arn_with_colon(self):188bucket, key = find_bucket_key(189'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'190'accesspoint:my-accesspoint'191)192self.assertEqual(193bucket,194(195'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'196'accesspoint:my-accesspoint'197)198)199self.assertEqual(key, '')200201def test_outpost_arn_with_colon_and_key(self):202bucket, key = find_bucket_key(203'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'204'accesspoint:my-accesspoint/key'205)206self.assertEqual(207bucket,208(209'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'210'accesspoint:my-accesspoint'211)212)213self.assertEqual(key, 'key')214215def test_outpost_arn_with_colon_and_key_with_colon_in_name(self):216bucket, key = find_bucket_key(217'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'218'accesspoint:my-accesspoint/key:name'219)220self.assertEqual(221bucket,222(223'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'224'accesspoint:my-accesspoint'225)226)227self.assertEqual(key, 'key:name')228229def test_outpost_arn_with_colon_and_key_with_slash_in_name(self):230bucket, key = find_bucket_key(231'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'232'accesspoint:my-accesspoint/key/name'233)234self.assertEqual(235bucket,236(237'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'238'accesspoint:my-accesspoint'239)240)241self.assertEqual(key, 'key/name')242243def test_outpost_arn_with_colon_and_key_with_slash_and_colon_in_name(self):244bucket, key = find_bucket_key(245'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'246'accesspoint:my-accesspoint/prefix/key:name'247)248self.assertEqual(249bucket,250(251'arn:aws:s3-outposts:us-west-2:123456789012:outpost:op-12334:'252'accesspoint:my-accesspoint'253)254)255self.assertEqual(key, 'prefix/key:name')256257def test_outpost_arn_with_slash(self):258bucket, key = find_bucket_key(259'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'260'accesspoint/my-accesspoint'261)262self.assertEqual(263bucket,264(265'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'266'accesspoint/my-accesspoint'267)268)269self.assertEqual(key, '')270271def test_outpost_arn_with_slash_and_key(self):272bucket, key = find_bucket_key(273'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'274'accesspoint/my-accesspoint/key'275)276self.assertEqual(277bucket,278(279'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'280'accesspoint/my-accesspoint'281)282)283self.assertEqual(key, 'key')284285def test_outpost_arn_with_slash_and_key_with_colon_in_name(self):286bucket, key = find_bucket_key(287'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'288'accesspoint/my-accesspoint/key:name'289)290self.assertEqual(291bucket,292(293'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'294'accesspoint/my-accesspoint'295)296)297self.assertEqual(key, 'key:name')298299def test_outpost_arn_with_slash_and_key_with_slash_in_name(self):300bucket, key = find_bucket_key(301'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'302'accesspoint/my-accesspoint/key/name'303)304self.assertEqual(305bucket,306(307'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'308'accesspoint/my-accesspoint'309)310)311self.assertEqual(key, 'key/name')312313def test_outpost_arn_with_slash_and_key_with_slash_and_colon_in_name(self):314bucket, key = find_bucket_key(315'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'316'accesspoint/my-accesspoint/prefix/key:name'317)318self.assertEqual(319bucket,320(321'arn:aws:s3-outposts:us-west-2:123456789012:outpost/op-12334/'322'accesspoint/my-accesspoint'323)324)325self.assertEqual(key, 'prefix/key:name')326327328class TestBlockUnsupportedResources(unittest.TestCase):329def test_object_lambda_arn_with_colon_raises_exception(self):330with self.assertRaisesRegex(331ValueError, 'Use s3api commands instead'):332block_unsupported_resources(333'arn:aws:s3-object-lambda:us-west-2:123456789012:'334'accesspoint:my-accesspoint'335)336337def test_object_lambda_arn_with_slash_raises_exception(self):338with self.assertRaisesRegex(339ValueError, 'Use s3api commands instead'):340block_unsupported_resources(341'arn:aws:s3-object-lambda:us-west-2:123456789012:'342'accesspoint/my-accesspoint'343)344345def test_outpost_bucket_arn_with_colon_raises_exception(self):346with self.assertRaisesRegex(347ValueError, 'Use s3control commands instead'):348block_unsupported_resources(349'arn:aws:s3-outposts:us-west-2:123456789012:'350'outpost/op-0a12345678abcdefg:bucket/bucket-foo'351)352353def test_outpost_bucket_arn_with_slash_raises_exception(self):354with self.assertRaisesRegex(355ValueError, 'Use s3control commands instead'):356block_unsupported_resources(357'arn:aws:s3-outposts:us-west-2:123456789012:'358'outpost/op-0a12345678abcdefg/bucket/bucket-foo'359)360361362class TestCreateWarning(unittest.TestCase):363def test_create_warning(self):364path = '/foo/'365error_message = 'There was an error'366warning_message = create_warning(path, error_message)367self.assertEqual(warning_message.message,368'warning: Skipping file /foo/. There was an error')369self.assertFalse(warning_message.error)370self.assertTrue(warning_message.warning)371372373class TestGuessContentType(unittest.TestCase):374def test_guess_content_type(self):375self.assertEqual(guess_content_type('foo.txt'), 'text/plain')376377def test_guess_content_type_with_no_valid_matches(self):378self.assertEqual(guess_content_type('no-extension'), None)379380def test_guess_content_type_with_unicode_error_returns_no_match(self):381with mock.patch('mimetypes.guess_type') as guess_type_patch:382# This should throw a UnicodeDecodeError.383guess_type_patch.side_effect = lambda x: b'\xe2'.decode('ascii')384self.assertEqual(guess_content_type('foo.txt'), None)385386387class TestRelativePath(unittest.TestCase):388def test_relpath_normal(self):389self.assertEqual(relative_path('/tmp/foo/bar', '/tmp/foo'),390'.' + os.sep + 'bar')391392# We need to patch out relpath with the ntpath version so393# we can simulate testing drives on windows.394@mock.patch('os.path.relpath', ntpath.relpath)395def test_relpath_with_error(self):396# Just want to check we don't get an exception raised,397# which is what was happening previously.398self.assertIn(r'foo\bar', relative_path(r'c:\foo\bar'))399400401class TestStablePriorityQueue(unittest.TestCase):402def test_fifo_order_of_same_priorities(self):403a = mock.Mock()404a.PRIORITY = 5405b = mock.Mock()406b.PRIORITY = 5407c = mock.Mock()408c.PRIORITY = 1409410q = StablePriorityQueue(maxsize=10, max_priority=20)411q.put(a)412q.put(b)413q.put(c)414415# First we should get c because it's the lowest priority.416# We're using assertIs because we want the *exact* object.417self.assertIs(q.get(), c)418# Then a and b are the same priority, but we should get419# a first because it was inserted first.420self.assertIs(q.get(), a)421self.assertIs(q.get(), b)422423def test_queue_length(self):424a = mock.Mock()425a.PRIORITY = 5426427q = StablePriorityQueue(maxsize=10, max_priority=20)428self.assertEqual(q.qsize(), 0)429430q.put(a)431self.assertEqual(q.qsize(), 1)432433q.get()434self.assertEqual(q.qsize(), 0)435436def test_insert_max_priority_capped(self):437q = StablePriorityQueue(maxsize=10, max_priority=20)438a = mock.Mock()439a.PRIORITY = 100440q.put(a)441442self.assertIs(q.get(), a)443444def test_priority_attr_is_missing(self):445# If priority attr is missing, we should add it446# to the lowest priority.447q = StablePriorityQueue(maxsize=10, max_priority=20)448a = object()449b = mock.Mock()450b.PRIORITY = 5451452q.put(a)453q.put(b)454455self.assertIs(q.get(), b)456self.assertIs(q.get(), a)457458459class TestBucketList(unittest.TestCase):460def setUp(self):461self.client = mock.Mock()462self.emitter = HierarchicalEmitter()463self.client.meta.events = self.emitter464self.date_parser = mock.Mock()465self.date_parser.return_value = mock.sentinel.now466self.responses = []467468def fake_paginate(self, *args, **kwargs):469for response in self.responses:470self.emitter.emit('after-call.s3.ListObjectsV2', parsed=response)471return self.responses472473def test_list_objects(self):474now = mock.sentinel.now475self.client.get_paginator.return_value.paginate = self.fake_paginate476individual_response_elements = [477{'LastModified': '2014-02-27T04:20:38.000Z',478'Key': 'a', 'Size': 1},479{'LastModified': '2014-02-27T04:20:38.000Z',480'Key': 'b', 'Size': 2},481{'LastModified': '2014-02-27T04:20:38.000Z',482'Key': 'c', 'Size': 3}483]484self.responses = [485{'Contents': individual_response_elements[0:2]},486{'Contents': [individual_response_elements[2]]}487]488lister = BucketLister(self.client, self.date_parser)489objects = list(lister.list_objects(bucket='foo'))490self.assertEqual(objects,491[('foo/a', individual_response_elements[0]),492('foo/b', individual_response_elements[1]),493('foo/c', individual_response_elements[2])])494for individual_response in individual_response_elements:495self.assertEqual(individual_response['LastModified'], now)496497def test_list_objects_passes_in_extra_args(self):498self.client.get_paginator.return_value.paginate.return_value = [499{'Contents': [500{'LastModified': '2014-02-27T04:20:38.000Z',501'Key': 'mykey', 'Size': 3}502]}503]504lister = BucketLister(self.client, self.date_parser)505list(506lister.list_objects(507bucket='mybucket', extra_args={'RequestPayer': 'requester'}508)509)510self.client.get_paginator.return_value.paginate.assert_called_with(511Bucket='mybucket', PaginationConfig={'PageSize': None},512RequestPayer='requester'513)514515516class TestGetFileStat(unittest.TestCase):517518def test_get_file_stat(self):519now = datetime.datetime.now(tzlocal())520epoch_now = time.mktime(now.timetuple())521with temporary_file('w') as f:522f.write('foo')523f.flush()524os.utime(f.name, (epoch_now, epoch_now))525size, update_time = get_file_stat(f.name)526self.assertEqual(size, 3)527self.assertEqual(time.mktime(update_time.timetuple()), epoch_now)528529def test_error_message(self):530with mock.patch('os.stat', mock.Mock(side_effect=IOError('msg'))):531with self.assertRaisesRegex(ValueError, r'myfilename\.txt'):532get_file_stat('myfilename.txt')533534def assert_handles_fromtimestamp_error(self, error):535patch_attribute = 'awscli.customizations.s3.utils.datetime'536with mock.patch(patch_attribute) as datetime_mock:537with temporary_file('w') as temp_file:538temp_file.write('foo')539temp_file.flush()540datetime_mock.fromtimestamp.side_effect = error541size, update_time = get_file_stat(temp_file.name)542self.assertIsNone(update_time)543544def test_returns_epoch_on_invalid_timestamp(self):545self.assert_handles_fromtimestamp_error(ValueError())546547def test_returns_epoch_on_invalid_timestamp_os_error(self):548self.assert_handles_fromtimestamp_error(OSError())549550def test_returns_epoch_on_invalid_timestamp_overflow_error(self):551self.assert_handles_fromtimestamp_error(OverflowError())552553554class TestSetsFileUtime(unittest.TestCase):555556def test_successfully_sets_utime(self):557now = datetime.datetime.now(tzlocal())558epoch_now = time.mktime(now.timetuple())559with temporary_file('w') as f:560set_file_utime(f.name, epoch_now)561_, update_time = get_file_stat(f.name)562self.assertEqual(time.mktime(update_time.timetuple()), epoch_now)563564def test_throws_more_relevant_error_when_errno_1(self):565now = datetime.datetime.now(tzlocal())566epoch_now = time.mktime(now.timetuple())567with mock.patch('os.utime') as utime_mock:568utime_mock.side_effect = OSError(1, '')569with self.assertRaises(SetFileUtimeError):570set_file_utime('not_real_file', epoch_now)571572def test_passes_through_other_os_errors(self):573now = datetime.datetime.now(tzlocal())574epoch_now = time.mktime(now.timetuple())575with mock.patch('os.utime') as utime_mock:576utime_mock.side_effect = OSError(2, '')577with self.assertRaises(OSError):578set_file_utime('not_real_file', epoch_now)579580581class TestRequestParamsMapperSSE(unittest.TestCase):582def setUp(self):583self.cli_params = {584'sse': 'AES256',585'sse_kms_key_id': 'my-kms-key',586'sse_c': 'AES256',587'sse_c_key': 'my-sse-c-key',588'sse_c_copy_source': 'AES256',589'sse_c_copy_source_key': 'my-sse-c-copy-source-key'590}591592def test_head_object(self):593params = {}594RequestParamsMapper.map_head_object_params(params, self.cli_params)595self.assertEqual(596params,597{'SSECustomerAlgorithm': 'AES256',598'SSECustomerKey': 'my-sse-c-key'}599)600601def test_put_object(self):602params = {}603RequestParamsMapper.map_put_object_params(params, self.cli_params)604self.assertEqual(605params,606{'SSECustomerAlgorithm': 'AES256',607'SSECustomerKey': 'my-sse-c-key',608'SSEKMSKeyId': 'my-kms-key',609'ServerSideEncryption': 'AES256'}610)611612def test_get_object(self):613params = {}614RequestParamsMapper.map_get_object_params(params, self.cli_params)615self.assertEqual(616params,617{'SSECustomerAlgorithm': 'AES256',618'SSECustomerKey': 'my-sse-c-key'}619)620621def test_copy_object(self):622params = {}623RequestParamsMapper.map_copy_object_params(params, self.cli_params)624self.assertEqual(625params,626{'CopySourceSSECustomerAlgorithm': 'AES256',627'CopySourceSSECustomerKey': 'my-sse-c-copy-source-key',628'SSECustomerAlgorithm': 'AES256',629'SSECustomerKey': 'my-sse-c-key',630'SSEKMSKeyId': 'my-kms-key',631'ServerSideEncryption': 'AES256'}632)633634def test_create_multipart_upload(self):635params = {}636RequestParamsMapper.map_create_multipart_upload_params(637params, self.cli_params)638self.assertEqual(639params,640{'SSECustomerAlgorithm': 'AES256',641'SSECustomerKey': 'my-sse-c-key',642'SSEKMSKeyId': 'my-kms-key',643'ServerSideEncryption': 'AES256'}644)645646def test_upload_part(self):647params = {}648RequestParamsMapper.map_upload_part_params(params, self.cli_params)649self.assertEqual(650params,651{'SSECustomerAlgorithm': 'AES256',652'SSECustomerKey': 'my-sse-c-key'}653)654655def test_upload_part_copy(self):656params = {}657RequestParamsMapper.map_upload_part_copy_params(658params, self.cli_params)659self.assertEqual(660params,661{'CopySourceSSECustomerAlgorithm': 'AES256',662'CopySourceSSECustomerKey': 'my-sse-c-copy-source-key',663'SSECustomerAlgorithm': 'AES256',664'SSECustomerKey': 'my-sse-c-key'})665666667class TestRequestParamsMapperChecksumAlgorithm:668@pytest.fixture669def cli_params(self):670return {'checksum_algorithm': 'CRC32'}671672@pytest.fixture673def cli_params_no_algorithm(self):674return {}675676def test_put_object(self, cli_params):677request_params = {}678RequestParamsMapper.map_put_object_params(request_params, cli_params)679assert request_params == {'ChecksumAlgorithm': 'CRC32'}680681def test_put_object_no_checksum(self, cli_params_no_algorithm):682request_params = {}683RequestParamsMapper.map_put_object_params(request_params, cli_params_no_algorithm)684assert 'ChecksumAlgorithm' not in request_params685686def test_copy_object(self, cli_params):687request_params = {}688RequestParamsMapper.map_copy_object_params(request_params, cli_params)689assert request_params == {'ChecksumAlgorithm': 'CRC32'}690691def test_copy_object_no_checksum(self, cli_params_no_algorithm):692request_params = {}693RequestParamsMapper.map_put_object_params(request_params, cli_params_no_algorithm)694assert 'ChecksumAlgorithm' not in request_params695696697class TestRequestParamsMapperChecksumMode:698@pytest.fixture699def cli_params(self):700return {'checksum_mode': 'ENABLED'}701702@pytest.fixture703def cli_params_no_checksum(self):704return {}705706def test_get_object(self, cli_params):707request_params = {}708RequestParamsMapper.map_get_object_params(request_params, cli_params)709assert request_params == {'ChecksumMode': 'ENABLED'}710711def test_get_object_no_checksums(self, cli_params_no_checksum):712request_params = {}713RequestParamsMapper.map_get_object_params(request_params, cli_params_no_checksum)714assert 'ChecksumMode' not in request_params715716717class TestRequestParamsMapperRequestPayer(unittest.TestCase):718def setUp(self):719self.cli_params = {'request_payer': 'requester'}720721def test_head_object(self):722params = {}723RequestParamsMapper.map_head_object_params(params, self.cli_params)724self.assertEqual(params, {'RequestPayer': 'requester'})725726def test_put_object(self):727params = {}728RequestParamsMapper.map_put_object_params(params, self.cli_params)729self.assertEqual(params, {'RequestPayer': 'requester'})730731def test_get_object(self):732params = {}733RequestParamsMapper.map_get_object_params(params, self.cli_params)734self.assertEqual(params, {'RequestPayer': 'requester'})735736def test_copy_object(self):737params = {}738RequestParamsMapper.map_copy_object_params(params, self.cli_params)739self.assertEqual(params, {'RequestPayer': 'requester'})740741def test_create_multipart_upload(self):742params = {}743RequestParamsMapper.map_create_multipart_upload_params(744params, self.cli_params)745self.assertEqual(params, {'RequestPayer': 'requester'})746747def test_upload_part(self):748params = {}749RequestParamsMapper.map_upload_part_params(params, self.cli_params)750self.assertEqual(params, {'RequestPayer': 'requester'})751752def test_upload_part_copy(self):753params = {}754RequestParamsMapper.map_upload_part_copy_params(755params, self.cli_params)756self.assertEqual(params, {'RequestPayer': 'requester'})757758def test_delete_object(self):759params = {}760RequestParamsMapper.map_delete_object_params(761params, self.cli_params)762self.assertEqual(params, {'RequestPayer': 'requester'})763764def test_list_objects(self):765params = {}766RequestParamsMapper.map_list_objects_v2_params(767params, self.cli_params)768self.assertEqual(params, {'RequestPayer': 'requester'})769770771class TestBytesPrint(unittest.TestCase):772def setUp(self):773self.stdout = mock.Mock()774self.stdout.buffer = self.stdout775776def test_stdout_wrapper(self):777wrapper = StdoutBytesWriter(self.stdout)778wrapper.write(b'foo')779self.assertTrue(self.stdout.write.called)780self.assertEqual(self.stdout.write.call_args[0][0], b'foo')781782783class TestProvideSizeSubscriber(unittest.TestCase):784def setUp(self):785self.transfer_future = mock.Mock(spec=TransferFuture)786self.transfer_meta = TransferMeta()787self.transfer_future.meta = self.transfer_meta788789def test_size_set(self):790self.transfer_meta.provide_transfer_size(5)791subscriber = ProvideSizeSubscriber(10)792subscriber.on_queued(self.transfer_future)793self.assertEqual(self.transfer_meta.size, 10)794795796class TestProvideEtagSubscriber:797def test_etag_set(self):798transfer_meta = TransferMeta()799transfer_future = mock.Mock(spec=TransferFuture)800transfer_future.meta = transfer_meta801etag = 'myetag'802803transfer_meta.provide_object_etag('oldetag')804subscriber = ProvideETagSubscriber(etag)805subscriber.on_queued(transfer_future)806assert transfer_meta.etag == etag807808809class OnDoneFilteredRecordingSubscriber(OnDoneFilteredSubscriber):810def __init__(self):811self.on_success_calls = []812self.on_failure_calls = []813814def _on_success(self, future):815self.on_success_calls.append(future)816817def _on_failure(self, future, exception):818self.on_failure_calls.append((future, exception))819820821class TestOnDoneFilteredSubscriber(unittest.TestCase):822def test_on_success(self):823subscriber = OnDoneFilteredRecordingSubscriber()824future = FakeTransferFuture('return-value')825subscriber.on_done(future)826self.assertEqual(subscriber.on_success_calls, [future])827self.assertEqual(subscriber.on_failure_calls, [])828829def test_on_failure(self):830subscriber = OnDoneFilteredRecordingSubscriber()831exception = Exception('my exception')832future = FakeTransferFuture(exception=exception)833subscriber.on_done(future)834self.assertEqual(subscriber.on_failure_calls, [(future, exception)])835self.assertEqual(subscriber.on_success_calls, [])836837838class TestProvideUploadContentTypeSubscriber(unittest.TestCase):839def setUp(self):840self.filename = 'myfile.txt'841self.extra_args = {}842self.future = self.set_future()843self.subscriber = ProvideUploadContentTypeSubscriber()844845def set_future(self):846call_args = FakeTransferFutureCallArgs(847fileobj=self.filename, extra_args=self.extra_args)848meta = FakeTransferFutureMeta(call_args=call_args)849return FakeTransferFuture(meta=meta)850851def test_on_queued_provides_content_type(self):852self.subscriber.on_queued(self.future)853self.assertEqual(self.extra_args, {'ContentType': 'text/plain'})854855def test_on_queued_does_not_provide_content_type_when_unknown(self):856self.filename = 'file-with-no-extension'857self.future = self.set_future()858self.subscriber.on_queued(self.future)859self.assertEqual(self.extra_args, {})860861862class TestProvideCopyContentTypeSubscriber(863TestProvideUploadContentTypeSubscriber):864def setUp(self):865self.filename = 'myfile.txt'866self.extra_args = {}867self.future = self.set_future()868self.subscriber = ProvideCopyContentTypeSubscriber()869870def set_future(self):871copy_source = {'Bucket': 'mybucket', 'Key': self.filename}872call_args = FakeTransferFutureCallArgs(873copy_source=copy_source, extra_args=self.extra_args)874meta = FakeTransferFutureMeta(call_args=call_args)875return FakeTransferFuture(meta=meta)876877878class BaseTestWithFileCreator(unittest.TestCase):879def setUp(self):880self.file_creator = FileCreator()881882def tearDown(self):883self.file_creator.remove_all()884885886class TestProvideLastModifiedTimeSubscriber(BaseTestWithFileCreator):887def setUp(self):888super(TestProvideLastModifiedTimeSubscriber, self).setUp()889self.filename = self.file_creator.create_file('myfile', 'my contents')890self.desired_utime = datetime.datetime(8912016, 1, 18, 7, 0, 0, tzinfo=tzlocal())892self.result_queue = queue.Queue()893self.subscriber = ProvideLastModifiedTimeSubscriber(894self.desired_utime, self.result_queue)895896call_args = FakeTransferFutureCallArgs(fileobj=self.filename)897meta = FakeTransferFutureMeta(call_args=call_args)898self.future = FakeTransferFuture(meta=meta)899900def test_on_success_modifies_utime(self):901self.subscriber.on_done(self.future)902_, utime = get_file_stat(self.filename)903self.assertEqual(utime, self.desired_utime)904905def test_on_success_failure_in_utime_mod_raises_warning(self):906self.subscriber = ProvideLastModifiedTimeSubscriber(907None, self.result_queue)908self.subscriber.on_done(self.future)909# Because the time to provide was None it will throw an exception910# which results in the a warning about the utime not being able911# to be set being placed in the result queue.912result = self.result_queue.get()913self.assertIsInstance(result, WarningResult)914self.assertIn(915'unable to update the last modified time', result.message)916917918class TestDirectoryCreatorSubscriber(BaseTestWithFileCreator):919def setUp(self):920super(TestDirectoryCreatorSubscriber, self).setUp()921self.directory_to_create = os.path.join(922self.file_creator.rootdir, 'new-directory')923self.filename = os.path.join(self.directory_to_create, 'myfile')924925call_args = FakeTransferFutureCallArgs(fileobj=self.filename)926meta = FakeTransferFutureMeta(call_args=call_args)927self.future = FakeTransferFuture(meta=meta)928929self.subscriber = DirectoryCreatorSubscriber()930931def test_on_queued_creates_directories_if_do_not_exist(self):932self.subscriber.on_queued(self.future)933self.assertTrue(os.path.exists(self.directory_to_create))934935def test_on_queued_does_not_create_directories_if_exist(self):936os.makedirs(self.directory_to_create)937# This should not cause any issues if the directory already exists938self.subscriber.on_queued(self.future)939# The directory should still exist940self.assertTrue(os.path.exists(self.directory_to_create))941942def test_on_queued_failure_propagates_create_directory_error(self):943# If makedirs() raises an OSError of exception, we should944# propagate the exception with a better worded CreateDirectoryError.945with mock.patch('os.makedirs') as makedirs_patch:946makedirs_patch.side_effect = OSError()947with self.assertRaises(CreateDirectoryError):948self.subscriber.on_queued(self.future)949self.assertFalse(os.path.exists(self.directory_to_create))950951def test_on_queued_failure_propagates_clear_error_message(self):952# If makedirs() raises an OSError of exception, we should953# propagate the exception.954with mock.patch('os.makedirs') as makedirs_patch:955os_error = OSError()956os_error.errno = errno.EEXIST957makedirs_patch.side_effect = os_error958# The on_queued should not raise an error if the directory959# already exists960try:961self.subscriber.on_queued(self.future)962except Exception as e:963self.fail(964'on_queued should not have raised an exception related '965'to directory creation especially if one already existed '966'but got %s' % e)967968969class TestDeleteSourceObjectSubscriber(unittest.TestCase):970def setUp(self):971self.client = mock.Mock()972self.bucket = 'mybucket'973self.key = 'mykey'974call_args = FakeTransferFutureCallArgs(975bucket=self.bucket, key=self.key, extra_args={})976meta = FakeTransferFutureMeta(call_args=call_args)977self.future = mock.Mock()978self.future.meta = meta979980def test_deletes_object(self):981DeleteSourceObjectSubscriber(self.client).on_done(self.future)982self.client.delete_object.assert_called_once_with(983Bucket=self.bucket, Key=self.key)984self.future.set_exception.assert_not_called()985986def test_sets_exception_on_error(self):987exception = ValueError()988self.client.delete_object.side_effect = exception989DeleteSourceObjectSubscriber(self.client).on_done(self.future)990self.client.delete_object.assert_called_once_with(991Bucket=self.bucket, Key=self.key)992self.future.set_exception.assert_called_once_with(exception)993994def test_with_request_payer(self):995self.future.meta.call_args.extra_args = {'RequestPayer': 'requester'}996DeleteSourceObjectSubscriber(self.client).on_done(self.future)997self.client.delete_object.assert_called_once_with(998Bucket=self.bucket, Key=self.key, RequestPayer='requester')99910001001class TestDeleteCopySourceObjectSubscriber(unittest.TestCase):1002def setUp(self):1003self.client = mock.Mock()1004self.bucket = 'mybucket'1005self.key = 'mykey'1006copy_source = {'Bucket': self.bucket, 'Key': self.key}1007call_args = FakeTransferFutureCallArgs(1008copy_source=copy_source, extra_args={})1009meta = FakeTransferFutureMeta(call_args=call_args)1010self.future = mock.Mock()1011self.future.meta = meta10121013def test_deletes_object(self):1014DeleteCopySourceObjectSubscriber(self.client).on_done(self.future)1015self.client.delete_object.assert_called_once_with(1016Bucket=self.bucket, Key=self.key)1017self.future.set_exception.assert_not_called()10181019def test_sets_exception_on_error(self):1020exception = ValueError()1021self.client.delete_object.side_effect = exception1022DeleteCopySourceObjectSubscriber(self.client).on_done(self.future)1023self.client.delete_object.assert_called_once_with(1024Bucket=self.bucket, Key=self.key)1025self.future.set_exception.assert_called_once_with(exception)10261027def test_with_request_payer(self):1028self.future.meta.call_args.extra_args = {'RequestPayer': 'requester'}1029DeleteCopySourceObjectSubscriber(self.client).on_done(self.future)1030self.client.delete_object.assert_called_once_with(1031Bucket=self.bucket, Key=self.key, RequestPayer='requester')103210331034class TestDeleteSourceFileSubscriber(unittest.TestCase):1035def setUp(self):1036self.tempdir = tempfile.mkdtemp()1037self.filename = os.path.join(self.tempdir, 'myfile')1038call_args = FakeTransferFutureCallArgs(fileobj=self.filename)1039meta = FakeTransferFutureMeta(call_args=call_args)1040self.future = mock.Mock()1041self.future.meta = meta10421043def tearDown(self):1044shutil.rmtree(self.tempdir)10451046def test_deletes_file(self):1047with open(self.filename, 'w') as f:1048f.write('data')1049DeleteSourceFileSubscriber().on_done(self.future)1050self.assertFalse(os.path.exists(self.filename))1051self.future.set_exception.assert_not_called()10521053def test_sets_exception_on_error(self):1054DeleteSourceFileSubscriber().on_done(self.future)1055self.assertFalse(os.path.exists(self.filename))1056call_args = self.future.set_exception.call_args[0]1057self.assertIsInstance(call_args[0], EnvironmentError)105810591060class TestNonSeekableStream(unittest.TestCase):1061def test_can_make_stream_unseekable(self):1062fileobj = StringIO('foobar')1063self.assertTrue(seekable(fileobj))1064nonseekable_fileobj = NonSeekableStream(fileobj)1065self.assertFalse(seekable(nonseekable_fileobj))1066self.assertEqual(nonseekable_fileobj.read(), 'foobar')10671068def test_can_specify_amount_for_nonseekable_stream(self):1069nonseekable_fileobj = NonSeekableStream(StringIO('foobar'))1070self.assertEqual(nonseekable_fileobj.read(3), 'foo')107110721073class TestS3PathResolver:1074_BASE_ACCESSPOINT_ARN = (1075"s3://arn:aws:s3:us-west-2:123456789012:accesspoint/myaccesspoint")1076_BASE_OUTPOST_ACCESSPOINT_ARN = (1077"s3://arn:aws:s3-outposts:us-east-1:123456789012:outpost"1078"/op-foo/accesspoint/myaccesspoint")1079_BASE_ACCESSPOINT_ALIAS = "s3://myaccesspoint-foobar12345-s3alias"1080_BASE_OUTPOST_ACCESSPOINT_ALIAS = "s3://myaccesspoint-foobar12345--op-s3"1081_BASE_MRAP_ARN = "s3://arn:aws:s3::123456789012:accesspoint/myalias.mrap"10821083@pytest.mark.parametrize(1084"path,resolved",1085[(_BASE_ACCESSPOINT_ARN,"s3://mybucket/"),1086(f"{_BASE_ACCESSPOINT_ARN}/","s3://mybucket/"),1087(f"{_BASE_ACCESSPOINT_ARN}/mykey","s3://mybucket/mykey"),1088(f"{_BASE_ACCESSPOINT_ARN}/myprefix/","s3://mybucket/myprefix/"),1089(f"{_BASE_ACCESSPOINT_ARN}/myprefix/mykey",1090"s3://mybucket/myprefix/mykey")]1091)1092def test_resolves_accesspoint_arn(1093self, path, resolved, s3_path_resolver, s3control_client1094):1095resolved_paths = s3_path_resolver.resolve_underlying_s3_paths(path)1096assert resolved_paths == [resolved]1097s3control_client.get_access_point.assert_called_with(1098AccountId="123456789012",1099Name="myaccesspoint"1100)11011102@pytest.mark.parametrize(1103"path,resolved",1104[(_BASE_OUTPOST_ACCESSPOINT_ARN,"s3://mybucket/"),1105(f"{_BASE_OUTPOST_ACCESSPOINT_ARN}/","s3://mybucket/"),1106(f"{_BASE_OUTPOST_ACCESSPOINT_ARN}/mykey","s3://mybucket/mykey"),1107(f"{_BASE_OUTPOST_ACCESSPOINT_ARN}/myprefix/",1108"s3://mybucket/myprefix/"),1109(f"{_BASE_OUTPOST_ACCESSPOINT_ARN}/myprefix/mykey",1110"s3://mybucket/myprefix/mykey")]1111)1112def test_resolves_outpost_accesspoint_arn(1113self, path, resolved, s3_path_resolver, s3control_client1114):1115resolved_paths = s3_path_resolver.resolve_underlying_s3_paths(path)1116assert resolved_paths == [resolved]1117s3control_client.get_access_point.assert_called_with(1118AccountId="123456789012",1119Name=("arn:aws:s3-outposts:us-east-1:123456789012:outpost"1120"/op-foo/accesspoint/myaccesspoint")1121)11221123@pytest.mark.parametrize(1124"path,resolved",1125[(_BASE_ACCESSPOINT_ALIAS,"s3://mybucket/"),1126(f"{_BASE_ACCESSPOINT_ALIAS}/","s3://mybucket/"),1127(f"{_BASE_ACCESSPOINT_ALIAS}/mykey","s3://mybucket/mykey"),1128(f"{_BASE_ACCESSPOINT_ALIAS}/myprefix/","s3://mybucket/myprefix/"),1129(f"{_BASE_ACCESSPOINT_ALIAS}/myprefix/mykey",1130"s3://mybucket/myprefix/mykey")]1131)1132def test_resolves_accesspoint_alias(1133self, path, resolved, s3_path_resolver, s3control_client, sts_client1134):1135resolved_paths = s3_path_resolver.resolve_underlying_s3_paths(path)1136assert resolved_paths == [resolved]1137sts_client.get_caller_identity.assert_called_once()1138s3control_client.get_access_point.assert_called_with(1139AccountId="123456789012",1140Name="myaccesspoint-foobar12345-s3alias"1141)11421143@pytest.mark.parametrize(1144"path",1145[(_BASE_OUTPOST_ACCESSPOINT_ALIAS),1146(f"{_BASE_OUTPOST_ACCESSPOINT_ALIAS}/"),1147(f"{_BASE_OUTPOST_ACCESSPOINT_ALIAS}/mykey"),1148(f"{_BASE_OUTPOST_ACCESSPOINT_ALIAS}/myprefix/"),1149(f"{_BASE_OUTPOST_ACCESSPOINT_ALIAS}/myprefix/mykey")]1150)1151def test_outpost_accesspoint_alias_raises_exception(1152self, path, s3_path_resolver1153):1154with pytest.raises(ValueError) as e:1155s3_path_resolver.resolve_underlying_s3_paths(path)1156assert "Can't resolve underlying bucket name" in str(e.value)11571158@pytest.mark.parametrize(1159"path,resolved",1160[(_BASE_MRAP_ARN,"s3://mybucket/"),1161(f"{_BASE_MRAP_ARN}/","s3://mybucket/"),1162(f"{_BASE_MRAP_ARN}/mykey","s3://mybucket/mykey"),1163(f"{_BASE_MRAP_ARN}/myprefix/","s3://mybucket/myprefix/"),1164(f"{_BASE_MRAP_ARN}/myprefix/mykey","s3://mybucket/myprefix/mykey")]1165)1166def test_resolves_mrap_arn(1167self, path, resolved, s3_path_resolver, s3control_client1168):1169resolved_paths = s3_path_resolver.resolve_underlying_s3_paths(path)1170assert resolved_paths == [resolved]1171s3control_client.list_multi_region_access_points.assert_called_with(1172AccountId="123456789012"1173)11741175@pytest.mark.parametrize(1176"path,resolved,name",1177[(f"{_BASE_ACCESSPOINT_ARN}-s3alias/mykey","s3://mybucket/mykey",1178"myaccesspoint-s3alias"),1179(f"{_BASE_OUTPOST_ACCESSPOINT_ARN}--op-s3/mykey",1180"s3://mybucket/mykey",1181f"{_BASE_OUTPOST_ACCESSPOINT_ARN[5:]}--op-s3")]1182)1183def test_alias_suffixes_dont_match_accesspoint_arns(1184self, path, resolved, name, s3_path_resolver, s3control_client1185):1186resolved_paths = s3_path_resolver.resolve_underlying_s3_paths(path)1187assert resolved_paths == [resolved]1188s3control_client.get_access_point.assert_called_with(1189AccountId="123456789012",1190Name=name1191)11921193@pytest.mark.parametrize(1194"path,expected_has_underlying_s3_path",1195[(_BASE_ACCESSPOINT_ARN,True),1196(f"{_BASE_ACCESSPOINT_ARN}/mykey",True),1197(f"{_BASE_ACCESSPOINT_ARN}/myprefix/mykey",True),1198(_BASE_ACCESSPOINT_ALIAS,True),1199(_BASE_OUTPOST_ACCESSPOINT_ARN,True),1200(_BASE_OUTPOST_ACCESSPOINT_ALIAS,True),1201(_BASE_MRAP_ARN,True),1202("s3://mybucket/",False),1203("s3://mybucket/mykey",False),1204("s3://mybucket/myprefix/mykey",False)]1205)1206def test_has_underlying_s3_path(self, path, expected_has_underlying_s3_path):1207has_underlying_s3_path = S3PathResolver.has_underlying_s3_path(path)1208assert has_underlying_s3_path == expected_has_underlying_s3_path120912101211