Path: blob/develop/tests/unit/customizations/test_paginate.py
2627 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 functools import partial1314import pytest1516from awscli.customizations.paginate import PageArgument17from awscli.testutils import mock, unittest, capture_output1819from botocore.exceptions import DataNotFoundError, PaginationError20from botocore.model import OperationModel21from awscli.help import OperationHelpCommand, OperationDocumentEventHandler2223from awscli.customizations import paginate2425@pytest.fixture26def max_items_page_arg():27return PageArgument('max-items', 'documentation', int, 'MaxItems')2829class TestPaginateBase(unittest.TestCase):3031def setUp(self):32self.session = mock.Mock()33self.paginator_model = mock.Mock()34self.pagination_config = {35'input_token': 'Foo',36'limit_key': 'Bar',37}38self.paginator_model.get_paginator.return_value = \39self.pagination_config40self.session.get_paginator_model.return_value = self.paginator_model4142self.operation_model = mock.Mock()43self.foo_param = mock.Mock()44self.foo_param.name = 'Foo'45self.foo_param.type_name = 'string'46self.bar_param = mock.Mock()47self.bar_param.type_name = 'string'48self.bar_param.name = 'Bar'49self.params = [self.foo_param, self.bar_param]50self.operation_model.input_shape.members = {"Foo": self.foo_param,51"Bar": self.bar_param}525354class TestArgumentTableModifications(TestPaginateBase):5556def test_customize_arg_table(self):57argument_table = {58'foo': mock.Mock(),59'bar': mock.Mock(),60}61paginate.unify_paging_params(argument_table, self.operation_model,62'building-argument-table.foo.bar',63self.session)64# We should mark the built in input_token as 'hidden'.65self.assertTrue(argument_table['foo']._UNDOCUMENTED)66# Also need to hide the limit key.67self.assertTrue(argument_table['bar']._UNDOCUMENTED)68# We also need to inject starting-token and max-items.69self.assertIn('starting-token', argument_table)70self.assertIn('max-items', argument_table)71self.assertIn('page-size', argument_table)72# And these should be PageArguments.73self.assertIsInstance(argument_table['starting-token'],74paginate.PageArgument)75self.assertIsInstance(argument_table['max-items'],76paginate.PageArgument)77self.assertIsInstance(argument_table['page-size'],78paginate.PageArgument)7980def test_operation_with_no_paginate(self):81# Operations that don't paginate are left alone.82self.paginator_model.get_paginator.side_effect = ValueError()83argument_table = {84'foo': 'FakeArgObject',85'bar': 'FakeArgObject',86}87starting_table = argument_table.copy()88paginate.unify_paging_params(argument_table, self.operation_model,89'building-argument-table.foo.bar',90self.session)91self.assertEqual(starting_table, argument_table)9293def test_service_with_no_paginate(self):94# Operations that don't paginate are left alone.95self.session.get_paginator_model.side_effect = \96DataNotFoundError(data_path='foo.paginators.json')97argument_table = {98'foo': 'FakeArgObject',99'bar': 'FakeArgObject',100}101starting_table = argument_table.copy()102paginate.unify_paging_params(argument_table, self.operation_model,103'building-argument-table.foo.bar',104self.session)105self.assertEqual(starting_table, argument_table)106107108class TestHelpDocumentationModifications(TestPaginateBase):109def test_injects_pagination_help_text(self):110with mock.patch('awscli.customizations.paginate.get_paginator_config',111return_value={'result_key': 'abc'}):112help_command = OperationHelpCommand(113mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)114help_command.obj = mock.Mock(OperationModel)115help_command.obj.name = 'foo'116paginate.add_paging_description(help_command)117self.assertIn('``foo`` is a paginated operation. Multiple API',118help_command.doc.getvalue().decode())119self.assertIn('following query expressions: ``abc``',120help_command.doc.getvalue().decode())121122def test_shows_result_keys_when_array(self):123with mock.patch('awscli.customizations.paginate.get_paginator_config',124return_value={'result_key': ['abc', '123']}):125help_command = OperationHelpCommand(126mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)127help_command.obj = mock.Mock(OperationModel)128help_command.obj.name = 'foo'129paginate.add_paging_description(help_command)130self.assertIn('following query expressions: ``abc``, ``123``',131help_command.doc.getvalue().decode())132133def test_does_not_show_result_key_if_not_present(self):134with mock.patch('awscli.customizations.paginate.get_paginator_config',135return_value={'limit_key': 'aaa'}):136help_command = OperationHelpCommand(137mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)138help_command.obj = mock.Mock(OperationModel)139help_command.obj.name = 'foo'140paginate.add_paging_description(help_command)141self.assertIn('``foo`` is a paginated operation. Multiple API',142help_command.doc.getvalue().decode())143self.assertNotIn('following query expressions',144help_command.doc.getvalue().decode())145146def test_does_not_inject_when_no_pagination(self):147with mock.patch('awscli.customizations.paginate.get_paginator_config',148return_value=None):149help_command = OperationHelpCommand(150mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)151help_command.obj = mock.Mock(OperationModel)152help_command.obj.name = 'foo'153paginate.add_paging_description(help_command)154self.assertNotIn('``foo`` is a paginated operation',155help_command.doc.getvalue().decode())156157158class TestStringLimitKey(TestPaginateBase):159160def setUp(self):161super(TestStringLimitKey, self).setUp()162self.bar_param.type_name = 'string'163164def test_integer_limit_key(self):165argument_table = {166'foo': mock.Mock(),167'bar': mock.Mock(),168}169paginate.unify_paging_params(argument_table, self.operation_model,170'building-argument-table.foo.bar',171self.session)172# Max items should be the same type as bar, which may not be an int173self.assertEqual('string', argument_table['max-items'].cli_type_name)174175176class TestIntegerLimitKey(TestPaginateBase):177178def setUp(self):179super(TestIntegerLimitKey, self).setUp()180self.bar_param.type_name = 'integer'181182def test_integer_limit_key(self):183argument_table = {184'foo': mock.Mock(),185'bar': mock.Mock(),186}187paginate.unify_paging_params(argument_table, self.operation_model,188'building-argument-table.foo.bar',189self.session)190# Max items should be the same type as bar, which may not be an int191self.assertEqual('integer', argument_table['max-items'].cli_type_name)192193194class TestBadLimitKey(TestPaginateBase):195196def setUp(self):197super(TestBadLimitKey, self).setUp()198self.bar_param.type_name = 'bad'199200def test_integer_limit_key(self):201argument_table = {202'foo': mock.Mock(),203'bar': mock.Mock(),204}205with self.assertRaises(TypeError):206paginate.unify_paging_params(argument_table, self.operation_model,207'building-argument-table.foo.bar',208self.session)209210211class TestShouldEnablePagination(TestPaginateBase):212def setUp(self):213super(TestShouldEnablePagination, self).setUp()214self.parsed_globals = mock.Mock()215self.parsed_args = mock.Mock()216self.parsed_args.starting_token = None217self.parsed_args.page_size = None218self.parsed_args.max_items = None219self.call_parameters = {}220221def test_should_not_enable_pagination(self):222# Here the user has specified a manual pagination argument,223# so we should turn pagination off.224# From setUp(), the limit_key is 'Bar'225input_tokens = ['foo', 'bar']226self.parsed_globals.paginate = True227# Corresponds to --bar 10228self.parsed_args.foo = None229self.parsed_args.bar = 10230paginate.check_should_enable_pagination(231input_tokens, {}, {}, self.parsed_args, self.parsed_globals)232# We should have turned paginate off because the233# user specified --bar 10234self.assertFalse(self.parsed_globals.paginate)235236def test_should_enable_pagination_with_no_args(self):237input_tokens = ['foo', 'bar']238self.parsed_globals.paginate = True239# Corresponds to not specifying --foo nor --bar240self.parsed_args.foo = None241self.parsed_args.bar = None242paginate.check_should_enable_pagination(243input_tokens, {}, {}, self.parsed_args, self.parsed_globals)244# We should have turned paginate off because the245# user specified --bar 10246self.assertTrue(self.parsed_globals.paginate)247248def test_default_to_pagination_on_when_ambiguous(self):249input_tokens = ['foo', 'max-items']250self.parsed_globals.paginate = True251# Here the user specifies --max-items 10 This is ambiguous because the252# input_token also contains 'max-items'. Should we assume they want253# pagination turned off or should we assume that this is the normalized254# --max-items?255# Will we default to assuming they meant the normalized256# --max-items.257self.parsed_args.foo = None258self.parsed_args.max_items = 10259paginate.check_should_enable_pagination(260input_tokens, {}, {}, self.parsed_args, self.parsed_globals)261self.assertTrue(self.parsed_globals.paginate,262"Pagination was not enabled.")263264def test_fall_back_to_original_max_items_when_pagination_turned_off(self):265input_tokens = ['max-items']266# User specifies --no-paginate.267self.parsed_globals.paginate = False268# But also specifies --max-items 10, which is normally a pagination arg269# we replace. However, because they've explicitly turned off270# pagination, we should put back the original arg.271self.parsed_args.max_items = 10272shadowed_args = {'max-items': mock.sentinel.ORIGINAL_ARG}273arg_table = {'max-items': mock.sentinel.PAGINATION_ARG}274275paginate.check_should_enable_pagination(276input_tokens, shadowed_args, arg_table,277self.parsed_args, self.parsed_globals)278279def test_shadowed_args_are_replaced_when_pagination_turned_off(self):280input_tokens = ['foo', 'bar']281self.parsed_globals.paginate = True282# Corresponds to --bar 10283self.parsed_args.foo = None284self.parsed_args.bar = 10285shadowed_args = {'foo': mock.sentinel.ORIGINAL_ARG}286arg_table = {'foo': mock.sentinel.PAGINATION_ARG}287paginate.check_should_enable_pagination(288input_tokens, shadowed_args, arg_table,289self.parsed_args, self.parsed_globals)290# We should have turned paginate off because the291# user specified --bar 10292self.assertFalse(self.parsed_globals.paginate)293self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG)294295def test_shadowed_args_are_replaced_when_pagination_set_off(self):296input_tokens = ['foo', 'bar']297self.parsed_globals.paginate = False298# Corresponds to --bar 10299self.parsed_args.foo = None300self.parsed_args.bar = 10301shadowed_args = {'foo': mock.sentinel.ORIGINAL_ARG}302arg_table = {'foo': mock.sentinel.PAGINATION_ARG}303paginate.check_should_enable_pagination(304input_tokens, shadowed_args, arg_table,305self.parsed_args, self.parsed_globals)306# We should have turned paginate off because the307# user specified --bar 10308self.assertFalse(self.parsed_globals.paginate)309self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG)310311312class TestPaginateV2Debug(TestPaginateBase):313def setUp(self):314super().setUp()315self.parsed_globals = mock.Mock()316self.parsed_args = mock.Mock()317self.parsed_args.starting_token = None318self.parsed_args.page_size = None319self.parsed_args.max_items = None320self.call_parameters = {}321322def _mock_emit_first_non_none_response(323self,324mock_input_json_data,325event_name326):327if event_name == 'get-cli-input-json-data':328return mock_input_json_data329return None330331def test_v2_debug_call_parameters(self):332# Here the user has specified a manual pagination argument,333# via CLI Input JSON and specified v2-debug, so a334# migration warning should be printed.335# From setUp(), the limit_key is 'Bar'336input_tokens = ['Foo', 'Bar']337self.parsed_globals.v2_debug = True338self.parsed_globals.paginate = True339self.session.emit_first_non_none_response.side_effect = partial(340self._mock_emit_first_non_none_response,341{'Bar': 10}342)343# Corresponds to --bar 10344self.call_parameters['Foo'] = None345self.call_parameters['Bar'] = 10346with capture_output() as output:347paginate.check_should_enable_pagination_call_parameters(348self.session,349input_tokens,350self.call_parameters,351{},352self.parsed_globals353)354# We should have printed the migration warning355# because the user specified {Bar: 10} in the input JSON356self.assertIn(357'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify '358'pagination parameters by using a file with the '359'`--cli-input-json` parameter, automatic pagination will be '360'turned off.',361output.stderr.getvalue()362)363364def test_v2_debug_call_params_does_not_print_for_cmd_args(self):365# Here the user has specified a pagination argument as a command366# argument and specified v2-debug, so the migration warning should NOT367# be printed. From setUp(), the limit_key is 'Bar'368input_tokens = ['Foo', 'Bar']369self.parsed_globals.v2_debug = True370self.parsed_globals.paginate = True371self.session.emit_first_non_none_response.side_effect = partial(372self._mock_emit_first_non_none_response,373None374)375# Corresponds to --bar 10376self.call_parameters['Foo'] = None377self.call_parameters['Bar'] = 10378with capture_output() as output:379paginate.check_should_enable_pagination_call_parameters(380self.session,381input_tokens,382self.call_parameters,383{},384self.parsed_globals385)386# We should not have printed the warning because387# the user did not specify any params through CLI input JSON388self.assertNotIn(389'AWS CLI v2 UPGRADE WARNING: In AWS CLI v2, if you specify '390'pagination parameters by using a file with the '391'`--cli-input-json` parameter, automatic pagination will be '392'turned off.',393output.stderr.getvalue()394)395396397class TestEnsurePagingParamsNotSet(TestPaginateBase):398def setUp(self):399super(TestEnsurePagingParamsNotSet, self).setUp()400self.parsed_args = mock.Mock()401402self.parsed_args.starting_token = None403self.parsed_args.page_size = None404self.parsed_args.max_items = None405406def test_pagination_params_raise_error_with_no_paginate(self):407self.parsed_args.max_items = 100408409with self.assertRaises(PaginationError):410paginate.ensure_paging_params_not_set(self.parsed_args, {})411412def test_can_handle_missing_page_size(self):413# Not all pagination operations have a page_size.414del self.parsed_args.page_size415self.assertIsNone(paginate.ensure_paging_params_not_set(416self.parsed_args, {}))417418419class TestNonPositiveMaxItems:420def test_positive_integer_does_not_raise_warning(self, max_items_page_arg, capsys):421max_items_page_arg.add_to_params({}, 1)422captured = capsys.readouterr()423assert captured.err == ""424425def test_zero_raises_warning(self, max_items_page_arg, capsys):426max_items_page_arg.add_to_params({}, 0)427captured = capsys.readouterr()428assert "Non-positive values for --max-items" in captured.err429430def test_negative_integer_raises_warning(self, max_items_page_arg, capsys):431max_items_page_arg.add_to_params({}, -1)432captured = capsys.readouterr()433assert "Non-positive values for --max-items" in captured.err434435436