diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index a863d483d..a22cc3bfb 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -7272,9 +7272,9 @@ - [X] start_workflow_execution - [ ] tag_resource - [X] terminate_workflow_execution -- [ ] undeprecate_activity_type -- [ ] undeprecate_domain -- [ ] undeprecate_workflow_type +- [X] undeprecate_activity_type +- [X] undeprecate_domain +- [X] undeprecate_workflow_type - [ ] untag_resource ## textract diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 88f750775..8e5a61755 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -450,9 +450,9 @@ class Item(BaseModel): old_list_key = list_append_re.group(1) # old_key could be a function itself (if_not_exists) if old_list_key.startswith("if_not_exists"): - old_list = DynamoType( - expression_attribute_values[self._get_default(old_list_key)] - ) + old_list = self._get_default(old_list_key) + if not isinstance(old_list, DynamoType): + old_list = DynamoType(expression_attribute_values[old_list]) else: old_list = self.attrs[old_list_key.split(".")[0]] if "." in old_list_key: diff --git a/moto/route53/models.py b/moto/route53/models.py index 2ae03e54d..0bdefd25b 100644 --- a/moto/route53/models.py +++ b/moto/route53/models.py @@ -88,6 +88,8 @@ class RecordSet(BaseModel): self.hosted_zone_name = kwargs.get("HostedZoneName") self.hosted_zone_id = kwargs.get("HostedZoneId") self.alias_target = kwargs.get("AliasTarget") + self.failover = kwargs.get("Failover") + self.geo_location = kwargs.get("GeoLocation") @classmethod def create_from_cloudformation_json( @@ -154,6 +156,16 @@ class RecordSet(BaseModel): {% if record_set.ttl %} {{ record_set.ttl }} {% endif %} + {% if record_set.failover %} + {{ record_set.failover }} + {% endif %} + {% if record_set.geo_location %} + + {% for geo_key in ['ContinentCode','CountryCode','SubdivisionCode'] %} + {% if record_set.geo_location[geo_key] %}<{{ geo_key }}>{{ record_set.geo_location[geo_key] }}{% endif %} + {% endfor %} + + {% endif %} {% if record_set.alias_target %} {{ record_set.alias_target['HostedZoneId'] }} diff --git a/moto/ssm/models.py b/moto/ssm/models.py index 60c47f021..a7518d405 100644 --- a/moto/ssm/models.py +++ b/moto/ssm/models.py @@ -278,10 +278,7 @@ class SimpleSystemManagerBackend(BaseBackend): self._region = region def delete_parameter(self, name): - try: - del self._parameters[name] - except KeyError: - pass + return self._parameters.pop(name, None) def delete_parameters(self, names): result = [] diff --git a/moto/ssm/responses.py b/moto/ssm/responses.py index 1b13780a8..831737848 100644 --- a/moto/ssm/responses.py +++ b/moto/ssm/responses.py @@ -22,7 +22,13 @@ class SimpleSystemManagerResponse(BaseResponse): def delete_parameter(self): name = self._get_param("Name") - self.ssm_backend.delete_parameter(name) + result = self.ssm_backend.delete_parameter(name) + if result is None: + error = { + "__type": "ParameterNotFound", + "message": "Parameter {0} not found.".format(name), + } + return json.dumps(error), dict(status=400) return json.dumps({}) def delete_parameters(self): diff --git a/moto/swf/models/__init__.py b/moto/swf/models/__init__.py index e5b285f5b..010c8c734 100644 --- a/moto/swf/models/__init__.py +++ b/moto/swf/models/__init__.py @@ -121,6 +121,12 @@ class SWFBackend(BaseBackend): raise SWFDomainDeprecatedFault(name) domain.status = "DEPRECATED" + def undeprecate_domain(self, name): + domain = self._get_domain(name) + if domain.status == "REGISTERED": + raise SWFDomainAlreadyExistsFault(name) + domain.status = "REGISTERED" + def describe_domain(self, name): return self._get_domain(name) @@ -148,6 +154,13 @@ class SWFBackend(BaseBackend): raise SWFTypeDeprecatedFault(_type) _type.status = "DEPRECATED" + def undeprecate_type(self, kind, domain_name, name, version): + domain = self._get_domain(domain_name) + _type = domain.get_type(kind, name, version) + if _type.status == "REGISTERED": + raise SWFTypeAlreadyExistsFault(_type) + _type.status = "REGISTERED" + def describe_type(self, kind, domain_name, name, version): domain = self._get_domain(domain_name) return domain.get_type(kind, name, version) diff --git a/moto/swf/responses.py b/moto/swf/responses.py index c8c601fa7..17ec7281a 100644 --- a/moto/swf/responses.py +++ b/moto/swf/responses.py @@ -92,6 +92,17 @@ class SWFResponse(BaseResponse): self.swf_backend.deprecate_type(kind, domain, name, version) return "" + def _undeprecate_type(self, kind): + domain = self._params["domain"] + _type_args = self._params["{0}Type".format(kind)] + name = _type_args["name"] + version = _type_args["version"] + self._check_string(domain) + self._check_string(name) + self._check_string(version) + self.swf_backend.undeprecate_type(kind, domain, name, version) + return "" + # TODO: implement pagination def list_domains(self): status = self._params["registrationStatus"] @@ -219,6 +230,12 @@ class SWFResponse(BaseResponse): self.swf_backend.deprecate_domain(name) return "" + def undeprecate_domain(self): + name = self._params["name"] + self._check_string(name) + self.swf_backend.undeprecate_domain(name) + return "" + def describe_domain(self): name = self._params["name"] self._check_string(name) @@ -278,6 +295,9 @@ class SWFResponse(BaseResponse): def deprecate_activity_type(self): return self._deprecate_type("activity") + def undeprecate_activity_type(self): + return self._undeprecate_type("activity") + def describe_activity_type(self): return self._describe_type("activity") @@ -333,6 +353,9 @@ class SWFResponse(BaseResponse): def deprecate_workflow_type(self): return self._deprecate_type("workflow") + def undeprecate_workflow_type(self): + return self._undeprecate_type("workflow") + def describe_workflow_type(self): return self._describe_type("workflow") diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 180f460c0..428b58f81 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -3634,6 +3634,31 @@ def test_update_supports_list_append_with_nested_if_not_exists_operation(): ) +@mock_dynamodb2 +def test_update_supports_list_append_with_nested_if_not_exists_operation_and_property_already_exists(): + dynamo = boto3.resource("dynamodb", region_name="us-west-1") + table_name = "test" + + dynamo.create_table( + TableName=table_name, + AttributeDefinitions=[{"AttributeName": "Id", "AttributeType": "S"}], + KeySchema=[{"AttributeName": "Id", "KeyType": "HASH"}], + ProvisionedThroughput={"ReadCapacityUnits": 20, "WriteCapacityUnits": 20}, + ) + + table = dynamo.Table(table_name) + + table.put_item(Item={"Id": "item-id", "event_history": ["other_value"]}) + table.update_item( + Key={"Id": "item-id"}, + UpdateExpression="SET event_history = list_append(if_not_exists(event_history, :empty_list), :new_value)", + ExpressionAttributeValues={":empty_list": [], ":new_value": ["some_value"]}, + ) + table.get_item(Key={"Id": "item-id"})["Item"].should.equal( + {"Id": "item-id", "event_history": ["other_value", "some_value"]} + ) + + @mock_dynamodb2 def test_update_catches_invalid_list_append_operation(): client = boto3.client("dynamodb", region_name="us-east-1") diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py index 746c78719..8c036441c 100644 --- a/tests/test_route53/test_route53.py +++ b/tests/test_route53/test_route53.py @@ -753,6 +753,79 @@ def test_change_weighted_resource_record_sets(): record["Weight"].should.equal(10) +@mock_route53 +def test_failover_record_sets(): + conn = boto3.client("route53", region_name="us-east-2") + conn.create_hosted_zone(Name="test.zone.", CallerReference=str(hash("test"))) + zones = conn.list_hosted_zones_by_name(DNSName="test.zone.") + hosted_zone_id = zones["HostedZones"][0]["Id"] + + # Create geolocation record + conn.change_resource_record_sets( + HostedZoneId=hosted_zone_id, + ChangeBatch={ + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "failover.test.zone.", + "Type": "A", + "TTL": 10, + "ResourceRecords": [{"Value": "127.0.0.1"}], + "Failover": "PRIMARY", + }, + } + ] + }, + ) + + response = conn.list_resource_record_sets(HostedZoneId=hosted_zone_id) + record = response["ResourceRecordSets"][0] + record["Failover"].should.equal("PRIMARY") + + +@mock_route53 +def test_geolocation_record_sets(): + conn = boto3.client("route53", region_name="us-east-2") + conn.create_hosted_zone(Name="test.zone.", CallerReference=str(hash("test"))) + zones = conn.list_hosted_zones_by_name(DNSName="test.zone.") + hosted_zone_id = zones["HostedZones"][0]["Id"] + + # Create geolocation record + conn.change_resource_record_sets( + HostedZoneId=hosted_zone_id, + ChangeBatch={ + "Changes": [ + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "georecord1.test.zone.", + "Type": "A", + "TTL": 10, + "ResourceRecords": [{"Value": "127.0.0.1"}], + "GeoLocation": {"ContinentCode": "EU"}, + }, + }, + { + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "georecord2.test.zone.", + "Type": "A", + "TTL": 10, + "ResourceRecords": [{"Value": "127.0.0.2"}], + "GeoLocation": {"CountryCode": "US", "SubdivisionCode": "NY"}, + }, + }, + ] + }, + ) + + response = conn.list_resource_record_sets(HostedZoneId=hosted_zone_id) + rrs = response["ResourceRecordSets"] + rrs[0]["GeoLocation"].should.equal({"ContinentCode": "EU"}) + rrs[1]["GeoLocation"].should.equal({"CountryCode": "US", "SubdivisionCode": "NY"}) + + @mock_route53 def test_change_resource_record_invalid(): conn = boto3.client("route53", region_name="us-east-1") diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py index 5b978520d..bb674fb65 100644 --- a/tests/test_ssm/test_ssm_boto3.py +++ b/tests/test_ssm/test_ssm_boto3.py @@ -30,6 +30,18 @@ def test_delete_parameter(): len(response["Parameters"]).should.equal(0) +@mock_ssm +def test_delete_nonexistent_parameter(): + client = boto3.client("ssm", region_name="us-east-1") + + with assert_raises(ClientError) as ex: + client.delete_parameter(Name="test_noexist") + ex.exception.response["Error"]["Code"].should.equal("ParameterNotFound") + ex.exception.response["Error"]["Message"].should.equal( + "Parameter test_noexist not found." + ) + + @mock_ssm def test_delete_parameters(): client = boto3.client("ssm", region_name="us-east-1") diff --git a/tests/test_swf/responses/test_activity_types.py b/tests/test_swf/responses/test_activity_types.py index 3fa9ad6b1..d49e5d4cb 100644 --- a/tests/test_swf/responses/test_activity_types.py +++ b/tests/test_swf/responses/test_activity_types.py @@ -1,8 +1,11 @@ import boto from boto.swf.exceptions import SWFResponseError +import boto3 +from botocore.exceptions import ClientError import sure # noqa from moto import mock_swf_deprecated +from moto import mock_swf # RegisterActivityType endpoint @@ -110,6 +113,77 @@ def test_deprecate_non_existent_activity_type(): ).should.throw(SWFResponseError) +# DeprecateActivityType endpoint +@mock_swf +def test_undeprecate_activity_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + client.deprecate_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + client.undeprecate_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + + resp = client.describe_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + resp["typeInfo"]["status"].should.equal("REGISTERED") + + +@mock_swf +def test_undeprecate_already_undeprecated_activity_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + client.deprecate_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + client.undeprecate_activity_type( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ) + + client.undeprecate_activity_type.when.called_with( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ).should.throw(ClientError) + + +@mock_swf +def test_undeprecate_never_deprecated_activity_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_activity_type( + domain="test-domain", name="test-activity", version="v1.0" + ) + + client.undeprecate_activity_type.when.called_with( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ).should.throw(ClientError) + + +@mock_swf +def test_undeprecate_non_existent_activity_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + client.undeprecate_activity_type.when.called_with( + domain="test-domain", activityType={"name": "test-activity", "version": "v1.0"} + ).should.throw(ClientError) + + # DescribeActivityType endpoint @mock_swf_deprecated def test_describe_activity_type(): diff --git a/tests/test_swf/responses/test_domains.py b/tests/test_swf/responses/test_domains.py index 199219d27..59ba551a6 100644 --- a/tests/test_swf/responses/test_domains.py +++ b/tests/test_swf/responses/test_domains.py @@ -1,8 +1,11 @@ import boto from boto.swf.exceptions import SWFResponseError +import boto3 +from botocore.exceptions import ClientError import sure # noqa from moto import mock_swf_deprecated +from moto import mock_swf # RegisterDomain endpoint @@ -94,6 +97,56 @@ def test_deprecate_non_existent_domain(): ) +# UndeprecateDomain endpoint +@mock_swf +def test_undeprecate_domain(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.deprecate_domain(name="test-domain") + client.undeprecate_domain(name="test-domain") + + resp = client.describe_domain(name="test-domain") + + resp["domainInfo"]["status"].should.equal("REGISTERED") + + +@mock_swf +def test_undeprecate_already_undeprecated_domain(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.deprecate_domain(name="test-domain") + client.undeprecate_domain(name="test-domain") + + client.undeprecate_domain.when.called_with(name="test-domain").should.throw( + ClientError + ) + + +@mock_swf +def test_undeprecate_never_deprecated_domain(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + client.undeprecate_domain.when.called_with(name="test-domain").should.throw( + ClientError + ) + + +@mock_swf +def test_undeprecate_non_existent_domain(): + client = boto3.client("swf", region_name="us-east-1") + + client.undeprecate_domain.when.called_with(name="non-existent").should.throw( + ClientError + ) + + # DescribeDomain endpoint @mock_swf_deprecated def test_describe_domain(): diff --git a/tests/test_swf/responses/test_workflow_types.py b/tests/test_swf/responses/test_workflow_types.py index 72aa814d2..e1990596b 100644 --- a/tests/test_swf/responses/test_workflow_types.py +++ b/tests/test_swf/responses/test_workflow_types.py @@ -5,6 +5,7 @@ import boto3 from moto import mock_swf_deprecated from moto import mock_swf from boto.swf.exceptions import SWFResponseError +from botocore.exceptions import ClientError # RegisterWorkflowType endpoint @@ -112,6 +113,77 @@ def test_deprecate_non_existent_workflow_type(): ).should.throw(SWFResponseError) +# UndeprecateWorkflowType endpoint +@mock_swf +def test_undeprecate_workflow_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + client.deprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + client.undeprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + + resp = client.describe_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + resp["typeInfo"]["status"].should.equal("REGISTERED") + + +@mock_swf +def test_undeprecate_already_undeprecated_workflow_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + client.deprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + client.undeprecate_workflow_type( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ) + + client.undeprecate_workflow_type.when.called_with( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ).should.throw(ClientError) + + +@mock_swf +def test_undeprecate_never_deprecated_workflow_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + client.register_workflow_type( + domain="test-domain", name="test-workflow", version="v1.0" + ) + + client.undeprecate_workflow_type.when.called_with( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ).should.throw(ClientError) + + +@mock_swf +def test_undeprecate_non_existent_workflow_type(): + client = boto3.client("swf", region_name="us-east-1") + client.register_domain( + name="test-domain", workflowExecutionRetentionPeriodInDays="60" + ) + + client.undeprecate_workflow_type.when.called_with( + domain="test-domain", workflowType={"name": "test-workflow", "version": "v1.0"} + ).should.throw(ClientError) + + # DescribeWorkflowType endpoint @mock_swf_deprecated def test_describe_workflow_type():