Path: blob/develop/tests/unit/customizations/test_paginate.py
1567 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.12import pytest1314from awscli.customizations.paginate import PageArgument15from awscli.testutils import mock, unittest1617from botocore.exceptions import DataNotFoundError, PaginationError18from botocore.model import OperationModel19from awscli.help import OperationHelpCommand, OperationDocumentEventHandler2021from awscli.customizations import paginate2223@pytest.fixture24def max_items_page_arg():25return PageArgument('max-items', 'documentation', int, 'MaxItems')2627class TestPaginateBase(unittest.TestCase):2829def setUp(self):30self.session = mock.Mock()31self.paginator_model = mock.Mock()32self.pagination_config = {33'input_token': 'Foo',34'limit_key': 'Bar',35}36self.paginator_model.get_paginator.return_value = \37self.pagination_config38self.session.get_paginator_model.return_value = self.paginator_model3940self.operation_model = mock.Mock()41self.foo_param = mock.Mock()42self.foo_param.name = 'Foo'43self.foo_param.type_name = 'string'44self.bar_param = mock.Mock()45self.bar_param.type_name = 'string'46self.bar_param.name = 'Bar'47self.params = [self.foo_param, self.bar_param]48self.operation_model.input_shape.members = {"Foo": self.foo_param,49"Bar": self.bar_param}505152class TestArgumentTableModifications(TestPaginateBase):5354def test_customize_arg_table(self):55argument_table = {56'foo': mock.Mock(),57'bar': mock.Mock(),58}59paginate.unify_paging_params(argument_table, self.operation_model,60'building-argument-table.foo.bar',61self.session)62# We should mark the built in input_token as 'hidden'.63self.assertTrue(argument_table['foo']._UNDOCUMENTED)64# Also need to hide the limit key.65self.assertTrue(argument_table['bar']._UNDOCUMENTED)66# We also need to inject starting-token and max-items.67self.assertIn('starting-token', argument_table)68self.assertIn('max-items', argument_table)69self.assertIn('page-size', argument_table)70# And these should be PageArguments.71self.assertIsInstance(argument_table['starting-token'],72paginate.PageArgument)73self.assertIsInstance(argument_table['max-items'],74paginate.PageArgument)75self.assertIsInstance(argument_table['page-size'],76paginate.PageArgument)7778def test_operation_with_no_paginate(self):79# Operations that don't paginate are left alone.80self.paginator_model.get_paginator.side_effect = ValueError()81argument_table = {82'foo': 'FakeArgObject',83'bar': 'FakeArgObject',84}85starting_table = argument_table.copy()86paginate.unify_paging_params(argument_table, self.operation_model,87'building-argument-table.foo.bar',88self.session)89self.assertEqual(starting_table, argument_table)9091def test_service_with_no_paginate(self):92# Operations that don't paginate are left alone.93self.session.get_paginator_model.side_effect = \94DataNotFoundError(data_path='foo.paginators.json')95argument_table = {96'foo': 'FakeArgObject',97'bar': 'FakeArgObject',98}99starting_table = argument_table.copy()100paginate.unify_paging_params(argument_table, self.operation_model,101'building-argument-table.foo.bar',102self.session)103self.assertEqual(starting_table, argument_table)104105106class TestHelpDocumentationModifications(TestPaginateBase):107def test_injects_pagination_help_text(self):108with mock.patch('awscli.customizations.paginate.get_paginator_config',109return_value={'result_key': 'abc'}):110help_command = OperationHelpCommand(111mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)112help_command.obj = mock.Mock(OperationModel)113help_command.obj.name = 'foo'114paginate.add_paging_description(help_command)115self.assertIn('``foo`` is a paginated operation. Multiple API',116help_command.doc.getvalue().decode())117self.assertIn('following query expressions: ``abc``',118help_command.doc.getvalue().decode())119120def test_shows_result_keys_when_array(self):121with mock.patch('awscli.customizations.paginate.get_paginator_config',122return_value={'result_key': ['abc', '123']}):123help_command = OperationHelpCommand(124mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)125help_command.obj = mock.Mock(OperationModel)126help_command.obj.name = 'foo'127paginate.add_paging_description(help_command)128self.assertIn('following query expressions: ``abc``, ``123``',129help_command.doc.getvalue().decode())130131def test_does_not_show_result_key_if_not_present(self):132with mock.patch('awscli.customizations.paginate.get_paginator_config',133return_value={'limit_key': 'aaa'}):134help_command = OperationHelpCommand(135mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)136help_command.obj = mock.Mock(OperationModel)137help_command.obj.name = 'foo'138paginate.add_paging_description(help_command)139self.assertIn('``foo`` is a paginated operation. Multiple API',140help_command.doc.getvalue().decode())141self.assertNotIn('following query expressions',142help_command.doc.getvalue().decode())143144def test_does_not_inject_when_no_pagination(self):145with mock.patch('awscli.customizations.paginate.get_paginator_config',146return_value=None):147help_command = OperationHelpCommand(148mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)149help_command.obj = mock.Mock(OperationModel)150help_command.obj.name = 'foo'151paginate.add_paging_description(help_command)152self.assertNotIn('``foo`` is a paginated operation',153help_command.doc.getvalue().decode())154155156class TestStringLimitKey(TestPaginateBase):157158def setUp(self):159super(TestStringLimitKey, self).setUp()160self.bar_param.type_name = 'string'161162def test_integer_limit_key(self):163argument_table = {164'foo': mock.Mock(),165'bar': mock.Mock(),166}167paginate.unify_paging_params(argument_table, self.operation_model,168'building-argument-table.foo.bar',169self.session)170# Max items should be the same type as bar, which may not be an int171self.assertEqual('string', argument_table['max-items'].cli_type_name)172173174class TestIntegerLimitKey(TestPaginateBase):175176def setUp(self):177super(TestIntegerLimitKey, self).setUp()178self.bar_param.type_name = 'integer'179180def test_integer_limit_key(self):181argument_table = {182'foo': mock.Mock(),183'bar': mock.Mock(),184}185paginate.unify_paging_params(argument_table, self.operation_model,186'building-argument-table.foo.bar',187self.session)188# Max items should be the same type as bar, which may not be an int189self.assertEqual('integer', argument_table['max-items'].cli_type_name)190191192class TestBadLimitKey(TestPaginateBase):193194def setUp(self):195super(TestBadLimitKey, self).setUp()196self.bar_param.type_name = 'bad'197198def test_integer_limit_key(self):199argument_table = {200'foo': mock.Mock(),201'bar': mock.Mock(),202}203with self.assertRaises(TypeError):204paginate.unify_paging_params(argument_table, self.operation_model,205'building-argument-table.foo.bar',206self.session)207208209class TestShouldEnablePagination(TestPaginateBase):210def setUp(self):211super(TestShouldEnablePagination, self).setUp()212self.parsed_globals = mock.Mock()213self.parsed_args = mock.Mock()214self.parsed_args.starting_token = None215self.parsed_args.page_size = None216self.parsed_args.max_items = None217218def test_should_not_enable_pagination(self):219# Here the user has specified a manual pagination argument,220# so we should turn pagination off.221# From setUp(), the limit_key is 'Bar'222input_tokens = ['foo', 'bar']223self.parsed_globals.paginate = True224# Corresponds to --bar 10225self.parsed_args.foo = None226self.parsed_args.bar = 10227paginate.check_should_enable_pagination(228input_tokens, {}, {}, self.parsed_args, self.parsed_globals)229# We should have turned paginate off because the230# user specified --bar 10231self.assertFalse(self.parsed_globals.paginate)232233def test_should_enable_pagination_with_no_args(self):234input_tokens = ['foo', 'bar']235self.parsed_globals.paginate = True236# Corresponds to not specifying --foo nor --bar237self.parsed_args.foo = None238self.parsed_args.bar = None239paginate.check_should_enable_pagination(240input_tokens, {}, {}, self.parsed_args, self.parsed_globals)241# We should have turned paginate off because the242# user specified --bar 10243self.assertTrue(self.parsed_globals.paginate)244245def test_default_to_pagination_on_when_ambiguous(self):246input_tokens = ['foo', 'max-items']247self.parsed_globals.paginate = True248# Here the user specifies --max-items 10 This is ambiguous because the249# input_token also contains 'max-items'. Should we assume they want250# pagination turned off or should we assume that this is the normalized251# --max-items?252# Will we default to assuming they meant the normalized253# --max-items.254self.parsed_args.foo = None255self.parsed_args.max_items = 10256paginate.check_should_enable_pagination(257input_tokens, {}, {}, self.parsed_args, self.parsed_globals)258self.assertTrue(self.parsed_globals.paginate,259"Pagination was not enabled.")260261def test_fall_back_to_original_max_items_when_pagination_turned_off(self):262input_tokens = ['max-items']263# User specifies --no-paginate.264self.parsed_globals.paginate = False265# But also specifies --max-items 10, which is normally a pagination arg266# we replace. However, because they've explicitly turned off267# pagination, we should put back the original arg.268self.parsed_args.max_items = 10269shadowed_args = {'max-items': mock.sentinel.ORIGINAL_ARG}270arg_table = {'max-items': mock.sentinel.PAGINATION_ARG}271272paginate.check_should_enable_pagination(273input_tokens, shadowed_args, arg_table,274self.parsed_args, self.parsed_globals)275276def test_shadowed_args_are_replaced_when_pagination_turned_off(self):277input_tokens = ['foo', 'bar']278self.parsed_globals.paginate = True279# Corresponds to --bar 10280self.parsed_args.foo = None281self.parsed_args.bar = 10282shadowed_args = {'foo': mock.sentinel.ORIGINAL_ARG}283arg_table = {'foo': mock.sentinel.PAGINATION_ARG}284paginate.check_should_enable_pagination(285input_tokens, shadowed_args, arg_table,286self.parsed_args, self.parsed_globals)287# We should have turned paginate off because the288# user specified --bar 10289self.assertFalse(self.parsed_globals.paginate)290self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG)291292def test_shadowed_args_are_replaced_when_pagination_set_off(self):293input_tokens = ['foo', 'bar']294self.parsed_globals.paginate = False295# Corresponds to --bar 10296self.parsed_args.foo = None297self.parsed_args.bar = 10298shadowed_args = {'foo': mock.sentinel.ORIGINAL_ARG}299arg_table = {'foo': mock.sentinel.PAGINATION_ARG}300paginate.check_should_enable_pagination(301input_tokens, shadowed_args, arg_table,302self.parsed_args, self.parsed_globals)303# We should have turned paginate off because the304# user specified --bar 10305self.assertFalse(self.parsed_globals.paginate)306self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG)307308309class TestEnsurePagingParamsNotSet(TestPaginateBase):310def setUp(self):311super(TestEnsurePagingParamsNotSet, self).setUp()312self.parsed_args = mock.Mock()313314self.parsed_args.starting_token = None315self.parsed_args.page_size = None316self.parsed_args.max_items = None317318def test_pagination_params_raise_error_with_no_paginate(self):319self.parsed_args.max_items = 100320321with self.assertRaises(PaginationError):322paginate.ensure_paging_params_not_set(self.parsed_args, {})323324def test_can_handle_missing_page_size(self):325# Not all pagination operations have a page_size.326del self.parsed_args.page_size327self.assertIsNone(paginate.ensure_paging_params_not_set(328self.parsed_args, {}))329330331class TestNonPositiveMaxItems:332def test_positive_integer_does_not_raise_warning(self, max_items_page_arg, capsys):333max_items_page_arg.add_to_params({}, 1)334captured = capsys.readouterr()335assert captured.err == ""336337def test_zero_raises_warning(self, max_items_page_arg, capsys):338max_items_page_arg.add_to_params({}, 0)339captured = capsys.readouterr()340assert "Non-positive values for --max-items" in captured.err341342def test_negative_integer_raises_warning(self, max_items_page_arg, capsys):343max_items_page_arg.add_to_params({}, -1)344captured = capsys.readouterr()345assert "Non-positive values for --max-items" in captured.err346347348