Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/unit/test_argprocess.py
2616 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 json
14
15
from botocore import xform_name
16
from botocore import model
17
from botocore.compat import OrderedDict
18
19
from awscli.testutils import mock
20
from awscli.testutils import unittest
21
from awscli.testutils import BaseCLIDriverTest
22
from awscli.testutils import temporary_file
23
from awscli.help import OperationHelpCommand
24
from awscli.argprocess import detect_shape_structure
25
from awscli.argprocess import unpack_cli_arg
26
from awscli.argprocess import ParamShorthandParser
27
from awscli.argprocess import ParamShorthandDocGen
28
from awscli.argprocess import ParamError
29
from awscli.argprocess import ParamUnknownKeyError
30
from awscli.paramfile import URIArgumentHandler
31
from awscli.arguments import CustomArgument, CLIArgument
32
from awscli.arguments import ListArgument, BooleanArgument
33
from awscli.arguments import create_argument_model_from_schema
34
35
from awscli.argparser import ArgTableArgParser
36
37
# These tests use real service types so that we can
38
# verify the real shapes of services.
39
class BaseArgProcessTest(BaseCLIDriverTest):
40
41
def get_param_model(self, dotted_name):
42
service_name, operation_name, param_name = dotted_name.split('.')
43
service_model = self.session.get_service_model(service_name)
44
operation = service_model.operation_model(operation_name)
45
input_shape = operation.input_shape
46
required_arguments = input_shape.required_members
47
is_required = param_name in required_arguments
48
member_shape = input_shape.members[param_name]
49
type_name = member_shape.type_name
50
cli_arg_name = xform_name(param_name, '-')
51
if type_name == 'boolean':
52
cls = BooleanArgument
53
elif type_name == 'list':
54
cls = ListArgument
55
else:
56
cls = CLIArgument
57
return cls(cli_arg_name, member_shape, mock.Mock(), is_required)
58
59
def create_argument(self, argument_model, argument_name=None):
60
if argument_name is None:
61
argument_name = 'foo'
62
argument = mock.Mock()
63
m = model.DenormalizedStructureBuilder().with_members(
64
argument_model)
65
argument.argument_model = m.build_model()
66
argument.name = argument_name
67
argument.cli_name = "--" + argument_name
68
return argument
69
70
71
class TestURIParams(BaseArgProcessTest):
72
def setUp(self):
73
super(TestURIParams, self).setUp()
74
self.uri_param = URIArgumentHandler()
75
76
def test_uri_param(self):
77
p = self.get_param_model('ec2.DescribeInstances.Filters')
78
with temporary_file('r+') as f:
79
json_argument = json.dumps(
80
[{"Name": "instance-id", "Values": ["i-1234"]}]
81
)
82
f.write(json_argument)
83
f.flush()
84
result = self.uri_param('event-name', p, 'file://%s' % f.name, mock.Mock())
85
self.assertEqual(result, json_argument)
86
87
def test_uri_param_no_paramfile_false(self):
88
p = self.get_param_model('ec2.DescribeInstances.Filters')
89
p.no_paramfile = False
90
with temporary_file('r+') as f:
91
json_argument = json.dumps([{"Name": "instance-id", "Values": ["i-1234"]}])
92
f.write(json_argument)
93
f.flush()
94
result = self.uri_param('event-name', p, 'file://%s' % f.name, mock.Mock())
95
self.assertEqual(result, json_argument)
96
97
def test_uri_param_no_paramfile_true(self):
98
p = self.get_param_model('ec2.DescribeInstances.Filters')
99
p.no_paramfile = True
100
with temporary_file('r+') as f:
101
json_argument = json.dumps([{"Name": "instance-id", "Values": ["i-1234"]}])
102
f.write(json_argument)
103
f.flush()
104
result = self.uri_param('event-name', p, 'file://%s' % f.name, mock.Mock())
105
self.assertEqual(result, None)
106
107
108
class TestArgShapeDetection(BaseArgProcessTest):
109
110
def assert_shape_type(self, spec, expected_type):
111
p = self.get_param_model(spec)
112
actual_structure = detect_shape_structure(p.argument_model)
113
self.assertEqual(actual_structure, expected_type)
114
115
def assert_custom_shape_type(self, schema, expected_type):
116
argument_model = create_argument_model_from_schema(schema)
117
argument = CustomArgument('test', argument_model=argument_model)
118
actual_structure = detect_shape_structure(argument.argument_model)
119
self.assertEqual(actual_structure, expected_type)
120
121
def test_detect_scalar(self):
122
self.assert_shape_type('iam.AddRoleToInstanceProfile.RoleName',
123
'scalar')
124
125
def test_detect_list_of_strings(self):
126
self.assert_shape_type('sns.AddPermission.AWSAccountId', 'list-scalar')
127
128
def test_detect_structure_of_scalars(self):
129
self.assert_shape_type(
130
'elasticbeanstalk.CreateConfigurationTemplate.SourceConfiguration',
131
'structure(scalars)')
132
133
def test_list_structure_scalars(self):
134
self.assert_shape_type(
135
'elb.RegisterInstancesWithLoadBalancer.Instances',
136
'list-structure(scalar)')
137
138
def test_list_structure_scalars_2(self):
139
self.assert_shape_type(
140
'elb.CreateLoadBalancer.Listeners',
141
'list-structure(scalars)')
142
143
def test_list_structure_of_list_and_strings(self):
144
self.assert_shape_type(
145
'ec2.DescribeInstances.Filters', 'list-structure(list-scalar, scalar)')
146
147
def test_map_scalar(self):
148
self.assert_shape_type(
149
'sqs.SetQueueAttributes.Attributes', 'map-scalar')
150
151
def test_struct_list_scalar(self):
152
self.assert_custom_shape_type({
153
"type": "object",
154
"properties": {
155
"Consistent": {
156
"type": "boolean",
157
},
158
"Args": {
159
"type": "array",
160
"items": {
161
"type": "string"
162
}
163
}
164
}
165
}, 'structure(list-scalar, scalar)')
166
167
def test_recursive_shape(self):
168
shapes = {
169
'InputStructure': {
170
'type': 'structure',
171
'members': {
172
'A': {'shape': 'RecursiveShape'}
173
}
174
},
175
'RecursiveShape': {
176
'type': 'structure',
177
'members': {
178
'B': {'shape': 'StringType'},
179
'C': {'shape': 'RecursiveShape'},
180
}
181
},
182
'StringType': {
183
'type': 'string'
184
}
185
}
186
shape = model.StructureShape(shape_name='InputStructure',
187
shape_model=shapes['InputStructure'],
188
shape_resolver=model.ShapeResolver(
189
shape_map=shapes))
190
self.assertIn('recursive', detect_shape_structure(shape))
191
192
193
class TestParamShorthand(BaseArgProcessTest):
194
maxDiff = None
195
196
def setUp(self):
197
super(TestParamShorthand, self).setUp()
198
self._shorthand = ParamShorthandParser()
199
200
def parse_shorthand(self, cli_argument, value, event_name=None):
201
event = event_name
202
if event is None:
203
event = 'process-cli-arg.foo.bar'
204
return self._shorthand(cli_argument, value, event)
205
206
def test_simplify_structure_scalars(self):
207
p = self.get_param_model(
208
'elasticbeanstalk.CreateConfigurationTemplate.SourceConfiguration')
209
value = 'ApplicationName=foo,TemplateName=bar'
210
json_value = '{"ApplicationName": "foo", "TemplateName": "bar"}'
211
returned = self.parse_shorthand(p, value)
212
json_version = unpack_cli_arg(p, json_value)
213
self.assertEqual(returned, json_version)
214
215
def test_flattens_marked_single_member_structure_list(self):
216
argument = self.create_argument({
217
'Arg': {
218
'type': 'list',
219
'member': {
220
'type': 'structure',
221
'members': {
222
'Bar': {'type': 'string'}
223
}
224
}
225
}
226
}, 'arg')
227
argument.argument_model = argument.argument_model.members['Arg']
228
value = ['foo', 'baz']
229
uses_old_list = 'awscli.argprocess.ParamShorthand._uses_old_list_case'
230
with mock.patch(uses_old_list, mock.Mock(return_value=True)):
231
returned = self.parse_shorthand(argument, value)
232
self.assertEqual(returned, [{"Bar": "foo"}, {"Bar": "baz"}])
233
234
def test_does_not_flatten_unmarked_single_member_structure_list(self):
235
argument = self.create_argument({
236
'Arg': {
237
'type': 'list',
238
'member': {
239
'type': 'structure',
240
'members': {
241
'Bar': {'type': 'string'}
242
}
243
}
244
}
245
}, 'arg')
246
argument.argument_model = argument.argument_model.members['Arg']
247
value = ['Bar=foo', 'Bar=baz']
248
uses_old_list = 'awscli.argprocess.ParamShorthand._uses_old_list_case'
249
with mock.patch(uses_old_list, mock.Mock(return_value=False)):
250
returned = self.parse_shorthand(argument, value)
251
self.assertEqual(returned, [{"Bar": "foo"}, {"Bar": "baz"}])
252
253
def test_parse_boolean_shorthand(self):
254
bool_param = mock.Mock()
255
bool_param.cli_type_name = 'boolean'
256
bool_param.argument_model.type_name = 'boolean'
257
bool_param.argument_model.is_document_type = False
258
self.assertTrue(unpack_cli_arg(bool_param, True))
259
self.assertTrue(unpack_cli_arg(bool_param, 'True'))
260
self.assertTrue(unpack_cli_arg(bool_param, 'true'))
261
262
self.assertFalse(unpack_cli_arg(bool_param, False))
263
self.assertFalse(unpack_cli_arg(bool_param, 'False'))
264
self.assertFalse(unpack_cli_arg(bool_param, 'false'))
265
266
def test_simplify_map_scalar(self):
267
p = self.get_param_model('sqs.SetQueueAttributes.Attributes')
268
returned = self.parse_shorthand(p, 'VisibilityTimeout=15')
269
json_version = unpack_cli_arg(p, '{"VisibilityTimeout": "15"}')
270
self.assertEqual(returned, {'VisibilityTimeout': '15'})
271
self.assertEqual(returned, json_version)
272
273
def test_list_structure_scalars(self):
274
p = self.get_param_model(
275
'elb.RegisterInstancesWithLoadBalancer.Instances')
276
event_name = ('process-cli-arg.elastic-load-balancing'
277
'.register-instances-with-load-balancer')
278
# Because this is a list type param, we'll use nargs
279
# with argparse which means the value will be presented
280
# to us as a list.
281
returned = self.parse_shorthand(
282
p, ['instance-1', 'instance-2'], event_name)
283
self.assertEqual(returned, [{'InstanceId': 'instance-1'},
284
{'InstanceId': 'instance-2'}])
285
286
def test_list_structure_list_scalar(self):
287
p = self.get_param_model('ec2.DescribeInstances.Filters')
288
expected = [{"Name": "instance-id", "Values": ["i-1", "i-2"]},
289
{"Name": "architecture", "Values": ["i386"]}]
290
returned = self.parse_shorthand(
291
p, ["Name=instance-id,Values=i-1,i-2",
292
"Name=architecture,Values=i386"])
293
self.assertEqual(returned, expected)
294
295
# With spaces around the comma.
296
returned2 = self.parse_shorthand(
297
p, ["Name=instance-id, Values=i-1,i-2",
298
"Name=architecture, Values=i386"])
299
self.assertEqual(returned2, expected)
300
301
# Strip off leading/trailing spaces.
302
returned3 = self.parse_shorthand(
303
p, ["Name = instance-id, Values = i-1,i-2",
304
"Name = architecture, Values = i386"])
305
self.assertEqual(returned3, expected)
306
307
def test_parse_empty_values(self):
308
# A value can be omitted and will default to an empty string.
309
p = self.get_param_model('ec2.DescribeInstances.Filters')
310
expected = [{"Name": "", "Values": ["i-1", "i-2"]},
311
{"Name": "architecture", "Values": ['']}]
312
returned = self.parse_shorthand(
313
p, ["Name=,Values=i-1,i-2",
314
"Name=architecture,Values="])
315
self.assertEqual(returned, expected)
316
317
def test_list_structure_list_scalar_2(self):
318
p = self.get_param_model('emr.ModifyInstanceGroups.InstanceGroups')
319
expected = [
320
{"InstanceGroupId": "foo",
321
"InstanceCount": 4},
322
{"InstanceGroupId": "bar",
323
"InstanceCount": 1}
324
]
325
326
simplified = self.parse_shorthand(p, [
327
"InstanceGroupId=foo,InstanceCount=4",
328
"InstanceGroupId=bar,InstanceCount=1"
329
])
330
331
self.assertEqual(simplified, expected)
332
333
def test_empty_value_of_list_structure(self):
334
p = self.get_param_model('emr.ModifyInstanceGroups.InstanceGroups')
335
expected = []
336
simplified = self.parse_shorthand(p, [])
337
self.assertEqual(simplified, expected)
338
339
def test_list_structure_list_multiple_scalar(self):
340
p = self.get_param_model(
341
'emr.ModifyInstanceGroups.InstanceGroups')
342
returned = self.parse_shorthand(
343
p,
344
['InstanceGroupId=foo,InstanceCount=3,'
345
'EC2InstanceIdsToTerminate=i-12345,i-67890'])
346
self.assertEqual(returned, [{'EC2InstanceIdsToTerminate': [
347
'i-12345', 'i-67890'
348
],
349
'InstanceGroupId': 'foo',
350
'InstanceCount': 3}])
351
352
def test_list_structure_scalars_2(self):
353
p = self.get_param_model('elb.CreateLoadBalancer.Listeners')
354
expected = [
355
{"Protocol": "protocol1",
356
"LoadBalancerPort": 1,
357
"InstanceProtocol": "instance_protocol1",
358
"InstancePort": 2,
359
"SSLCertificateId": "ssl_certificate_id1"},
360
{"Protocol": "protocol2",
361
"LoadBalancerPort": 3,
362
"InstanceProtocol": "instance_protocol2",
363
"InstancePort": 4,
364
"SSLCertificateId": "ssl_certificate_id2"},
365
]
366
returned = unpack_cli_arg(
367
p, ['{"Protocol": "protocol1", "LoadBalancerPort": 1, '
368
'"InstanceProtocol": "instance_protocol1", '
369
'"InstancePort": 2, "SSLCertificateId": '
370
'"ssl_certificate_id1"}',
371
'{"Protocol": "protocol2", "LoadBalancerPort": 3, '
372
'"InstanceProtocol": "instance_protocol2", '
373
'"InstancePort": 4, "SSLCertificateId": '
374
'"ssl_certificate_id2"}',
375
])
376
self.assertEqual(returned, expected)
377
simplified = self.parse_shorthand(p, [
378
'Protocol=protocol1,LoadBalancerPort=1,'
379
'InstanceProtocol=instance_protocol1,'
380
'InstancePort=2,SSLCertificateId=ssl_certificate_id1',
381
'Protocol=protocol2,LoadBalancerPort=3,'
382
'InstanceProtocol=instance_protocol2,'
383
'InstancePort=4,SSLCertificateId=ssl_certificate_id2'
384
])
385
self.assertEqual(simplified, expected)
386
387
def test_keyval_with_long_values(self):
388
p = self.get_param_model(
389
'dynamodb.UpdateTable.ProvisionedThroughput')
390
value = 'WriteCapacityUnits=10,ReadCapacityUnits=10'
391
returned = self.parse_shorthand(p, value)
392
self.assertEqual(returned, {'WriteCapacityUnits': 10,
393
'ReadCapacityUnits': 10})
394
395
def test_error_messages_for_structure_scalar(self):
396
p = self.get_param_model(
397
'elasticbeanstalk.CreateConfigurationTemplate.SourceConfiguration')
398
value = 'ApplicationName:foo,TemplateName=bar'
399
error_msg = "Error parsing parameter '--source-configuration'.*Expected"
400
with self.assertRaisesRegex(ParamError, error_msg):
401
self.parse_shorthand(p, value)
402
403
def test_improper_separator(self):
404
# If the user uses ':' instead of '=', we should give a good
405
# error message.
406
p = self.get_param_model(
407
'elasticbeanstalk.CreateConfigurationTemplate.SourceConfiguration')
408
value = 'ApplicationName:foo,TemplateName:bar'
409
error_msg = "Error parsing parameter '--source-configuration'.*Expected"
410
with self.assertRaisesRegex(ParamError, error_msg):
411
self.parse_shorthand(p, value)
412
413
def test_improper_separator_for_filters_param(self):
414
p = self.get_param_model('ec2.DescribeInstances.Filters')
415
error_msg = "Error parsing parameter '--filters'.*Expected"
416
with self.assertRaisesRegex(ParamError, error_msg):
417
self.parse_shorthand(p, ["Name:tag:Name,Values:foo"])
418
419
def test_csv_syntax_escaped(self):
420
p = self.get_param_model('cloudformation.CreateStack.Parameters')
421
returned = self.parse_shorthand(
422
p, [r"ParameterKey=key,ParameterValue=foo\,bar"])
423
expected = [{"ParameterKey": "key",
424
"ParameterValue": "foo,bar"}]
425
self.assertEqual(returned, expected)
426
427
def test_csv_syntax_double_quoted(self):
428
p = self.get_param_model('cloudformation.CreateStack.Parameters')
429
returned = self.parse_shorthand(
430
p, ['ParameterKey=key,ParameterValue="foo,bar"'])
431
expected = [{"ParameterKey": "key",
432
"ParameterValue": "foo,bar"}]
433
self.assertEqual(returned, expected)
434
435
def test_csv_syntax_single_quoted(self):
436
p = self.get_param_model('cloudformation.CreateStack.Parameters')
437
returned = self.parse_shorthand(
438
p, ["ParameterKey=key,ParameterValue='foo,bar'"])
439
expected = [{"ParameterKey": "key",
440
"ParameterValue": "foo,bar"}]
441
self.assertEqual(returned, expected)
442
443
def test_csv_syntax_errors(self):
444
p = self.get_param_model('cloudformation.CreateStack.Parameters')
445
error_msg = "Error parsing parameter '--parameters'.*Expected"
446
with self.assertRaisesRegex(ParamError, error_msg):
447
self.parse_shorthand(p, ['ParameterKey=key,ParameterValue="foo,bar'])
448
with self.assertRaisesRegex(ParamError, error_msg):
449
self.parse_shorthand(p, ['ParameterKey=key,ParameterValue=foo,bar"'])
450
with self.assertRaisesRegex(ParamError, error_msg):
451
self.parse_shorthand(p, ['ParameterKey=key,ParameterValue=""foo,bar"'])
452
with self.assertRaisesRegex(ParamError, error_msg):
453
self.parse_shorthand(p, ['ParameterKey=key,ParameterValue="foo,bar\''])
454
455
456
class TestParamShorthandCustomArguments(BaseArgProcessTest):
457
458
def setUp(self):
459
super(TestParamShorthandCustomArguments, self).setUp()
460
self.shorthand = ParamShorthandParser()
461
462
def test_list_structure_list_scalar_custom_arg(self):
463
schema = {
464
'type': 'array',
465
'items': {
466
'type': 'object',
467
'properties': {
468
'Name': {
469
'type': 'string'
470
},
471
'Args': {
472
'type': 'array',
473
'items': {
474
'type': 'string'
475
}
476
}
477
}
478
}
479
}
480
argument_model = create_argument_model_from_schema(schema)
481
cli_argument = CustomArgument('foo', argument_model=argument_model)
482
483
expected = [
484
{"Name": "foo",
485
"Args": ["a", "k1=v1", "b"]},
486
{"Name": "bar",
487
"Args": ["baz"]},
488
{"Name": "single_kv",
489
"Args": ["key=value"]},
490
{"Name": "single_v",
491
"Args": ["value"]}
492
]
493
494
simplified = self.shorthand(cli_argument, [
495
"Name=foo,Args=[a,k1=v1,b]",
496
"Name=bar,Args=baz",
497
"Name=single_kv,Args=[key=value]",
498
"Name=single_v,Args=[value]"
499
], 'process-cli-arg.foo.bar')
500
501
self.assertEqual(simplified, expected)
502
503
def test_struct_list_scalars(self):
504
schema = {
505
"type": "object",
506
"properties": {
507
"Consistent": {
508
"type": "boolean",
509
},
510
"Args": {
511
"type": "array",
512
"items": {
513
"type": "string"
514
}
515
}
516
}
517
}
518
argument_model = create_argument_model_from_schema(schema)
519
cli_argument = CustomArgument('test', argument_model=argument_model)
520
521
returned = self.shorthand(
522
cli_argument, 'Consistent=true,Args=foo1,foo2',
523
'process-cli-arg.foo.bar')
524
self.assertEqual(returned, {'Consistent': True,
525
'Args': ['foo1', 'foo2']})
526
527
528
class TestDocGen(BaseArgProcessTest):
529
# These aren't very extensive doc tests, as we want to stay somewhat
530
# flexible and allow the docs to slightly change without breaking these
531
# tests.
532
def setUp(self):
533
super(TestDocGen, self).setUp()
534
self.shorthand_documenter = ParamShorthandDocGen()
535
self.service_name = 'foo'
536
self.operation_name = 'bar'
537
self.service_id = 'baz'
538
539
def get_generated_example_for(self, argument):
540
# Returns a string containing the generated documentation.
541
return self.shorthand_documenter.generate_shorthand_example(
542
argument, self.service_id, self.operation_name)
543
544
def assert_generated_example_is(self, argument, expected_docs):
545
generated_docs = self.get_generated_example_for(argument)
546
self.assertEqual(generated_docs, expected_docs)
547
548
def assert_generated_example_contains(self, argument, expected_to_contain):
549
generated_docs = self.get_generated_example_for(argument)
550
self.assertIn(expected_to_contain, generated_docs)
551
552
def test_gen_map_type_docs(self):
553
argument = self.get_param_model('sqs.SetQueueAttributes.Attributes')
554
expected_example_str = (
555
"KeyName1=string,KeyName2=string\n\n"
556
"Where valid key names are:\n"
557
)
558
self.assert_generated_example_contains(argument, expected_example_str)
559
560
def test_gen_list_scalar_docs(self):
561
self.service_name = 'elb'
562
self.service_id = 'elastic-load-balancing'
563
self.operation_name = 'register-instances-with-load-balancer'
564
argument = self.get_param_model(
565
'elb.RegisterInstancesWithLoadBalancer.Instances')
566
doc_string = '--instances InstanceId1 InstanceId2 InstanceId3'
567
self.assert_generated_example_is(argument, doc_string)
568
569
def test_flattens_marked_single_member_structure_list(self):
570
argument = self.create_argument({
571
'Arg': {
572
'type': 'list',
573
'member': {
574
'type': 'structure',
575
'members': {
576
'Bar': {'type': 'string'}
577
}
578
}
579
}
580
}, 'arg')
581
argument.argument_model = argument.argument_model.members['Arg']
582
uses_old_list = 'awscli.argprocess.ParamShorthand._uses_old_list_case'
583
with mock.patch(uses_old_list, mock.Mock(return_value=True)):
584
self.assert_generated_example_is(argument, '--arg Bar1 Bar2 Bar3')
585
586
def test_generates_single_example_with_min_max_1(self):
587
# An example of this is
588
# 'workspaces rebuild-workspaces --rebuild-workspace-requests'
589
argument = self.create_argument({
590
'Arg': {
591
'type': 'list',
592
'max': 1,
593
'min': 1,
594
'member': {
595
'type': 'structure',
596
'members': {
597
'Bar': {'type': 'string'}
598
}
599
}
600
}
601
}, 'arg')
602
argument.argument_model = argument.argument_model.members['Arg']
603
uses_old_list = 'awscli.argprocess.ParamShorthand._uses_old_list_case'
604
with mock.patch(uses_old_list, mock.Mock(return_value=True)):
605
self.assert_generated_example_is(argument, '--arg Bar1')
606
607
def test_does_not_flatten_unmarked_single_member_structure_list(self):
608
argument = self.create_argument({
609
'Arg': {
610
'type': 'list',
611
'member': {
612
'type': 'structure',
613
'members': {
614
'Bar': {'type': 'string'}
615
}
616
}
617
}
618
}, 'arg')
619
argument.argument_model = argument.argument_model.members['Arg']
620
uses_old_list = 'awscli.argprocess.ParamShorthand._uses_old_list_case'
621
with mock.patch(uses_old_list, mock.Mock(return_value=False)):
622
self.assert_generated_example_is(argument, 'Bar=string ...')
623
624
def test_gen_list_structure_of_scalars_docs(self):
625
argument = self.get_param_model('elb.CreateLoadBalancer.Listeners')
626
generated_example = self.get_generated_example_for(argument)
627
self.assertIn('Protocol=string', generated_example)
628
self.assertIn('LoadBalancerPort=integer', generated_example)
629
self.assertIn('InstanceProtocol=string', generated_example)
630
self.assertIn('InstancePort=integer', generated_example)
631
self.assertIn('SSLCertificateId=string', generated_example)
632
633
def test_gen_list_structure_multiple_scalar_docs(self):
634
expected = (
635
'Scalar1=string,'
636
'Scalar2=string,'
637
'List1=string,string ...'
638
)
639
m = model.DenormalizedStructureBuilder().with_members(OrderedDict([
640
('List', {'type': 'list',
641
'member': {
642
'type': 'structure',
643
'members': OrderedDict([
644
('Scalar1', {'type': 'string'}),
645
('Scalar2', {'type': 'string'}),
646
('List1', {
647
'type': 'list',
648
'member': {'type': 'string'},
649
}),
650
]),
651
}}),
652
])).build_model().members['List']
653
argument = mock.Mock()
654
argument.argument_model = m
655
argument.name = 'foo'
656
argument.cli_name = '--foo'
657
generated_example = self.get_generated_example_for(argument)
658
self.assertIn(expected, generated_example)
659
660
def test_gen_list_structure_list_scalar_scalar_docs(self):
661
# Verify that we have *two* top level list items displayed,
662
# so we make it clear that multiple values are separated by spaces.
663
argument = self.get_param_model('ec2.DescribeInstances.Filters')
664
generated_example = self.get_generated_example_for(argument)
665
self.assertIn('Name=string,Values=string,string',
666
generated_example)
667
668
def test_gen_structure_list_scalar_docs(self):
669
argument = self.create_argument(OrderedDict([
670
('Consistent', {'type': 'boolean'}),
671
('Args', {'type': 'list', 'member': {'type': 'string'}}),
672
]), 'foo')
673
generated_example = self.get_generated_example_for(argument)
674
self.assertIn('Consistent=boolean,Args=string,string',
675
generated_example)
676
677
def test_can_gen_recursive_structure(self):
678
argument = self.get_param_model('dynamodb.PutItem.Item')
679
generated_example = self.get_generated_example_for(argument)
680
681
def test_can_document_nested_structs(self):
682
argument = self.get_param_model('ec2.RunInstances.BlockDeviceMappings')
683
generated_example = self.get_generated_example_for(argument)
684
self.assertRegex(generated_example, r'Ebs={\w+=\w+')
685
686
def test_can_document_nested_lists(self):
687
argument = self.create_argument({
688
'A': {
689
'type': 'list',
690
'member': {
691
'type': 'list',
692
'member': {'type': 'string'},
693
},
694
},
695
})
696
generated_example = self.get_generated_example_for(argument)
697
self.assertIn('A=[[string,string],[string,string]]', generated_example)
698
699
def test_can_generated_nested_maps(self):
700
argument = self.create_argument({
701
'A': {
702
'type': 'map',
703
'key': {'type': 'string'},
704
'value': {'type': 'string'}
705
},
706
})
707
generated_example = self.get_generated_example_for(argument)
708
self.assertIn('A={KeyName1=string,KeyName2=string}', generated_example)
709
710
def test_list_of_structures_with_triple_dots(self):
711
list_shape = {
712
'type': 'list',
713
'member': {'shape': 'StructShape'},
714
}
715
shapes = {
716
'Top': list_shape,
717
'String': {'type': 'string'},
718
'StructShape': {
719
'type': 'structure',
720
'members': OrderedDict([
721
('A', {'shape': 'String'}),
722
('B', {'shape': 'String'}),
723
])
724
}
725
}
726
m = model.ListShape(
727
shape_name='Top',
728
shape_model=list_shape,
729
shape_resolver=model.ShapeResolver(shapes))
730
argument = mock.Mock()
731
argument.argument_model = m
732
argument.name = 'foo'
733
argument.cli_name = '--foo'
734
generated_example = self.get_generated_example_for(argument)
735
self.assertIn('A=string,B=string ...', generated_example)
736
737
def test_handle_special_case_value_struct_not_documented(self):
738
argument = self.create_argument({
739
'Value': {'type': 'string'}
740
})
741
generated_example = self.get_generated_example_for(argument)
742
# This is one of the special cases, we shouldn't generate any
743
# shorthand example for this shape.
744
self.assertIsNone(generated_example)
745
746
def test_can_document_recursive_struct(self):
747
# It's a little more work to set up a recursive
748
# shape because DenormalizedStructureBuilder cannot handle
749
# recursion.
750
struct_shape = {
751
'type': 'structure',
752
'members': OrderedDict([
753
('Recurse', {'shape': 'SubShape'}),
754
('Scalar', {'shape': 'String'}),
755
]),
756
}
757
shapes = {
758
'Top': struct_shape,
759
'String': {'type': 'string'},
760
'SubShape': {
761
'type': 'structure',
762
'members': OrderedDict([
763
('SubRecurse', {'shape': 'Top'}),
764
('Scalar', {'shape': 'String'}),
765
]),
766
}
767
}
768
m = model.StructureShape(
769
shape_name='Top',
770
shape_model=struct_shape,
771
shape_resolver=model.ShapeResolver(shapes))
772
argument = mock.Mock()
773
argument.argument_model = m
774
argument.name = 'foo'
775
argument.cli_name = '--foo'
776
generated_example = self.get_generated_example_for(argument)
777
self.assertIn(
778
'Recurse={SubRecurse={( ... recursive ... ),Scalar=string},'
779
'Scalar=string},Scalar=string',
780
generated_example)
781
782
def test_skip_deeply_nested_shorthand(self):
783
# The eventual goal is to have a better way to document
784
# deeply nested shorthand params, but for now, we'll
785
# only document shorthand params up to a certain stack level.
786
argument = self.create_argument({
787
'A': {
788
'type': 'structure',
789
'members': {
790
'B': {
791
'type': 'structure',
792
'members': {
793
'C': {
794
'type': 'structure',
795
'members': {
796
'D': {'type': 'string'},
797
}
798
}
799
}
800
}
801
}
802
},
803
})
804
generated_example = self.get_generated_example_for(argument)
805
self.assertEqual(generated_example, '')
806
807
def test_structure_within_map(self):
808
argument = self.create_argument(
809
{
810
'A': {
811
'type': 'map',
812
'key': {'type': 'string'},
813
'value': {
814
'type': 'structure',
815
'members': {
816
'B': {'type': 'string'},
817
},
818
},
819
},
820
}
821
)
822
generated_example = self.get_generated_example_for(argument)
823
self.assertEqual('A={KeyName1={B=string},KeyName2={B=string}}', generated_example)
824
825
826
class TestUnpackJSONParams(BaseArgProcessTest):
827
def setUp(self):
828
super(TestUnpackJSONParams, self).setUp()
829
self.simplify = ParamShorthandParser()
830
831
def test_json_with_spaces(self):
832
p = self.get_param_model('ec2.RunInstances.BlockDeviceMappings')
833
# If a user specifies the json with spaces, it will show up as
834
# a multi element list. For example:
835
# --block-device-mappings [{ "DeviceName":"/dev/sdf",
836
# "VirtualName":"ephemeral0"}, {"DeviceName":"/dev/sdg",
837
# "VirtualName":"ephemeral1" }]
838
#
839
# Will show up as:
840
block_device_mapping = [
841
'[{', 'DeviceName:/dev/sdf,', 'VirtualName:ephemeral0},',
842
'{DeviceName:/dev/sdg,', 'VirtualName:ephemeral1', '}]']
843
# The shell has removed the double quotes so this is invalid
844
# JSON, but we should still raise a better exception.
845
with self.assertRaises(ParamError) as e:
846
unpack_cli_arg(p, block_device_mapping)
847
# Parameter name should be in error message.
848
self.assertIn('--block-device-mappings', str(e.exception))
849
# The actual JSON itself should be in the error message.
850
# Because this is a list, only the first element in the JSON
851
# will show. This will at least let customers know what
852
# we tried to parse.
853
self.assertIn('[{', str(e.exception))
854
855
856
class TestJSONValueHeaderParams(BaseArgProcessTest):
857
def setUp(self):
858
super(TestJSONValueHeaderParams, self).setUp()
859
self.p = self.get_param_model(
860
'lex-runtime.PostContent.sessionAttributes')
861
862
def test_json_value_dict(self):
863
value = '{"foo": "bar"}'
864
self.assertEqual(unpack_cli_arg(self.p, value),
865
OrderedDict([('foo', 'bar')]))
866
867
def test_json_value_list(self):
868
value = '["foo", "bar"]'
869
self.assertEqual(unpack_cli_arg(self.p, value), ['foo', 'bar'])
870
871
def test_json_value_int(self):
872
value = "5"
873
self.assertEqual(unpack_cli_arg(self.p, value), 5)
874
875
def test_json_value_float(self):
876
value = "1.2"
877
self.assertEqual(unpack_cli_arg(self.p, value), 1.2)
878
879
def test_json_value_string(self):
880
value = '"5"'
881
self.assertEqual(unpack_cli_arg(self.p, value), '5')
882
883
def test_json_value_boolean(self):
884
value = "true"
885
self.assertEqual(unpack_cli_arg(self.p, value), True)
886
value = "false"
887
self.assertEqual(unpack_cli_arg(self.p, value), False)
888
889
def test_json_value_null(self):
890
value = 'null'
891
self.assertEqual(unpack_cli_arg(self.p, value), None)
892
893
def test_json_value_decode_error(self):
894
value = 'invalid string to be serialized'
895
with self.assertRaises(ParamError):
896
unpack_cli_arg(self.p, value)
897
898
899
class TestArgumentPercentEscaping(BaseArgProcessTest):
900
def _test_percent_escaping(self, arg_type, arg_class, doc_string):
901
argument = self.create_argument(
902
{
903
'Test': {
904
'type': arg_type,
905
'documentation': doc_string,
906
}
907
}
908
)
909
arg = arg_class(
910
'test-arg',
911
argument.argument_model.members['Test'],
912
mock.Mock(),
913
mock.Mock(),
914
is_required=False,
915
)
916
arg_table = {arg.name: arg}
917
parser = ArgTableArgParser(arg_table)
918
help_output = parser.format_help()
919
self.assertIn(doc_string, help_output)
920
921
def test_cli_argument_escapes_percent(self):
922
self._test_percent_escaping('string', CLIArgument, 'Symbols: % ^ & *')
923
924
def test_boolean_argument_escapes_percent(self):
925
self._test_percent_escaping('boolean', BooleanArgument, 'Symbols: % ^ & *')
926
927
def test_cli_argument_escapes_url_encoded_percent(self):
928
self._test_percent_escaping('string', CLIArgument, 'File: test%28file%29.png')
929
930
def test_boolean_argument_escapes_url_encoded_percent(self):
931
self._test_percent_escaping('boolean', BooleanArgument, 'File: test%28file%29.png')
932
933
if __name__ == '__main__':
934
unittest.main()
935
936