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