From 877f3b056aebf619ccb2fafa91ef334dc43bfaae Mon Sep 17 00:00:00 2001 From: Dejan Levec Date: Fri, 27 Dec 2019 18:53:14 +0100 Subject: [PATCH 01/26] Add IsTruncated to Route53.list_resource_record_sets --- moto/route53/responses.py | 1 + tests/test_route53/test_route53.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/moto/route53/responses.py b/moto/route53/responses.py index 3e688b65d..077c89a2c 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -271,6 +271,7 @@ LIST_RRSET_RESPONSE = """ diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py index 0e9a1e2c0..746c78719 100644 --- a/tests/test_route53/test_route53.py +++ b/tests/test_route53/test_route53.py @@ -862,6 +862,8 @@ def test_list_resource_record_sets_name_type_filters(): StartRecordName=all_records[start_with][1], ) + response["IsTruncated"].should.equal(False) + returned_records = [ (record["Type"], record["Name"]) for record in response["ResourceRecordSets"] ] From 000cb968a44277d5dcd696f7745775468e3d45e6 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sun, 5 Jan 2020 11:36:51 +0000 Subject: [PATCH 02/26] #2623 - Only return response from lambda, skip log output --- moto/awslambda/models.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index 95a5c4ad5..38ff81fb2 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -53,9 +53,6 @@ try: except ImportError: from backports.tempfile import TemporaryDirectory -# The lambci container is returning a special escape character for the "RequestID" fields. Unicode 033: -# _stderr_regex = re.compile(r"START|END|REPORT RequestId: .*") -_stderr_regex = re.compile(r"\033\[\d+.*") _orig_adapter_send = requests.adapters.HTTPAdapter.send docker_3 = docker.__version__[0] >= "3" @@ -385,7 +382,7 @@ class LambdaFunction(BaseModel): try: # TODO: I believe we can keep the container running and feed events as needed # also need to hook it up to the other services so it can make kws/s3 etc calls - # Should get invoke_id /RequestId from invovation + # Should get invoke_id /RequestId from invocation env_vars = { "AWS_LAMBDA_FUNCTION_TIMEOUT": self.timeout, "AWS_LAMBDA_FUNCTION_NAME": self.function_name, @@ -453,14 +450,9 @@ class LambdaFunction(BaseModel): if exit_code != 0: raise Exception("lambda invoke failed output: {}".format(output)) - # strip out RequestId lines (TODO: This will return an additional '\n' in the response) - output = os.linesep.join( - [ - line - for line in self.convert(output).splitlines() - if not _stderr_regex.match(line) - ] - ) + # We only care about the response from the lambda + # Which is the last line of the output, according to https://github.com/lambci/docker-lambda/issues/25 + output = output.splitlines()[-1] return output, False except BaseException as e: traceback.print_exc() From eab9e15bf08bf89ec8552c614a39457899b8427f Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sun, 5 Jan 2020 15:01:31 +0000 Subject: [PATCH 03/26] #2623 - Fix and simplify test in ServerMode --- tests/test_awslambda/test_lambda.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 6fd97e325..e378f6ee2 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -58,8 +58,7 @@ def lambda_handler(event, context): volume_id = event.get('volume_id') vol = ec2.Volume(volume_id) - print('get volume details for %s\\nVolume - %s state=%s, size=%s' % (volume_id, volume_id, vol.state, vol.size)) - return event + return {'id': vol.id, 'state': vol.state, 'size': vol.size} """.format( base_url="motoserver:5000" if settings.TEST_SERVER_MODE @@ -181,27 +180,11 @@ if settings.TEST_SERVER_MODE: Payload=json.dumps(in_data), ) result["StatusCode"].should.equal(202) - msg = "get volume details for %s\nVolume - %s state=%s, size=%s\n%s" % ( - vol.id, - vol.id, - vol.state, - vol.size, - json.dumps(in_data).replace( - " ", "" - ), # Makes the tests pass as the result is missing the whitespace + actual_payload = result["Payload"].read().decode("utf-8") + expected_payload = json.dumps( + {"id": vol.id, "state": vol.state, "size": vol.size} ) - - log_result = base64.b64decode(result["LogResult"]).decode("utf-8") - - # The Docker lambda invocation will return an additional '\n', so need to replace it: - log_result = log_result.replace("\n\n", "\n") - log_result.should.equal(msg) - - payload = result["Payload"].read().decode("utf-8") - - # The Docker lambda invocation will return an additional '\n', so need to replace it: - payload = payload.replace("\n\n", "\n") - payload.should.equal(msg) + actual_payload.should.equal(expected_payload) @mock_logs From 68d882e6c0408b029ab0be5a8641d19c7652a154 Mon Sep 17 00:00:00 2001 From: Franz See Date: Sun, 5 Jan 2020 23:55:04 +0800 Subject: [PATCH 04/26] moto/issues/2672 | Modified 'token_use' to return 'id' for an id token, and 'access' for an access token --- moto/cognitoidp/models.py | 8 ++++---- tests/test_cognitoidp/test_cognitoidp.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 6700920ce..082fa5189 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -108,7 +108,7 @@ class CognitoIdpUserPool(BaseModel): return user_pool_json - def create_jwt(self, client_id, username, expires_in=60 * 60, extra_data={}): + def create_jwt(self, client_id, username, token_use, expires_in=60 * 60, extra_data={}): now = int(time.time()) payload = { "iss": "https://cognito-idp.{}.amazonaws.com/{}".format( @@ -116,7 +116,7 @@ class CognitoIdpUserPool(BaseModel): ), "sub": self.users[username].id, "aud": client_id, - "token_use": "id", + "token_use": token_use, "auth_time": now, "exp": now + expires_in, } @@ -125,7 +125,7 @@ class CognitoIdpUserPool(BaseModel): return jws.sign(payload, self.json_web_key, algorithm="RS256"), expires_in def create_id_token(self, client_id, username): - id_token, expires_in = self.create_jwt(client_id, username) + id_token, expires_in = self.create_jwt(client_id, username, "id") self.id_tokens[id_token] = (client_id, username) return id_token, expires_in @@ -137,7 +137,7 @@ class CognitoIdpUserPool(BaseModel): def create_access_token(self, client_id, username): extra_data = self.get_user_extra_data_by_client_id(client_id, username) access_token, expires_in = self.create_jwt( - client_id, username, extra_data=extra_data + client_id, username, "access", extra_data=extra_data ) self.access_tokens[access_token] = (client_id, username) return access_token, expires_in diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 7ac1038b0..71a6e3191 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -1142,12 +1142,13 @@ def test_token_legitimacy(): id_claims = json.loads(jws.verify(id_token, json_web_key, "RS256")) id_claims["iss"].should.equal(issuer) id_claims["aud"].should.equal(client_id) + id_claims["token_use"].should.equal("id") access_claims = json.loads(jws.verify(access_token, json_web_key, "RS256")) access_claims["iss"].should.equal(issuer) access_claims["aud"].should.equal(client_id) for k, v in outputs["additional_fields"].items(): access_claims[k].should.equal(v) - + access_claims["token_use"].should.equal("access") @mock_cognitoidp def test_change_password(): From a8e1a3bf08312581bf4fae1908cc1bcb76aef7d6 Mon Sep 17 00:00:00 2001 From: Franz See Date: Mon, 6 Jan 2020 13:29:23 +0800 Subject: [PATCH 05/26] moto/issues/2672 | Formatted using black --- moto/cognitoidp/models.py | 4 +++- tests/test_cognitoidp/test_cognitoidp.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 082fa5189..b67239e93 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -108,7 +108,9 @@ class CognitoIdpUserPool(BaseModel): return user_pool_json - def create_jwt(self, client_id, username, token_use, expires_in=60 * 60, extra_data={}): + def create_jwt( + self, client_id, username, token_use, expires_in=60 * 60, extra_data={} + ): now = int(time.time()) payload = { "iss": "https://cognito-idp.{}.amazonaws.com/{}".format( diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 71a6e3191..79e6dbbb8 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -1150,6 +1150,7 @@ def test_token_legitimacy(): access_claims[k].should.equal(v) access_claims["token_use"].should.equal("access") + @mock_cognitoidp def test_change_password(): conn = boto3.client("cognito-idp", "us-west-2") From 5f59cb7fb0551eb39659228b311f894fa55fef96 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 6 Jan 2020 08:16:09 +0000 Subject: [PATCH 06/26] #2674 - ListAppend should also work when adding maps to a list --- moto/dynamodb2/models.py | 2 +- tests/test_dynamodb2/test_dynamodb.py | 52 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index d4907cba5..2313a6e41 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -457,7 +457,7 @@ class Item(BaseModel): ) if not old_list.is_list(): raise ParamValidationError - old_list.value.extend(new_value["L"]) + old_list.value.extend([DynamoType(v) for v in new_value["L"]]) value = old_list return value diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 831538054..2d961b406 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -3489,6 +3489,58 @@ def test_update_supports_nested_list_append_onto_another_list(): ) +@mock_dynamodb2 +def test_update_supports_list_append_maps(): + client = boto3.client("dynamodb", region_name="us-west-1") + client.create_table( + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "rid", "AttributeType": "S"}, + ], + TableName="TestTable", + KeySchema=[ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "rid", "KeyType": "RANGE"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + client.put_item( + TableName="TestTable", + Item={ + "id": {"S": "nested_list_append"}, + "rid": {"S": "range_key"}, + "a": {"L": [{"M": {"b": {"S": "bar1"}}}]}, + }, + ) + + # Update item using list_append expression + client.update_item( + TableName="TestTable", + Key={"id": {"S": "nested_list_append"}, "rid": {"S": "range_key"}}, + UpdateExpression="SET a = list_append(a, :i)", + ExpressionAttributeValues={":i": {"L": [{"M": {"b": {"S": "bar2"}}}]}}, + ) + + # Verify item is appended to the existing list + result = client.query( + TableName="TestTable", + KeyConditionExpression="id = :i AND begins_with(rid, :r)", + ExpressionAttributeValues={ + ":i": {"S": "nested_list_append"}, + ":r": {"S": "range_key"}, + }, + )["Items"] + result.should.equal( + [ + { + "a": {"L": [{"M": {"b": {"S": "bar1"}}}, {"M": {"b": {"S": "bar2"}}}]}, + "rid": {"S": "range_key"}, + "id": {"S": "nested_list_append"}, + } + ] + ) + + @mock_dynamodb2 def test_update_catches_invalid_list_append_operation(): client = boto3.client("dynamodb", region_name="us-east-1") From d06a5d3a2b2947e029f544ebbd84eca43e1f6eb5 Mon Sep 17 00:00:00 2001 From: Patrick Delaney Date: Tue, 7 Jan 2020 10:12:50 -0500 Subject: [PATCH 07/26] fix: small fixes to get scripts/scaffold.py working --- scripts/scaffold.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/scaffold.py b/scripts/scaffold.py index be154f103..43a648b48 100755 --- a/scripts/scaffold.py +++ b/scripts/scaffold.py @@ -20,8 +20,8 @@ import jinja2 from prompt_toolkit import ( prompt ) -from prompt_toolkit.contrib.completers import WordCompleter -from prompt_toolkit.shortcuts import print_tokens +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.shortcuts import print_formatted_text from botocore import xform_name from botocore.session import Session @@ -149,12 +149,12 @@ def append_mock_dict_to_backends_py(service): with open(path) as f: lines = [_.replace('\n', '') for _ in f.readlines()] - if any(_ for _ in lines if re.match(".*'{}': {}_backends.*".format(service, service), _)): + if any(_ for _ in lines if re.match(".*\"{}\": {}_backends.*".format(service, service), _)): return - filtered_lines = [_ for _ in lines if re.match(".*'.*':.*_backends.*", _)] + filtered_lines = [_ for _ in lines if re.match(".*\".*\":.*_backends.*", _)] last_elem_line_index = lines.index(filtered_lines[-1]) - new_line = " '{}': {}_backends,".format(service, get_escaped_service(service)) + new_line = " \"{}\": {}_backends,".format(service, get_escaped_service(service)) prev_line = lines[last_elem_line_index] if not prev_line.endswith('{') and not prev_line.endswith(','): lines[last_elem_line_index] += ',' From cba3cfc3843de2bd3fb6d1ad6867761af877843d Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Thu, 9 Jan 2020 09:10:16 +0000 Subject: [PATCH 08/26] Escape curly braces in formatting string --- tests/test_awslambda/test_lambda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index e378f6ee2..2d9a6bd5d 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -58,7 +58,7 @@ def lambda_handler(event, context): volume_id = event.get('volume_id') vol = ec2.Volume(volume_id) - return {'id': vol.id, 'state': vol.state, 'size': vol.size} + return {{'id': vol.id, 'state': vol.state, 'size': vol.size}} """.format( base_url="motoserver:5000" if settings.TEST_SERVER_MODE From 58844830199ed269b44e17ef8e839b2be1cfd2b0 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Thu, 9 Jan 2020 10:08:35 +0000 Subject: [PATCH 09/26] Compare map, instead of string repr --- tests/test_awslambda/test_lambda.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 2d9a6bd5d..2835729f8 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -180,10 +180,8 @@ if settings.TEST_SERVER_MODE: Payload=json.dumps(in_data), ) result["StatusCode"].should.equal(202) - actual_payload = result["Payload"].read().decode("utf-8") - expected_payload = json.dumps( - {"id": vol.id, "state": vol.state, "size": vol.size} - ) + actual_payload = json.loads(result["Payload"].read().decode("utf-8")) + expected_payload = {"id": vol.id, "state": vol.state, "size": vol.size} actual_payload.should.equal(expected_payload) From 2cb3f327de85268165b63f8576e4e135eb59333c Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Thu, 9 Jan 2020 22:50:55 -0600 Subject: [PATCH 10/26] Store 'networkMode' in ECS Task Definitions instead of just throwing it away --- moto/ecs/models.py | 9 +++++++-- moto/ecs/responses.py | 3 ++- tests/test_ecs/test_ecs_boto3.py | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/moto/ecs/models.py b/moto/ecs/models.py index 845bdf650..30075f7f0 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -118,6 +118,7 @@ class TaskDefinition(BaseObject): revision, container_definitions, region_name, + network_mode=None, volumes=None, tags=None, ): @@ -132,6 +133,10 @@ class TaskDefinition(BaseObject): self.volumes = [] else: self.volumes = volumes + if network_mode is None: + self.network_mode = 'bridge' + else: + self.network_mode = network_mode @property def response_object(self): @@ -553,7 +558,7 @@ class EC2ContainerServiceBackend(BaseBackend): raise Exception("{0} is not a cluster".format(cluster_name)) def register_task_definition( - self, family, container_definitions, volumes, tags=None + self, family, container_definitions, volumes=None, network_mode=None, tags=None ): if family in self.task_definitions: last_id = self._get_last_task_definition_revision_id(family) @@ -562,7 +567,7 @@ class EC2ContainerServiceBackend(BaseBackend): self.task_definitions[family] = {} revision = 1 task_definition = TaskDefinition( - family, revision, container_definitions, self.region_name, volumes, tags + family, revision, container_definitions, self.region_name, volumes=volumes, network_mode=network_mode, tags=tags ) self.task_definitions[family][revision] = task_definition diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index d08bded2c..ebbfeb84b 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -62,8 +62,9 @@ class EC2ContainerServiceResponse(BaseResponse): container_definitions = self._get_param("containerDefinitions") volumes = self._get_param("volumes") tags = self._get_param("tags") + network_mode = self._get_param('networkMode') task_definition = self.ecs_backend.register_task_definition( - family, container_definitions, volumes, tags + family, container_definitions, volumes=volumes, network_mode=network_mode, tags=tags, ) return json.dumps({"taskDefinition": task_definition.response_object}) diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py index 973c95b81..75598f6e5 100644 --- a/tests/test_ecs/test_ecs_boto3.py +++ b/tests/test_ecs/test_ecs_boto3.py @@ -94,6 +94,7 @@ def test_register_task_definition(): "logConfiguration": {"logDriver": "json-file"}, } ], + networkMode='bridge', tags=[ {"key": "createdBy", "value": "moto-unittest"}, {"key": "foo", "value": "bar"}, @@ -124,6 +125,7 @@ def test_register_task_definition(): response["taskDefinition"]["containerDefinitions"][0]["logConfiguration"][ "logDriver" ].should.equal("json-file") + response['taskDefinition']['networkMode'].should.equal('bridge') @mock_ecs From fd1fdde1bf8c0b3b179057e24fbce1c2b1f0a3bf Mon Sep 17 00:00:00 2001 From: Don Kuntz Date: Thu, 9 Jan 2020 23:45:14 -0600 Subject: [PATCH 11/26] Allow black to reformat correctly --- moto/ecs/models.py | 10 ++++++++-- moto/ecs/responses.py | 8 ++++++-- tests/test_ecs/test_ecs_boto3.py | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/moto/ecs/models.py b/moto/ecs/models.py index 30075f7f0..30e4687c4 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -134,7 +134,7 @@ class TaskDefinition(BaseObject): else: self.volumes = volumes if network_mode is None: - self.network_mode = 'bridge' + self.network_mode = "bridge" else: self.network_mode = network_mode @@ -567,7 +567,13 @@ class EC2ContainerServiceBackend(BaseBackend): self.task_definitions[family] = {} revision = 1 task_definition = TaskDefinition( - family, revision, container_definitions, self.region_name, volumes=volumes, network_mode=network_mode, tags=tags + family, + revision, + container_definitions, + self.region_name, + volumes=volumes, + network_mode=network_mode, + tags=tags, ) self.task_definitions[family][revision] = task_definition diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index ebbfeb84b..49bf022b4 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -62,9 +62,13 @@ class EC2ContainerServiceResponse(BaseResponse): container_definitions = self._get_param("containerDefinitions") volumes = self._get_param("volumes") tags = self._get_param("tags") - network_mode = self._get_param('networkMode') + network_mode = self._get_param("networkMode") task_definition = self.ecs_backend.register_task_definition( - family, container_definitions, volumes=volumes, network_mode=network_mode, tags=tags, + family, + container_definitions, + volumes=volumes, + network_mode=network_mode, + tags=tags, ) return json.dumps({"taskDefinition": task_definition.response_object}) diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py index 75598f6e5..f1f1e04ae 100644 --- a/tests/test_ecs/test_ecs_boto3.py +++ b/tests/test_ecs/test_ecs_boto3.py @@ -94,7 +94,7 @@ def test_register_task_definition(): "logConfiguration": {"logDriver": "json-file"}, } ], - networkMode='bridge', + networkMode="bridge", tags=[ {"key": "createdBy", "value": "moto-unittest"}, {"key": "foo", "value": "bar"}, @@ -125,7 +125,7 @@ def test_register_task_definition(): response["taskDefinition"]["containerDefinitions"][0]["logConfiguration"][ "logDriver" ].should.equal("json-file") - response['taskDefinition']['networkMode'].should.equal('bridge') + response["taskDefinition"]["networkMode"].should.equal("bridge") @mock_ecs From 6dac06ed7c8994719a5ac485d471d2c22045a05d Mon Sep 17 00:00:00 2001 From: Sebastian P Date: Fri, 10 Jan 2020 16:08:34 +0100 Subject: [PATCH 12/26] setup.py: Unlock use with jsondiff >1.1.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 97a6341ff..d09f8fc7b 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ install_requires = [ "python-jose<4.0.0", "mock", "docker>=2.5.1", - "jsondiff==1.1.2", + "jsondiff>=1.1.2", "aws-xray-sdk!=0.96,>=0.93", "responses>=0.9.0", "idna<2.9,>=2.5", From 9ce1ee49d763dc73d6630065c2b7adbacb279ad1 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sun, 12 Jan 2020 12:05:08 +0000 Subject: [PATCH 13/26] #2626 - DynamoDB - FilterExpression should ignore items with non-existent attribute --- moto/dynamodb2/comparisons.py | 8 -------- tests/test_dynamodb2/test_dynamodb.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/moto/dynamodb2/comparisons.py b/moto/dynamodb2/comparisons.py index 69d7f74e0..372f612c3 100644 --- a/moto/dynamodb2/comparisons.py +++ b/moto/dynamodb2/comparisons.py @@ -979,8 +979,6 @@ class OpLessThan(Op): # In python3 None is not a valid comparator when using < or > so must be handled specially if lhs and rhs: return lhs < rhs - elif lhs is None and rhs: - return True else: return False @@ -994,8 +992,6 @@ class OpGreaterThan(Op): # In python3 None is not a valid comparator when using < or > so must be handled specially if lhs and rhs: return lhs > rhs - elif lhs and rhs is None: - return True else: return False @@ -1027,8 +1023,6 @@ class OpLessThanOrEqual(Op): # In python3 None is not a valid comparator when using < or > so must be handled specially if lhs and rhs: return lhs <= rhs - elif lhs is None and rhs or lhs is None and rhs is None: - return True else: return False @@ -1042,8 +1036,6 @@ class OpGreaterThanOrEqual(Op): # In python3 None is not a valid comparator when using < or > so must be handled specially if lhs and rhs: return lhs >= rhs - elif lhs and rhs is None or lhs is None and rhs is None: - return True else: return False diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 831538054..b63a7c19e 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -1719,6 +1719,32 @@ def test_scan_filter4(): assert response["Count"] == 0 +@mock_dynamodb2 +def test_scan_filter_should_not_return_non_existing_attributes(): + table_name = "my-table" + item = {"partitionKey": "pk-2", "my-attr": 42} + # Create table + res = boto3.resource("dynamodb") + res.create_table( + TableName=table_name, + KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}], + BillingMode="PAY_PER_REQUEST", + ) + table = res.Table(table_name) + # Insert items + table.put_item(Item={"partitionKey": "pk-1"}) + table.put_item(Item=item) + # Verify a few operations + # Assert we only find the item that has this attribute + table.scan(FilterExpression=Attr("my-attr").lt(43))["Items"].should.equal([item]) + table.scan(FilterExpression=Attr("my-attr").lte(42))["Items"].should.equal([item]) + table.scan(FilterExpression=Attr("my-attr").gte(42))["Items"].should.equal([item]) + table.scan(FilterExpression=Attr("my-attr").gt(41))["Items"].should.equal([item]) + # Sanity check that we can't find the item if the FE is wrong + table.scan(FilterExpression=Attr("my-attr").gt(43))["Items"].should.equal([]) + + @mock_dynamodb2 def test_bad_scan_filter(): client = boto3.client("dynamodb", region_name="us-east-1") From 8c920cce109552d4841bf1ff72fda53f69b3bd45 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sun, 12 Jan 2020 12:20:55 +0000 Subject: [PATCH 14/26] Specify region in tests --- tests/test_dynamodb2/test_dynamodb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index b63a7c19e..333eba135 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -1724,7 +1724,7 @@ def test_scan_filter_should_not_return_non_existing_attributes(): table_name = "my-table" item = {"partitionKey": "pk-2", "my-attr": 42} # Create table - res = boto3.resource("dynamodb") + res = boto3.resource("dynamodb", region_name="us-east-1") res.create_table( TableName=table_name, KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}], From fba84ec34b2e0dbf9e8ade3ef0965de882e76bab Mon Sep 17 00:00:00 2001 From: Nikhil Date: Tue, 14 Jan 2020 12:28:48 +0530 Subject: [PATCH 15/26] Fixed a typo in README.md - related to https://github.com/spulec/moto/issues/2691 --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4024328a9..f5c45a6b6 100644 --- a/README.md +++ b/README.md @@ -283,14 +283,14 @@ def test_describe_instances_allowed(): ] } access_key = ... - # create access key for an IAM user/assumed role that has the policy above. + # create access key for an IAM user/assumed role that has the policy above. # this part should call __exactly__ 4 AWS actions, so that authentication and authorization starts exactly after this - + client = boto3.client('ec2', region_name='us-east-1', aws_access_key_id=access_key['AccessKeyId'], aws_secret_access_key=access_key['SecretAccessKey']) - - # if the IAM principal whose access key is used, does not have the permission to describe instances, this will fail + + # if the IAM principal whose access key is used, does not have the permission to describe instances, this will fail instances = client.describe_instances()['Reservations'][0]['Instances'] assert len(instances) == 0 ``` @@ -310,16 +310,16 @@ You need to ensure that the mocks are actually in place. Changes made to recent have altered some of the mock behavior. In short, you need to ensure that you _always_ do the following: 1. Ensure that your tests have dummy environment variables set up: - + export AWS_ACCESS_KEY_ID='testing' export AWS_SECRET_ACCESS_KEY='testing' export AWS_SECURITY_TOKEN='testing' export AWS_SESSION_TOKEN='testing' - -1. __VERY IMPORTANT__: ensure that you have your mocks set up __BEFORE__ your `boto3` client is established. + +1. __VERY IMPORTANT__: ensure that you have your mocks set up __BEFORE__ your `boto3` client is established. This can typically happen if you import a module that has a `boto3` client instantiated outside of a function. See the pesky imports section below on how to work around this. - + ### Example on usage? If you are a user of [pytest](https://pytest.org/en/latest/), you can leverage [pytest fixtures](https://pytest.org/en/latest/fixture.html#fixture) to help set up your mocks and other AWS resources that you would need. @@ -354,7 +354,7 @@ def cloudwatch(aws_credentials): ... etc. ``` -In the code sample above, all of the AWS/mocked fixtures take in a parameter of `aws_credentials`, +In the code sample above, all of the AWS/mocked fixtures take in a parameter of `aws_credentials`, which sets the proper fake environment variables. The fake environment variables are used so that `botocore` doesn't try to locate real credentials on your system. @@ -364,7 +364,7 @@ def test_create_bucket(s3): # s3 is a fixture defined above that yields a boto3 s3 client. # Feel free to instantiate another boto3 S3 client -- Keep note of the region though. s3.create_bucket(Bucket="somebucket") - + result = s3.list_buckets() assert len(result['Buckets']) == 1 assert result['Buckets'][0]['Name'] == 'somebucket' @@ -373,7 +373,7 @@ def test_create_bucket(s3): ### What about those pesky imports? Recall earlier, it was mentioned that mocks should be established __BEFORE__ the clients are set up. One way to avoid import issues is to make use of local Python imports -- i.e. import the module inside of the unit -test you want to run vs. importing at the top of the file. +test you want to run vs. importing at the top of the file. Example: ```python @@ -381,12 +381,12 @@ def test_something(s3): from some.package.that.does.something.with.s3 import some_func # <-- Local import for unit test # ^^ Importing here ensures that the mock has been established. - sume_func() # The mock has been established from the "s3" pytest fixture, so this function that uses + some_func() # The mock has been established from the "s3" pytest fixture, so this function that uses # a package-level S3 client will properly use the mock and not reach out to AWS. ``` ### Other caveats -For Tox, Travis CI, and other build systems, you might need to also perform a `touch ~/.aws/credentials` +For Tox, Travis CI, and other build systems, you might need to also perform a `touch ~/.aws/credentials` command before running the tests. As long as that file is present (empty preferably) and the environment variables above are set, you should be good to go. From db559e7e06bcadad70be048aeb4f1b1119671375 Mon Sep 17 00:00:00 2001 From: Asher Foa <1268088+asherf@users.noreply.github.com> Date: Tue, 14 Jan 2020 09:55:32 -0800 Subject: [PATCH 16/26] Fix some typos --- moto/ec2/responses/security_groups.py | 8 ++++---- tests/test_autoscaling/test_autoscaling.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index 6f2926f61..f0002d5bd 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -104,7 +104,7 @@ class SecurityGroups(BaseResponse): if self.is_not_dryrun("GrantSecurityGroupIngress"): for args in self._process_rules_from_querystring(): self.ec2_backend.authorize_security_group_ingress(*args) - return AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE + return AUTHORIZE_SECURITY_GROUP_INGRESS_RESPONSE def create_security_group(self): name = self._get_param("GroupName") @@ -158,7 +158,7 @@ class SecurityGroups(BaseResponse): if self.is_not_dryrun("RevokeSecurityGroupIngress"): for args in self._process_rules_from_querystring(): self.ec2_backend.revoke_security_group_ingress(*args) - return REVOKE_SECURITY_GROUP_INGRESS_REPONSE + return REVOKE_SECURITY_GROUP_INGRESS_RESPONSE CREATE_SECURITY_GROUP_RESPONSE = """ @@ -265,12 +265,12 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = ( """ ) -AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE = """ +AUTHORIZE_SECURITY_GROUP_INGRESS_RESPONSE = """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE true """ -REVOKE_SECURITY_GROUP_INGRESS_REPONSE = """ +REVOKE_SECURITY_GROUP_INGRESS_RESPONSE = """ 59dbff89-35bd-4eac-99ed-be587EXAMPLE true """ diff --git a/tests/test_autoscaling/test_autoscaling.py b/tests/test_autoscaling/test_autoscaling.py index c46bc7219..2e7255381 100644 --- a/tests/test_autoscaling/test_autoscaling.py +++ b/tests/test_autoscaling/test_autoscaling.py @@ -706,14 +706,14 @@ def test_create_autoscaling_group_boto3(): "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "propogated-tag-key", - "Value": "propogate-tag-value", + "Value": "propagate-tag-value", "PropagateAtLaunch": True, }, { "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "not-propogated-tag-key", - "Value": "not-propogate-tag-value", + "Value": "not-propagate-tag-value", "PropagateAtLaunch": False, }, ], @@ -744,14 +744,14 @@ def test_create_autoscaling_group_from_instance(): "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "propogated-tag-key", - "Value": "propogate-tag-value", + "Value": "propagate-tag-value", "PropagateAtLaunch": True, }, { "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "not-propogated-tag-key", - "Value": "not-propogate-tag-value", + "Value": "not-propagate-tag-value", "PropagateAtLaunch": False, }, ], @@ -1062,7 +1062,7 @@ def test_detach_one_instance_decrement(): "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "propogated-tag-key", - "Value": "propogate-tag-value", + "Value": "propagate-tag-value", "PropagateAtLaunch": True, } ], @@ -1116,7 +1116,7 @@ def test_detach_one_instance(): "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "propogated-tag-key", - "Value": "propogate-tag-value", + "Value": "propagate-tag-value", "PropagateAtLaunch": True, } ], @@ -1169,7 +1169,7 @@ def test_attach_one_instance(): "ResourceId": "test_asg", "ResourceType": "auto-scaling-group", "Key": "propogated-tag-key", - "Value": "propogate-tag-value", + "Value": "propagate-tag-value", "PropagateAtLaunch": True, } ], From db75c9e25ca49da7c1bb7e330579db695fbeff3b Mon Sep 17 00:00:00 2001 From: Franz See Date: Sun, 5 Jan 2020 23:13:36 +0800 Subject: [PATCH 17/26] moto/issues/2670 | Moved population of user attributes from accessToken to idToken --- moto/cognitoidp/models.py | 6 +++--- tests/test_cognitoidp/test_cognitoidp.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 78025627a..9f39d7a5f 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -127,7 +127,8 @@ class CognitoIdpUserPool(BaseModel): return jws.sign(payload, self.json_web_key, algorithm="RS256"), expires_in def create_id_token(self, client_id, username): - id_token, expires_in = self.create_jwt(client_id, username, "id") + extra_data = self.get_user_extra_data_by_client_id(client_id, username) + id_token, expires_in = self.create_jwt(client_id, username, "id", extra_data=extra_data) self.id_tokens[id_token] = (client_id, username) return id_token, expires_in @@ -137,9 +138,8 @@ class CognitoIdpUserPool(BaseModel): return refresh_token def create_access_token(self, client_id, username): - extra_data = self.get_user_extra_data_by_client_id(client_id, username) access_token, expires_in = self.create_jwt( - client_id, username, "access", extra_data=extra_data + client_id, username, "access" ) self.access_tokens[access_token] = (client_id, username) return access_token, expires_in diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py index 79e6dbbb8..6a13683f0 100644 --- a/tests/test_cognitoidp/test_cognitoidp.py +++ b/tests/test_cognitoidp/test_cognitoidp.py @@ -1143,11 +1143,11 @@ def test_token_legitimacy(): id_claims["iss"].should.equal(issuer) id_claims["aud"].should.equal(client_id) id_claims["token_use"].should.equal("id") + for k, v in outputs["additional_fields"].items(): + id_claims[k].should.equal(v) access_claims = json.loads(jws.verify(access_token, json_web_key, "RS256")) access_claims["iss"].should.equal(issuer) access_claims["aud"].should.equal(client_id) - for k, v in outputs["additional_fields"].items(): - access_claims[k].should.equal(v) access_claims["token_use"].should.equal("access") From 44e92f58ec44250c0701209549104d4545304ae8 Mon Sep 17 00:00:00 2001 From: Franz See Date: Wed, 15 Jan 2020 23:33:26 +0800 Subject: [PATCH 18/26] moto/issues/2670 | Used black to format the code --- moto/cognitoidp/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 9f39d7a5f..96b23a404 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -128,7 +128,9 @@ class CognitoIdpUserPool(BaseModel): def create_id_token(self, client_id, username): extra_data = self.get_user_extra_data_by_client_id(client_id, username) - id_token, expires_in = self.create_jwt(client_id, username, "id", extra_data=extra_data) + id_token, expires_in = self.create_jwt( + client_id, username, "id", extra_data=extra_data + ) self.id_tokens[id_token] = (client_id, username) return id_token, expires_in @@ -138,9 +140,7 @@ class CognitoIdpUserPool(BaseModel): return refresh_token def create_access_token(self, client_id, username): - access_token, expires_in = self.create_jwt( - client_id, username, "access" - ) + access_token, expires_in = self.create_jwt(client_id, username, "access") self.access_tokens[access_token] = (client_id, username) return access_token, expires_in From 33661d267e699a83828abc43cd2250b1e033fe9a Mon Sep 17 00:00:00 2001 From: Charles Park Date: Thu, 16 Jan 2020 16:33:59 -0500 Subject: [PATCH 19/26] Fix spelling typo --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 6311597fe..22ac97228 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -76,7 +76,7 @@ Currently implemented Services: +---------------------------+-----------------------+------------------------------------+ | Logs | @mock_logs | basic endpoints done | +---------------------------+-----------------------+------------------------------------+ -| Organizations | @mock_organizations | some core edpoints done | +| Organizations | @mock_organizations | some core endpoints done | +---------------------------+-----------------------+------------------------------------+ | Polly | @mock_polly | all endpoints done | +---------------------------+-----------------------+------------------------------------+ From 6f02782624e0a60d388b2ba56cf7be9cf359582b Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Wed, 22 Jan 2020 11:30:17 +0000 Subject: [PATCH 20/26] #2627 - Change comparison to differentiate between 0 and None --- moto/dynamodb2/comparisons.py | 8 ++--- tests/test_dynamodb2/test_dynamodb.py | 42 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/moto/dynamodb2/comparisons.py b/moto/dynamodb2/comparisons.py index 372f612c3..29951d92d 100644 --- a/moto/dynamodb2/comparisons.py +++ b/moto/dynamodb2/comparisons.py @@ -977,7 +977,7 @@ class OpLessThan(Op): lhs = self.lhs.expr(item) rhs = self.rhs.expr(item) # In python3 None is not a valid comparator when using < or > so must be handled specially - if lhs and rhs: + if lhs is not None and rhs is not None: return lhs < rhs else: return False @@ -990,7 +990,7 @@ class OpGreaterThan(Op): lhs = self.lhs.expr(item) rhs = self.rhs.expr(item) # In python3 None is not a valid comparator when using < or > so must be handled specially - if lhs and rhs: + if lhs is not None and rhs is not None: return lhs > rhs else: return False @@ -1021,7 +1021,7 @@ class OpLessThanOrEqual(Op): lhs = self.lhs.expr(item) rhs = self.rhs.expr(item) # In python3 None is not a valid comparator when using < or > so must be handled specially - if lhs and rhs: + if lhs is not None and rhs is not None: return lhs <= rhs else: return False @@ -1034,7 +1034,7 @@ class OpGreaterThanOrEqual(Op): lhs = self.lhs.expr(item) rhs = self.rhs.expr(item) # In python3 None is not a valid comparator when using < or > so must be handled specially - if lhs and rhs: + if lhs is not None and rhs is not None: return lhs >= rhs else: return False diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 333eba135..1a0865ba8 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -2531,6 +2531,48 @@ def test_condition_expressions(): ) +@mock_dynamodb2 +def test_condition_expression_numerical_attribute(): + dynamodb = boto3.resource("dynamodb") + dynamodb.create_table( + TableName="my-table", + KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}], + ) + table = dynamodb.Table("my-table") + table.put_item(Item={"partitionKey": "pk-pos", "myAttr": 5}) + table.put_item(Item={"partitionKey": "pk-neg", "myAttr": -5}) + + # try to update the item we put in the table using numerical condition expression + # Specifically, verify that we can compare with a zero-value + # First verify that > and >= work on positive numbers + update_numerical_con_expr( + key="pk-pos", con_expr="myAttr > :zero", res="6", table=table + ) + update_numerical_con_expr( + key="pk-pos", con_expr="myAttr >= :zero", res="7", table=table + ) + # Second verify that < and <= work on negative numbers + update_numerical_con_expr( + key="pk-neg", con_expr="myAttr < :zero", res="-4", table=table + ) + update_numerical_con_expr( + key="pk-neg", con_expr="myAttr <= :zero", res="-3", table=table + ) + + +def update_numerical_con_expr(key, con_expr, res, table): + table.update_item( + Key={"partitionKey": key}, + UpdateExpression="ADD myAttr :one", + ExpressionAttributeValues={":zero": 0, ":one": 1}, + ConditionExpression=con_expr, + ) + table.get_item(Key={"partitionKey": key})["Item"]["myAttr"].should.equal( + Decimal(res) + ) + + @mock_dynamodb2 def test_condition_expression__attr_doesnt_exist(): client = boto3.client("dynamodb", region_name="us-east-1") From 7ff7ee4e8ebf621ad24cb4101c9b7069e43867c6 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Wed, 22 Jan 2020 11:42:06 +0000 Subject: [PATCH 21/26] Test fix - Region must be specified --- tests/test_dynamodb2/test_dynamodb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 1a0865ba8..5a978edc0 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -2533,7 +2533,7 @@ def test_condition_expressions(): @mock_dynamodb2 def test_condition_expression_numerical_attribute(): - dynamodb = boto3.resource("dynamodb") + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") dynamodb.create_table( TableName="my-table", KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}], From a32b3c4b597c24844e2ee3af24b48cce746bf0d0 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Wed, 22 Jan 2020 19:38:07 -0600 Subject: [PATCH 22/26] Fix SQS get_queue_attributes to allow RedrivePolicy. Closes #2682. --- moto/sqs/models.py | 1 + tests/test_sqs/test_sqs.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/moto/sqs/models.py b/moto/sqs/models.py index 40dd6ba97..8b8263e3c 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -183,6 +183,7 @@ class Queue(BaseModel): "MaximumMessageSize", "MessageRetentionPeriod", "QueueArn", + "RedrivePolicy", "ReceiveMessageWaitTimeSeconds", "VisibilityTimeout", ] diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index 639d6e51c..c74c3822a 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -331,7 +331,20 @@ def test_delete_queue(): @mock_sqs def test_get_queue_attributes(): client = boto3.client("sqs", region_name="us-east-1") - response = client.create_queue(QueueName="test-queue") + + dlq_resp = client.create_queue(QueueName="test-dlr-queue") + dlq_arn1 = client.get_queue_attributes(QueueUrl=dlq_resp["QueueUrl"])["Attributes"][ + "QueueArn" + ] + + response = client.create_queue( + QueueName="test-queue", + Attributes={ + "RedrivePolicy": json.dumps( + {"deadLetterTargetArn": dlq_arn1, "maxReceiveCount": 2} + ), + }, + ) queue_url = response["QueueUrl"] response = client.get_queue_attributes(QueueUrl=queue_url) @@ -356,6 +369,7 @@ def test_get_queue_attributes(): "ApproximateNumberOfMessages", "MaximumMessageSize", "QueueArn", + "RedrivePolicy", "VisibilityTimeout", ], ) @@ -366,6 +380,9 @@ def test_get_queue_attributes(): "MaximumMessageSize": "65536", "QueueArn": "arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID), "VisibilityTimeout": "30", + "RedrivePolicy": json.dumps( + {"deadLetterTargetArn": dlq_arn1, "maxReceiveCount": 2} + ), } ) From d73a548bb0b72c8ccbb70e316ae5112f2f9264c5 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Wed, 22 Jan 2020 19:45:09 -0600 Subject: [PATCH 23/26] Remove duplicate StorageClass in S3_MULTIPART_LIST_RESPONSE. --- moto/s3/responses.py | 1 - 1 file changed, 1 deletion(-) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 71f21c8e1..c8f4e082b 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -1869,7 +1869,6 @@ S3_MULTIPART_LIST_RESPONSE = """ 75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a webfile - STANDARD 1 {{ count }} {{ count }} From 19bf8bf76207a01a168ec842c2e4e917adc466dd Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Wed, 22 Jan 2020 20:43:34 -0600 Subject: [PATCH 24/26] Change S3 S3_ALL_BUCKETS response to return bucket creation_date in iso format. --- moto/s3/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index c8f4e082b..a04427172 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -1482,7 +1482,7 @@ S3_ALL_BUCKETS = """ Date: Thu, 23 Jan 2020 10:16:12 -0800 Subject: [PATCH 26/26] remove this change. --- tests/test_sqs/test_sqs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index 413b16f80..1eb511db0 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -208,7 +208,7 @@ def test_message_with_complex_attributes(): "ccc": {"StringValue": "testjunk", "DataType": "String"}, "aaa": {"BinaryValue": b"\x02\x03\x04", "DataType": "Binary"}, "zzz": {"DataType": "Number", "StringValue": "0230.01"}, - "öthere_encodings": {"DataType": "String", "StringValue": "T\xFCst"}, + "öther_encodings": {"DataType": "String", "StringValue": "T\xFCst"}, }, ) msg.get("MD5OfMessageBody").should.equal("58fd9edd83341c29f1aebba81c31e257")