Path: blob/develop/tests/unit/customizations/s3/test_subcommands.py
2633 views
# Copyright 2014 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.0e7#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.12import argparse13import os14import sys1516import botocore.session17from awscli.customizations.s3.s3 import S318from awscli.customizations.s3.subcommands import CommandParameters, \19CommandArchitecture, CpCommand, SyncCommand, ListCommand, \20RbCommand, get_client21from awscli.customizations.s3.transferconfig import RuntimeConfig22from awscli.customizations.s3.syncstrategy.base import \23SizeAndLastModifiedSync, NeverSync, MissingFileSync24from awscli.testutils import mock, unittest, BaseAWSHelpOutputTest, \25BaseAWSCommandParamsTest, FileCreator26from tests.unit.customizations.s3 import make_loc_files, clean_loc_files27from awscli.compat import StringIO282930class FakeArgs(object):31def __init__(self, **kwargs):32self.__dict__.update(kwargs)3334def __contains__(self, key):35return key in self.__dict__363738class TestGetClient(unittest.TestCase):39def test_client(self):40session = mock.Mock()41endpoint = get_client(session, region='us-west-1', endpoint_url='URL',42verify=True)43session.create_client.assert_called_with(44's3', region_name='us-west-1', endpoint_url='URL', verify=True,45config=None)464748class TestRbCommand(unittest.TestCase):49def setUp(self):50self.session = mock.Mock()51self.session.get_scoped_config.return_value = {}52self.rb_command = RbCommand(self.session)53self.parsed_args = FakeArgs(path='s3://mybucket/',54force=True, dir_op=False)55self.parsed_globals = FakeArgs(region=None, endpoint_url=None,56verify_ssl=None)57self.cmd_name = 'awscli.customizations.s3.subcommands.RmCommand'58self.arch_name = 'awscli.customizations.s3.subcommands.CommandArchitecture'5960def test_rb_command_with_force_deletes_objects_in_bucket(self):61with mock.patch(self.cmd_name) as rm_command:62with mock.patch(self.arch_name):63# RmCommand returns an RmCommand instance whose __call__64# should be the RC of the command.65# In this case we'll have it return an RC of 0 which indicates66# success.67rm_command.return_value.return_value = 068self.rb_command._run_main(self.parsed_args,69parsed_globals=self.parsed_globals)70# Because of --force we should have called the71# rm_command with the --recursive option.72rm_command.return_value.assert_called_with(73['s3://mybucket/', '--recursive'], mock.ANY)7475def test_rb_command_with_force_requires_strict_path(self):76with self.assertRaises(ValueError):77self.parsed_args.path = 's3://mybucket/mykey'78self.rb_command._run_main(self.parsed_args,79parsed_globals=self.parsed_globals)808182class TestLSCommand(unittest.TestCase):83def setUp(self):84self.session = mock.Mock()85self.session.create_client.return_value.list_buckets.return_value\86= {'Buckets': []}87self.session.create_client.return_value.get_paginator.return_value\88.paginate.return_value = [{'Contents': [], 'CommonPrefixes': []}]8990def _get_fake_kwargs(self, override=None):91fake_kwargs = {92'paths': 's3://',93'dir_op': False,94'human_readable': False,95'summarize': False,96'page_size': None,97'request_payer': None,98'bucket_name_prefix': None,99'bucket_region': None,100}101fake_kwargs.update(override or {})102103return fake_kwargs104105def test_ls_command_for_bucket(self):106ls_command = ListCommand(self.session)107parsed_args = FakeArgs(**self._get_fake_kwargs({108'paths': 's3://mybucket/',109'page_size': '5',110}))111parsed_globals = mock.Mock()112ls_command._run_main(parsed_args, parsed_globals)113call = self.session.create_client.return_value.list_objects_v2114paginate = self.session.create_client.return_value.get_paginator\115.return_value.paginate116# We should make no operation calls.117self.assertEqual(call.call_count, 0)118# And only a single pagination call to ListObjectsV2.119self.session.create_client.return_value.get_paginator.\120assert_called_with('list_objects_v2')121ref_call_args = {'Bucket': u'mybucket', 'Delimiter': '/',122'Prefix': u'',123'PaginationConfig': {'PageSize': u'5'}}124125paginate.assert_called_with(**ref_call_args)126127def test_ls_command_with_no_args(self):128ls_command = ListCommand(self.session)129parsed_global = FakeArgs(region=None, endpoint_url=None,130verify_ssl=None)131parsed_args = FakeArgs(**self._get_fake_kwargs())132ls_command._run_main(parsed_args, parsed_global)133call = self.session.create_client.return_value.list_buckets134paginate = self.session.create_client.return_value.get_paginator\135.return_value.paginate136137# We should make no operation calls.138self.assertEqual(call.call_count, 0)139# And only a single pagination call to ListBuckets.140self.session.create_client.return_value.get_paginator.\141assert_called_with('list_buckets')142ref_call_args = {'PaginationConfig': {'PageSize': None}}143144paginate.assert_called_with(**ref_call_args)145146# Verify get_client147get_client = self.session.create_client148args = get_client.call_args149self.assertEqual(args, mock.call(150's3', region_name=None, endpoint_url=None, verify=None,151config=None))152153def test_ls_with_bucket_name_prefix(self):154ls_command = ListCommand(self.session)155parsed_args = FakeArgs(**self._get_fake_kwargs({156'bucket_name_prefix': 'myprefix',157}))158parsed_globals = FakeArgs(159region=None,160endpoint_url=None,161verify_ssl=None,162)163ls_command._run_main(parsed_args, parsed_globals)164call = self.session.create_client.return_value.list_objects165paginate = self.session.create_client.return_value.get_paginator\166.return_value.paginate167# We should make no operation calls.168self.assertEqual(call.call_count, 0)169self.session.create_client.return_value.get_paginator.\170assert_called_with('list_buckets')171ref_call_args = {172'PaginationConfig': {'PageSize': None},173'Prefix': 'myprefix',174}175176paginate.assert_called_with(**ref_call_args)177178def test_ls_with_bucket_region(self):179ls_command = ListCommand(self.session)180parsed_args = FakeArgs(**self._get_fake_kwargs({181'bucket_region': 'us-west-1',182}))183parsed_globals = FakeArgs(184region=None,185endpoint_url=None,186verify_ssl=None,187)188ls_command._run_main(parsed_args, parsed_globals)189call = self.session.create_client.return_value.list_objects190paginate = self.session.create_client.return_value.get_paginator\191.return_value.paginate192# We should make no operation calls.193self.assertEqual(call.call_count, 0)194self.session.create_client.return_value.get_paginator.\195assert_called_with('list_buckets')196ref_call_args = {197'PaginationConfig': {'PageSize': None},198'BucketRegion': 'us-west-1',199}200201paginate.assert_called_with(**ref_call_args)202203def test_ls_with_verify_argument(self):204ls_command = ListCommand(self.session)205parsed_global = FakeArgs(region='us-west-2', endpoint_url=None,206verify_ssl=False)207parsed_args = FakeArgs(**self._get_fake_kwargs({}))208ls_command._run_main(parsed_args, parsed_global)209# Verify get_client210get_client = self.session.create_client211args = get_client.call_args212self.assertEqual(args, mock.call(213's3', region_name='us-west-2', endpoint_url=None, verify=False,214config=None))215216def test_ls_with_requester_pays(self):217ls_command = ListCommand(self.session)218parsed_args = FakeArgs(**self._get_fake_kwargs({219'paths': 's3://mybucket/',220'page_size': '5',221'request_payer': 'requester',222}))223parsed_globals = mock.Mock()224ls_command._run_main(parsed_args, parsed_globals)225call = self.session.create_client.return_value.list_objects226paginate = self.session.create_client.return_value.get_paginator\227.return_value.paginate228# We should make no operation calls.229self.assertEqual(call.call_count, 0)230# And only a single pagination call to ListObjectsV2.231self.session.create_client.return_value.get_paginator.\232assert_called_with('list_objects_v2')233ref_call_args = {234'Bucket': u'mybucket', 'Delimiter': '/',235'Prefix': u'', 'PaginationConfig': {'PageSize': '5'},236'RequestPayer': 'requester',237}238239paginate.assert_called_with(**ref_call_args)240241242class CommandArchitectureTest(BaseAWSCommandParamsTest):243def setUp(self):244super(CommandArchitectureTest, self).setUp()245self.session = self.driver.session246self.bucket = 'mybucket'247self.file_creator = FileCreator()248self.loc_files = make_loc_files(self.file_creator)249self.output = StringIO()250self.err_output = StringIO()251self.saved_stdout = sys.stdout252self.saved_stderr = sys.stderr253sys.stdout = self.output254sys.stderr = self.err_output255256def tearDown(self):257self.output.close()258self.err_output.close()259sys.stdout = self.saved_stdout260sys.stderr = self.saved_stderr261262super(CommandArchitectureTest, self).tearDown()263clean_loc_files(self.file_creator)264265def _get_file_path(self, file):266try:267return os.path.relpath(file)268except ValueError:269# In some cases (usually it happens inside Windows based GitHub270# Action) tests are situated on one volume and temp folder on271# another one, in such a case there is no relative path between272# them and we use absolute path instead273return os.path.abspath(file)274275def test_set_client_no_source(self):276session = mock.Mock()277cmd_arc = CommandArchitecture(session, 'sync',278{'region': 'us-west-1',279'endpoint_url': None,280'verify_ssl': None,281'source_region': None})282cmd_arc.set_clients()283self.assertEqual(session.create_client.call_count, 2)284self.assertEqual(285session.create_client.call_args_list[0],286mock.call(287's3', region_name='us-west-1', endpoint_url=None, verify=None,288config=None)289)290# A client created with the same arguments as the first should be used291# for the source client since no source region was provided.292self.assertEqual(293session.create_client.call_args_list[1],294mock.call(295's3', region_name='us-west-1', endpoint_url=None, verify=None,296config=None)297)298299def test_set_client_with_source(self):300session = mock.Mock()301cmd_arc = CommandArchitecture(session, 'sync',302{'region': 'us-west-1',303'endpoint_url': None,304'verify_ssl': None,305'paths_type': 's3s3',306'source_region': 'us-west-2'})307cmd_arc.set_clients()308create_client_args = session.create_client.call_args_list309# Assert that two clients were created310self.assertEqual(len(create_client_args), 3)311self.assertEqual(312create_client_args[0][1],313{'region_name': 'us-west-1', 'verify': None, 'endpoint_url': None,314'config': None}315)316self.assertEqual(317create_client_args[1][1],318{'region_name': 'us-west-1', 'verify': None, 'endpoint_url': None,319'config': None}320)321# Assert override the second client created with the one needed for the322# source region.323self.assertEqual(324create_client_args[2][1],325{'region_name': 'us-west-2', 'verify': None, 'endpoint_url': None,326'config': None}327)328329def test_set_sigv4_clients_with_sse_kms(self):330session = mock.Mock()331cmd_arc = CommandArchitecture(332session, 'sync',333{'region': 'us-west-1', 'endpoint_url': None, 'verify_ssl': None,334'source_region': None, 'sse': 'aws:kms'})335cmd_arc.set_clients()336self.assertEqual( session.create_client.call_count, 2)337create_client_call = session.create_client.call_args_list[0]338create_source_client_call = session.create_client.call_args_list[1]339340# Make sure that both clients are using sigv4 if kms is enabled.341self.assertEqual(342create_client_call[1]['config'].signature_version, 's3v4')343self.assertEqual(344create_source_client_call[1]['config'].signature_version, 's3v4')345346def test_create_instructions(self):347"""348This tests to make sure the instructions for any command is generated349properly.350"""351cmds = ['cp', 'mv', 'rm', 'sync']352353instructions = {'cp': ['file_generator', 'file_info_builder',354's3_handler'],355'mv': ['file_generator', 'file_info_builder',356's3_handler'],357'rm': ['file_generator', 'file_info_builder',358's3_handler'],359'sync': ['file_generator', 'comparator',360'file_info_builder', 's3_handler']}361362params = {'filters': True, 'region': 'us-east-1', 'endpoint_url': None,363'verify_ssl': None, 'is_stream': False}364for cmd in cmds:365cmd_arc = CommandArchitecture(self.session, cmd,366{'region': 'us-east-1',367'endpoint_url': None,368'verify_ssl': None,369'is_stream': False})370cmd_arc.create_instructions()371self.assertEqual(cmd_arc.instructions, instructions[cmd])372373# Test if there is a filter.374cmd_arc = CommandArchitecture(self.session, 'cp', params)375cmd_arc.create_instructions()376self.assertEqual(cmd_arc.instructions, ['file_generator', 'filters',377'file_info_builder',378's3_handler'])379380def test_choose_sync_strategy_default(self):381session = mock.Mock()382cmd_arc = CommandArchitecture(session, 'sync',383{'region': 'us-east-1',384'endpoint_url': None,385'verify_ssl': None})386# Check if no plugins return their sync strategy. Should387# result in the default strategies388session.emit.return_value = None389sync_strategies = cmd_arc.choose_sync_strategies()390self.assertEqual(391sync_strategies['file_at_src_and_dest_sync_strategy'].__class__,392SizeAndLastModifiedSync393)394self.assertEqual(395sync_strategies['file_not_at_dest_sync_strategy'].__class__,396MissingFileSync397)398self.assertEqual(399sync_strategies['file_not_at_src_sync_strategy'].__class__,400NeverSync401)402403def test_choose_sync_strategy_overwrite(self):404session = mock.Mock()405cmd_arc = CommandArchitecture(session, 'sync',406{'region': 'us-east-1',407'endpoint_url': None,408'verify_ssl': None})409# Check that the default sync strategy is overwritten if a plugin410# returns its sync strategy.411mock_strategy = mock.Mock()412mock_strategy.sync_type = 'file_at_src_and_dest'413414mock_not_at_dest_sync_strategy = mock.Mock()415mock_not_at_dest_sync_strategy.sync_type = 'file_not_at_dest'416417mock_not_at_src_sync_strategy = mock.Mock()418mock_not_at_src_sync_strategy.sync_type = 'file_not_at_src'419420responses = [(None, mock_strategy),421(None, mock_not_at_dest_sync_strategy),422(None, mock_not_at_src_sync_strategy)]423424session.emit.return_value = responses425sync_strategies = cmd_arc.choose_sync_strategies()426self.assertEqual(427sync_strategies['file_at_src_and_dest_sync_strategy'],428mock_strategy429)430self.assertEqual(431sync_strategies['file_not_at_dest_sync_strategy'],432mock_not_at_dest_sync_strategy433)434self.assertEqual(435sync_strategies['file_not_at_src_sync_strategy'],436mock_not_at_src_sync_strategy437)438439def test_run_cp_put(self):440# This ensures that the architecture sets up correctly for a ``cp`` put441# command. It is just just a dry run, but all of the components need442# to be wired correctly for it to work.443s3_file = 's3://' + self.bucket + '/' + 'text1.txt'444local_file = self.loc_files[0]445rel_local_file = self._get_file_path(local_file)446filters = [['--include', '*']]447params = {'dir_op': False, 'dryrun': True, 'quiet': False,448'src': local_file, 'dest': s3_file, 'filters': filters,449'paths_type': 'locals3', 'region': 'us-east-1',450'endpoint_url': None, 'verify_ssl': None,451'follow_symlinks': True, 'page_size': None,452'is_stream': False, 'source_region': None, 'metadata': None,453'v2_debug': False}454config = RuntimeConfig().build_config()455cmd_arc = CommandArchitecture(self.session, 'cp', params, config)456cmd_arc.set_clients()457cmd_arc.create_instructions()458self.patch_make_request()459cmd_arc.run()460output_str = "(dryrun) upload: %s to %s" % (rel_local_file, s3_file)461self.assertIn(output_str, self.output.getvalue())462463def test_error_on_same_line_as_status(self):464s3_file = 's3://' + 'bucket-does-not-exist' + '/' + 'text1.txt'465local_file = self.loc_files[0]466rel_local_file = self._get_file_path(local_file)467filters = [['--include', '*']]468params = {'dir_op': False, 'dryrun': False, 'quiet': False,469'src': local_file, 'dest': s3_file, 'filters': filters,470'paths_type': 'locals3', 'region': 'us-east-1',471'endpoint_url': None, 'verify_ssl': None,472'follow_symlinks': True, 'page_size': None,473'is_stream': False, 'source_region': None, 'metadata': None,474'v2_debug': False}475self.http_response.status_code = 400476self.parsed_responses = [{'Error': {477'Code': 'BucketNotExists',478'Message': 'Bucket does not exist'}}]479cmd_arc = CommandArchitecture(480self.session, 'cp', params, RuntimeConfig().build_config())481cmd_arc.set_clients()482cmd_arc.create_instructions()483self.patch_make_request()484cmd_arc.run()485# Also, we need to verify that the error message is on the *same* line486# as the upload failed line, to make it easier to track.487output_str = (488"upload failed: %s to %s An error" % (489rel_local_file, s3_file))490self.assertIn(output_str, self.err_output.getvalue())491492def test_run_cp_get(self):493# This ensures that the architecture sets up correctly for a ``cp`` get494# command. It is just just a dry run, but all of the components need495# to be wired correctly for it to work.496s3_file = 's3://' + self.bucket + '/' + 'text1.txt'497local_file = self.loc_files[0]498rel_local_file = self._get_file_path(local_file)499filters = [['--include', '*']]500params = {'dir_op': False, 'dryrun': True, 'quiet': False,501'src': s3_file, 'dest': local_file, 'filters': filters,502'paths_type': 's3local', 'region': 'us-east-1',503'endpoint_url': None, 'verify_ssl': None,504'follow_symlinks': True, 'page_size': None,505'is_stream': False, 'source_region': None, 'v2_debug': False,506'case_conflict': 'ignore'}507self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100,508"LastModified": "2014-01-09T20:45:49.000Z"}]509config = RuntimeConfig().build_config()510cmd_arc = CommandArchitecture(self.session, 'cp', params, config)511cmd_arc.set_clients()512cmd_arc.create_instructions()513self.patch_make_request()514cmd_arc.run()515output_str = "(dryrun) download: %s to %s" % (s3_file, rel_local_file)516self.assertIn(output_str, self.output.getvalue())517518def test_run_cp_copy(self):519# This ensures that the architecture sets up correctly for a ``cp``520# copy command. It is just just a dry run, but all of the521# components need to be wired correctly for it to work.522s3_file = 's3://' + self.bucket + '/' + 'text1.txt'523filters = [['--include', '*']]524params = {'dir_op': False, 'dryrun': True, 'quiet': False,525'src': s3_file, 'dest': s3_file, 'filters': filters,526'paths_type': 's3s3', 'region': 'us-east-1',527'endpoint_url': None, 'verify_ssl': None,528'follow_symlinks': True, 'page_size': None,529'is_stream': False, 'source_region': None, 'v2_debug': False}530self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100,531"LastModified": "2014-01-09T20:45:49.000Z"}]532config = RuntimeConfig().build_config()533cmd_arc = CommandArchitecture(self.session, 'cp', params, config)534cmd_arc.set_clients()535cmd_arc.create_instructions()536self.patch_make_request()537cmd_arc.run()538output_str = "(dryrun) copy: %s to %s" % (s3_file, s3_file)539self.assertIn(output_str, self.output.getvalue())540541def test_run_mv(self):542# This ensures that the architecture sets up correctly for a ``mv``543# command. It is just just a dry run, but all of the components need544# to be wired correctly for it to work.545s3_file = 's3://' + self.bucket + '/' + 'text1.txt'546filters = [['--include', '*']]547params = {'dir_op': False, 'dryrun': True, 'quiet': False,548'src': s3_file, 'dest': s3_file, 'filters': filters,549'paths_type': 's3s3', 'region': 'us-east-1',550'endpoint_url': None, 'verify_ssl': None,551'follow_symlinks': True, 'page_size': None,552'is_stream': False, 'source_region': None,553'is_move': True, 'v2_debug': False}554self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100,555"LastModified": "2014-01-09T20:45:49.000Z"}]556config = RuntimeConfig().build_config()557cmd_arc = CommandArchitecture(self.session, 'mv', params, config)558cmd_arc.set_clients()559cmd_arc.create_instructions()560self.patch_make_request()561cmd_arc.run()562output_str = "(dryrun) move: %s to %s" % (s3_file, s3_file)563self.assertIn(output_str, self.output.getvalue())564565def test_run_remove(self):566# This ensures that the architecture sets up correctly for a ``rm``567# command. It is just just a dry run, but all of the components need568# to be wired correctly for it to work.569s3_file = 's3://' + self.bucket + '/' + 'text1.txt'570filters = [['--include', '*']]571params = {'dir_op': False, 'dryrun': True, 'quiet': False,572'src': s3_file, 'dest': s3_file, 'filters': filters,573'paths_type': 's3', 'region': 'us-east-1',574'endpoint_url': None, 'verify_ssl': None,575'follow_symlinks': True, 'page_size': None,576'is_stream': False, 'source_region': None,577'v2_debug': False}578self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100,579"LastModified": "2014-01-09T20:45:49.000Z"}]580config = RuntimeConfig().build_config()581cmd_arc = CommandArchitecture(self.session, 'rm', params, config)582cmd_arc.set_clients()583cmd_arc.create_instructions()584self.patch_make_request()585cmd_arc.run()586output_str = "(dryrun) delete: %s" % s3_file587self.assertIn(output_str, self.output.getvalue())588589def test_run_sync(self):590# This ensures that the architecture sets up correctly for a ``sync``591# command. It is just just a dry run, but all of the components need592# to be wired correctly for it to work.593s3_file = 's3://' + self.bucket + '/' + 'text1.txt'594local_file = self.loc_files[0]595s3_prefix = 's3://' + self.bucket + '/'596local_dir = self.loc_files[3]597rel_local_file = self._get_file_path(local_file)598filters = [['--include', '*']]599params = {'dir_op': True, 'dryrun': True, 'quiet': False,600'src': local_dir, 'dest': s3_prefix, 'filters': filters,601'paths_type': 'locals3', 'region': 'us-east-1',602'endpoint_url': None, 'verify_ssl': None,603'follow_symlinks': True, 'page_size': None,604'is_stream': False, 'source_region': 'us-west-2',605'v2_debug': False}606self.parsed_responses = [607{"CommonPrefixes": [], "Contents": [608{"Key": "text1.txt", "Size": 100,609"LastModified": "2014-01-09T20:45:49.000Z"}]},610{"CommonPrefixes": [], "Contents": []}]611config = RuntimeConfig().build_config()612cmd_arc = CommandArchitecture(self.session, 'sync', params, config)613cmd_arc.create_instructions()614cmd_arc.set_clients()615self.patch_make_request()616cmd_arc.run()617output_str = "(dryrun) upload: %s to %s" % (rel_local_file, s3_file)618self.assertIn(output_str, self.output.getvalue())619620def test_v2_debug_mv(self):621s3_file = 's3://' + self.bucket + '/' + 'text1.txt'622filters = [['--include', '*']]623params = {'dir_op': False, 'quiet': False, 'dryrun': True,624'src': s3_file, 'dest': s3_file, 'filters': filters,625'paths_type': 's3s3', 'region': 'us-east-1',626'endpoint_url': None, 'verify_ssl': None,627'follow_symlinks': True, 'page_size': None,628'is_stream': False, 'source_region': None,629'is_move': True, 'v2_debug': True}630self.parsed_responses = [{"ETag": "abcd", "ContentLength": 100,631"LastModified": "2014-01-09T20:45:49.000Z"}]632config = RuntimeConfig().build_config()633cmd_arc = CommandArchitecture(self.session, 'mv', params, config)634cmd_arc.set_clients()635cmd_arc.create_instructions()636self.patch_make_request()637cmd_arc.run()638warning_str = (639'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, object '640'properties will be copied from the source in '641'multipart copies between S3 buckets initiated via '642'`aws s3` commands'643)644output_str = f"(dryrun) move: {s3_file} to {s3_file}"645self.assertIn(warning_str, self.err_output.getvalue())646self.assertIn(output_str, self.output.getvalue())647648649class CommandParametersTest(unittest.TestCase):650def setUp(self):651self.environ = {}652self.environ_patch = mock.patch('os.environ', self.environ)653self.environ_patch.start()654self.mock = mock.MagicMock()655self.mock.get_config = mock.MagicMock(return_value={'region': None})656self.file_creator = FileCreator()657self.loc_files = make_loc_files(self.file_creator)658self.bucket = 's3testbucket'659self.session = mock.Mock()660self.parsed_global = FakeArgs(661region='us-west-2',662endpoint_url=None,663verify_ssl=False)664665def tearDown(self):666self.environ_patch.stop()667clean_loc_files(self.file_creator)668669def test_check_path_type_pass(self):670# This tests the class's ability to determine whether the correct671# path types have been passed for a particular command. It test every672# possible combination that is correct for every command.673cmds = {'cp': ['locals3', 's3s3', 's3local'],674'mv': ['locals3', 's3s3', 's3local'],675'rm': ['s3'], 'mb': ['s3'], 'rb': ['s3'],676'sync': ['locals3', 's3s3', 's3local']}677s3_file = 's3://' + self.bucket + '/' + 'text1.txt'678local_file = self.loc_files[0]679680combos = {'s3s3': [s3_file, s3_file],681's3local': [s3_file, local_file],682'locals3': [local_file, s3_file],683's3': [s3_file],684'local': [local_file],685'locallocal': [local_file, local_file]}686687for cmd in cmds.keys():688cmd_param = CommandParameters(cmd, {}, '',689self.session, self.parsed_global)690cmd_param.add_region(mock.Mock())691correct_paths = cmds[cmd]692for path_args in correct_paths:693cmd_param.check_path_type(combos[path_args])694695def test_check_path_type_fail(self):696# This tests the class's ability to determine whether the correct697# path types have been passed for a particular command. It test every698# possible combination that is incorrect for every command.699cmds = {'cp': ['local', 'locallocal', 's3'],700'mv': ['local', 'locallocal', 's3'],701'rm': ['local', 'locallocal', 's3s3', 'locals3', 's3local'],702'ls': ['local', 'locallocal', 's3s3', 'locals3', 's3local'],703'sync': ['local', 'locallocal', 's3'],704'mb': ['local', 'locallocal', 's3s3', 'locals3', 's3local'],705'rb': ['local', 'locallocal', 's3s3', 'locals3', 's3local']}706s3_file = 's3://' + self.bucket + '/' + 'text1.txt'707local_file = self.loc_files[0]708709combos = {'s3s3': [s3_file, s3_file],710's3local': [s3_file, local_file],711'locals3': [local_file, s3_file],712's3': [s3_file],713'local': [local_file],714'locallocal': [local_file, local_file]}715716for cmd in cmds.keys():717cmd_param = CommandParameters(cmd, {}, '',718self.session, self.parsed_global)719cmd_param.add_region(mock.Mock())720wrong_paths = cmds[cmd]721for path_args in wrong_paths:722with self.assertRaises(TypeError):723cmd_param.check_path_type(combos[path_args])724725def test_validate_streaming_paths_upload(self):726paths = ['-', 's3://bucket']727cmd_params = CommandParameters('cp', {}, '')728cmd_params.add_paths(paths)729self.assertTrue(cmd_params.parameters['is_stream'])730self.assertTrue(cmd_params.parameters['only_show_errors'])731self.assertFalse(cmd_params.parameters['dir_op'])732733def test_validate_streaming_paths_download(self):734paths = ['s3://bucket/key', '-']735cmd_params = CommandParameters('cp', {}, '')736cmd_params.add_paths(paths)737self.assertTrue(cmd_params.parameters['is_stream'])738self.assertTrue(cmd_params.parameters['only_show_errors'])739self.assertFalse(cmd_params.parameters['dir_op'])740741def test_validate_no_streaming_paths(self):742paths = [self.file_creator.rootdir, 's3://bucket']743cmd_params = CommandParameters('cp', {}, '')744cmd_params.add_paths(paths)745self.assertFalse(cmd_params.parameters['is_stream'])746747def test_validate_checksum_algorithm_download_error(self):748paths = ['s3://bucket/key', self.file_creator.rootdir]749parameters = {'checksum_algorithm': 'CRC32'}750cmd_params = CommandParameters('cp', parameters, '')751with self.assertRaises(ValueError) as cm:752cmd_params.add_paths(paths)753self.assertIn('Expected checksum-algorithm parameter to be used with one of following path formats', cm.msg)754755def test_validate_checksum_algorithm_sync_download_error(self):756paths = ['s3://bucket/key', self.file_creator.rootdir]757parameters = {'checksum_algorithm': 'CRC32C'}758cmd_params = CommandParameters('sync', parameters, '')759with self.assertRaises(ValueError) as cm:760cmd_params.add_paths(paths)761self.assertIn('Expected checksum-algorithm parameter to be used with one of following path formats', cm.msg)762763def test_validate_checksum_mode_upload_error(self):764paths = [self.file_creator.rootdir, 's3://bucket/key']765parameters = {'checksum_mode': 'ENABLED'}766cmd_params = CommandParameters('cp', parameters, '')767with self.assertRaises(ValueError) as cm:768cmd_params.add_paths(paths)769self.assertIn('Expected checksum-mode parameter to be used with one of following path formats', cm.msg)770771def test_validate_checksum_mode_sync_upload_error(self):772paths = [self.file_creator.rootdir, 's3://bucket/key']773parameters = {'checksum_mode': 'ENABLED'}774cmd_params = CommandParameters('sync', parameters, '')775with self.assertRaises(ValueError) as cm:776cmd_params.add_paths(paths)777self.assertIn('Expected checksum-mode parameter to be used with one of following path formats', cm.msg)778779def test_validate_checksum_mode_move_error(self):780paths = ['s3://bucket/key', 's3://bucket2/key']781parameters = {'checksum_mode': 'ENABLED'}782cmd_params = CommandParameters('mv', parameters, '')783with self.assertRaises(ValueError) as cm:784cmd_params.add_paths(paths)785self.assertIn('Expected checksum-mode parameter to be used with one of following path formats', cm.msg)786787def test_validate_streaming_paths_error(self):788parameters = {'src': '-', 'dest': 's3://bucket'}789cmd_params = CommandParameters('sync', parameters, '')790with self.assertRaises(ValueError):791cmd_params._validate_streaming_paths()792793def test_validate_non_existent_local_path_upload(self):794non_existent_path = os.path.join(self.file_creator.rootdir, 'foo')795paths = [non_existent_path, 's3://bucket/']796cmd_param = CommandParameters('cp', {}, '')797with self.assertRaises(RuntimeError):798cmd_param.add_paths(paths)799800def test_add_path_for_non_existsent_local_path_download(self):801non_existent_path = os.path.join(self.file_creator.rootdir, 'foo')802paths = ['s3://bucket', non_existent_path]803cmd_param = CommandParameters('cp', {'dir_op': True}, '')804cmd_param.add_paths(paths)805self.assertTrue(os.path.exists(non_existent_path))806807def test_validate_sse_c_args_missing_sse(self):808paths = ['s3://bucket/foo', 's3://bucket/bar']809params = {'dir_op': False, 'sse_c_key': 'foo'}810cmd_param = CommandParameters('cp', params, '')811with self.assertRaisesRegex(ValueError, '--sse-c must be specified'):812cmd_param.add_paths(paths)813814def test_validate_sse_c_args_missing_sse_c_key(self):815paths = ['s3://bucket/foo', 's3://bucket/bar']816params = {'dir_op': False, 'sse_c': 'AES256'}817cmd_param = CommandParameters('cp', params, '')818with self.assertRaisesRegex(ValueError,819'--sse-c-key must be specified'):820cmd_param.add_paths(paths)821822def test_validate_sse_c_args_missing_sse_c_copy_source(self):823paths = ['s3://bucket/foo', 's3://bucket/bar']824params = {'dir_op': False, 'sse_c_copy_source_key': 'foo'}825cmd_param = CommandParameters('cp', params, '')826with self.assertRaisesRegex(ValueError,827'--sse-c-copy-source must be specified'):828cmd_param.add_paths(paths)829830def test_validate_sse_c_args_missing_sse_c_copy_source_key(self):831paths = ['s3://bucket/foo', 's3://bucket/bar']832params = {'dir_op': False, 'sse_c_copy_source': 'AES256'}833cmd_param = CommandParameters('cp', params, '')834with self.assertRaisesRegex(ValueError,835'--sse-c-copy-source-key must be specified'):836cmd_param.add_paths(paths)837838def test_validate_sse_c_args_wrong_path_type(self):839paths = ['s3://bucket/foo', self.file_creator.rootdir]840params = {'dir_op': False, 'sse_c_copy_source': 'AES256',841'sse_c_copy_source_key': 'foo'}842cmd_param = CommandParameters('cp', params, '')843with self.assertRaisesRegex(ValueError,844'only supported for copy operations'):845cmd_param.add_paths(paths)846847def test_adds_is_move(self):848params = {}849CommandParameters('mv', params, '',850session=self.session,851parsed_globals=self.parsed_global)852self.assertTrue(params.get('is_move'))853854# is_move should only be true for mv855params = {}856CommandParameters('cp', params, '')857self.assertFalse(params.get('is_move'))858859860class HelpDocTest(BaseAWSHelpOutputTest):861def setUp(self):862super(HelpDocTest, self).setUp()863self.session = botocore.session.get_session()864865def tearDown(self):866super(HelpDocTest, self).tearDown()867868def test_s3_help(self):869# This tests the help command for the s3 service. This870# checks to make sure the appropriate descriptions are871# added including the tutorial.872s3 = S3(self.session)873parser = argparse.ArgumentParser()874parser.add_argument('--paginate', action='store_true')875parsed_global = parser.parse_args(['--paginate'])876help_command = s3.create_help_command()877help_command([], parsed_global)878self.assert_contains(879"This section explains prominent concepts "880"and notations in the set of high-level S3 commands provided.")881self.assert_contains("Every command takes one or two positional")882self.assert_contains("* rb")883884def test_s3command_help(self):885# This tests the help command for an s3 command. This886# checks to make sure the command prints appropriate887# parts. Note the examples are not included because888# the event was not registered.889s3command = CpCommand(self.session)890s3command._arg_table = s3command._build_arg_table()891parser = argparse.ArgumentParser()892parser.add_argument('--paginate', action='store_true')893parsed_global = parser.parse_args(['--paginate'])894help_command = s3command.create_help_command()895help_command([], parsed_global)896self.assert_contains("cp")897self.assert_contains("[--acl <value>]")898self.assert_contains("Displays the operations that would be")899900def test_help(self):901# This ensures that the file appropriately redirects to help object902# if help is the only argument left to be parsed. There should not903# have any contents in the docs.904s3_command = SyncCommand(self.session)905s3_command(['help'], [])906self.assert_contains('sync')907self.assert_contains("Synopsis")908909910if __name__ == "__main__":911unittest.main()912913914