Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/unit/customizations/test_paginate.py
1567 views
1
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
import pytest
14
15
from awscli.customizations.paginate import PageArgument
16
from awscli.testutils import mock, unittest
17
18
from botocore.exceptions import DataNotFoundError, PaginationError
19
from botocore.model import OperationModel
20
from awscli.help import OperationHelpCommand, OperationDocumentEventHandler
21
22
from awscli.customizations import paginate
23
24
@pytest.fixture
25
def max_items_page_arg():
26
return PageArgument('max-items', 'documentation', int, 'MaxItems')
27
28
class TestPaginateBase(unittest.TestCase):
29
30
def setUp(self):
31
self.session = mock.Mock()
32
self.paginator_model = mock.Mock()
33
self.pagination_config = {
34
'input_token': 'Foo',
35
'limit_key': 'Bar',
36
}
37
self.paginator_model.get_paginator.return_value = \
38
self.pagination_config
39
self.session.get_paginator_model.return_value = self.paginator_model
40
41
self.operation_model = mock.Mock()
42
self.foo_param = mock.Mock()
43
self.foo_param.name = 'Foo'
44
self.foo_param.type_name = 'string'
45
self.bar_param = mock.Mock()
46
self.bar_param.type_name = 'string'
47
self.bar_param.name = 'Bar'
48
self.params = [self.foo_param, self.bar_param]
49
self.operation_model.input_shape.members = {"Foo": self.foo_param,
50
"Bar": self.bar_param}
51
52
53
class TestArgumentTableModifications(TestPaginateBase):
54
55
def test_customize_arg_table(self):
56
argument_table = {
57
'foo': mock.Mock(),
58
'bar': mock.Mock(),
59
}
60
paginate.unify_paging_params(argument_table, self.operation_model,
61
'building-argument-table.foo.bar',
62
self.session)
63
# We should mark the built in input_token as 'hidden'.
64
self.assertTrue(argument_table['foo']._UNDOCUMENTED)
65
# Also need to hide the limit key.
66
self.assertTrue(argument_table['bar']._UNDOCUMENTED)
67
# We also need to inject starting-token and max-items.
68
self.assertIn('starting-token', argument_table)
69
self.assertIn('max-items', argument_table)
70
self.assertIn('page-size', argument_table)
71
# And these should be PageArguments.
72
self.assertIsInstance(argument_table['starting-token'],
73
paginate.PageArgument)
74
self.assertIsInstance(argument_table['max-items'],
75
paginate.PageArgument)
76
self.assertIsInstance(argument_table['page-size'],
77
paginate.PageArgument)
78
79
def test_operation_with_no_paginate(self):
80
# Operations that don't paginate are left alone.
81
self.paginator_model.get_paginator.side_effect = ValueError()
82
argument_table = {
83
'foo': 'FakeArgObject',
84
'bar': 'FakeArgObject',
85
}
86
starting_table = argument_table.copy()
87
paginate.unify_paging_params(argument_table, self.operation_model,
88
'building-argument-table.foo.bar',
89
self.session)
90
self.assertEqual(starting_table, argument_table)
91
92
def test_service_with_no_paginate(self):
93
# Operations that don't paginate are left alone.
94
self.session.get_paginator_model.side_effect = \
95
DataNotFoundError(data_path='foo.paginators.json')
96
argument_table = {
97
'foo': 'FakeArgObject',
98
'bar': 'FakeArgObject',
99
}
100
starting_table = argument_table.copy()
101
paginate.unify_paging_params(argument_table, self.operation_model,
102
'building-argument-table.foo.bar',
103
self.session)
104
self.assertEqual(starting_table, argument_table)
105
106
107
class TestHelpDocumentationModifications(TestPaginateBase):
108
def test_injects_pagination_help_text(self):
109
with mock.patch('awscli.customizations.paginate.get_paginator_config',
110
return_value={'result_key': 'abc'}):
111
help_command = OperationHelpCommand(
112
mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)
113
help_command.obj = mock.Mock(OperationModel)
114
help_command.obj.name = 'foo'
115
paginate.add_paging_description(help_command)
116
self.assertIn('``foo`` is a paginated operation. Multiple API',
117
help_command.doc.getvalue().decode())
118
self.assertIn('following query expressions: ``abc``',
119
help_command.doc.getvalue().decode())
120
121
def test_shows_result_keys_when_array(self):
122
with mock.patch('awscli.customizations.paginate.get_paginator_config',
123
return_value={'result_key': ['abc', '123']}):
124
help_command = OperationHelpCommand(
125
mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)
126
help_command.obj = mock.Mock(OperationModel)
127
help_command.obj.name = 'foo'
128
paginate.add_paging_description(help_command)
129
self.assertIn('following query expressions: ``abc``, ``123``',
130
help_command.doc.getvalue().decode())
131
132
def test_does_not_show_result_key_if_not_present(self):
133
with mock.patch('awscli.customizations.paginate.get_paginator_config',
134
return_value={'limit_key': 'aaa'}):
135
help_command = OperationHelpCommand(
136
mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)
137
help_command.obj = mock.Mock(OperationModel)
138
help_command.obj.name = 'foo'
139
paginate.add_paging_description(help_command)
140
self.assertIn('``foo`` is a paginated operation. Multiple API',
141
help_command.doc.getvalue().decode())
142
self.assertNotIn('following query expressions',
143
help_command.doc.getvalue().decode())
144
145
def test_does_not_inject_when_no_pagination(self):
146
with mock.patch('awscli.customizations.paginate.get_paginator_config',
147
return_value=None):
148
help_command = OperationHelpCommand(
149
mock.Mock(), mock.Mock(), mock.Mock(), 'foo', OperationDocumentEventHandler)
150
help_command.obj = mock.Mock(OperationModel)
151
help_command.obj.name = 'foo'
152
paginate.add_paging_description(help_command)
153
self.assertNotIn('``foo`` is a paginated operation',
154
help_command.doc.getvalue().decode())
155
156
157
class TestStringLimitKey(TestPaginateBase):
158
159
def setUp(self):
160
super(TestStringLimitKey, self).setUp()
161
self.bar_param.type_name = 'string'
162
163
def test_integer_limit_key(self):
164
argument_table = {
165
'foo': mock.Mock(),
166
'bar': mock.Mock(),
167
}
168
paginate.unify_paging_params(argument_table, self.operation_model,
169
'building-argument-table.foo.bar',
170
self.session)
171
# Max items should be the same type as bar, which may not be an int
172
self.assertEqual('string', argument_table['max-items'].cli_type_name)
173
174
175
class TestIntegerLimitKey(TestPaginateBase):
176
177
def setUp(self):
178
super(TestIntegerLimitKey, self).setUp()
179
self.bar_param.type_name = 'integer'
180
181
def test_integer_limit_key(self):
182
argument_table = {
183
'foo': mock.Mock(),
184
'bar': mock.Mock(),
185
}
186
paginate.unify_paging_params(argument_table, self.operation_model,
187
'building-argument-table.foo.bar',
188
self.session)
189
# Max items should be the same type as bar, which may not be an int
190
self.assertEqual('integer', argument_table['max-items'].cli_type_name)
191
192
193
class TestBadLimitKey(TestPaginateBase):
194
195
def setUp(self):
196
super(TestBadLimitKey, self).setUp()
197
self.bar_param.type_name = 'bad'
198
199
def test_integer_limit_key(self):
200
argument_table = {
201
'foo': mock.Mock(),
202
'bar': mock.Mock(),
203
}
204
with self.assertRaises(TypeError):
205
paginate.unify_paging_params(argument_table, self.operation_model,
206
'building-argument-table.foo.bar',
207
self.session)
208
209
210
class TestShouldEnablePagination(TestPaginateBase):
211
def setUp(self):
212
super(TestShouldEnablePagination, self).setUp()
213
self.parsed_globals = mock.Mock()
214
self.parsed_args = mock.Mock()
215
self.parsed_args.starting_token = None
216
self.parsed_args.page_size = None
217
self.parsed_args.max_items = None
218
219
def test_should_not_enable_pagination(self):
220
# Here the user has specified a manual pagination argument,
221
# so we should turn pagination off.
222
# From setUp(), the limit_key is 'Bar'
223
input_tokens = ['foo', 'bar']
224
self.parsed_globals.paginate = True
225
# Corresponds to --bar 10
226
self.parsed_args.foo = None
227
self.parsed_args.bar = 10
228
paginate.check_should_enable_pagination(
229
input_tokens, {}, {}, self.parsed_args, self.parsed_globals)
230
# We should have turned paginate off because the
231
# user specified --bar 10
232
self.assertFalse(self.parsed_globals.paginate)
233
234
def test_should_enable_pagination_with_no_args(self):
235
input_tokens = ['foo', 'bar']
236
self.parsed_globals.paginate = True
237
# Corresponds to not specifying --foo nor --bar
238
self.parsed_args.foo = None
239
self.parsed_args.bar = None
240
paginate.check_should_enable_pagination(
241
input_tokens, {}, {}, self.parsed_args, self.parsed_globals)
242
# We should have turned paginate off because the
243
# user specified --bar 10
244
self.assertTrue(self.parsed_globals.paginate)
245
246
def test_default_to_pagination_on_when_ambiguous(self):
247
input_tokens = ['foo', 'max-items']
248
self.parsed_globals.paginate = True
249
# Here the user specifies --max-items 10 This is ambiguous because the
250
# input_token also contains 'max-items'. Should we assume they want
251
# pagination turned off or should we assume that this is the normalized
252
# --max-items?
253
# Will we default to assuming they meant the normalized
254
# --max-items.
255
self.parsed_args.foo = None
256
self.parsed_args.max_items = 10
257
paginate.check_should_enable_pagination(
258
input_tokens, {}, {}, self.parsed_args, self.parsed_globals)
259
self.assertTrue(self.parsed_globals.paginate,
260
"Pagination was not enabled.")
261
262
def test_fall_back_to_original_max_items_when_pagination_turned_off(self):
263
input_tokens = ['max-items']
264
# User specifies --no-paginate.
265
self.parsed_globals.paginate = False
266
# But also specifies --max-items 10, which is normally a pagination arg
267
# we replace. However, because they've explicitly turned off
268
# pagination, we should put back the original arg.
269
self.parsed_args.max_items = 10
270
shadowed_args = {'max-items': mock.sentinel.ORIGINAL_ARG}
271
arg_table = {'max-items': mock.sentinel.PAGINATION_ARG}
272
273
paginate.check_should_enable_pagination(
274
input_tokens, shadowed_args, arg_table,
275
self.parsed_args, self.parsed_globals)
276
277
def test_shadowed_args_are_replaced_when_pagination_turned_off(self):
278
input_tokens = ['foo', 'bar']
279
self.parsed_globals.paginate = True
280
# Corresponds to --bar 10
281
self.parsed_args.foo = None
282
self.parsed_args.bar = 10
283
shadowed_args = {'foo': mock.sentinel.ORIGINAL_ARG}
284
arg_table = {'foo': mock.sentinel.PAGINATION_ARG}
285
paginate.check_should_enable_pagination(
286
input_tokens, shadowed_args, arg_table,
287
self.parsed_args, self.parsed_globals)
288
# We should have turned paginate off because the
289
# user specified --bar 10
290
self.assertFalse(self.parsed_globals.paginate)
291
self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG)
292
293
def test_shadowed_args_are_replaced_when_pagination_set_off(self):
294
input_tokens = ['foo', 'bar']
295
self.parsed_globals.paginate = False
296
# Corresponds to --bar 10
297
self.parsed_args.foo = None
298
self.parsed_args.bar = 10
299
shadowed_args = {'foo': mock.sentinel.ORIGINAL_ARG}
300
arg_table = {'foo': mock.sentinel.PAGINATION_ARG}
301
paginate.check_should_enable_pagination(
302
input_tokens, shadowed_args, arg_table,
303
self.parsed_args, self.parsed_globals)
304
# We should have turned paginate off because the
305
# user specified --bar 10
306
self.assertFalse(self.parsed_globals.paginate)
307
self.assertEqual(arg_table['foo'], mock.sentinel.ORIGINAL_ARG)
308
309
310
class TestEnsurePagingParamsNotSet(TestPaginateBase):
311
def setUp(self):
312
super(TestEnsurePagingParamsNotSet, self).setUp()
313
self.parsed_args = mock.Mock()
314
315
self.parsed_args.starting_token = None
316
self.parsed_args.page_size = None
317
self.parsed_args.max_items = None
318
319
def test_pagination_params_raise_error_with_no_paginate(self):
320
self.parsed_args.max_items = 100
321
322
with self.assertRaises(PaginationError):
323
paginate.ensure_paging_params_not_set(self.parsed_args, {})
324
325
def test_can_handle_missing_page_size(self):
326
# Not all pagination operations have a page_size.
327
del self.parsed_args.page_size
328
self.assertIsNone(paginate.ensure_paging_params_not_set(
329
self.parsed_args, {}))
330
331
332
class TestNonPositiveMaxItems:
333
def test_positive_integer_does_not_raise_warning(self, max_items_page_arg, capsys):
334
max_items_page_arg.add_to_params({}, 1)
335
captured = capsys.readouterr()
336
assert captured.err == ""
337
338
def test_zero_raises_warning(self, max_items_page_arg, capsys):
339
max_items_page_arg.add_to_params({}, 0)
340
captured = capsys.readouterr()
341
assert "Non-positive values for --max-items" in captured.err
342
343
def test_negative_integer_raises_warning(self, max_items_page_arg, capsys):
344
max_items_page_arg.add_to_params({}, -1)
345
captured = capsys.readouterr()
346
assert "Non-positive values for --max-items" in captured.err
347
348