From b64d63380587c3a75e5e5504540c79efe48dedb6 Mon Sep 17 00:00:00 2001 From: Maziar Date: Thu, 16 Mar 2023 10:57:52 -0400 Subject: [PATCH] Updated SNS Subscription handlers for resource migration. **Modified** - aws-sns-subscription/src/main/*: Updated CRUDL handlers for migration, - aws-sns-subscription/src/test/*: Updated unit tests. --- aws-sns-subscription/.gitignore | 5 +- aws-sns-subscription/.rpdk-config | 3 +- .../aws-sns-subscription.json | 32 +- aws-sns-subscription/docs/README.md | 13 + aws-sns-subscription/resource-role.yaml | 9 +- .../sns/subscription/BaseHandlerStd.java | 267 ++++---- .../sns/subscription/CallbackContext.java | 4 +- .../sns/subscription/ClientBuilder.java | 1 + .../sns/subscription/Configuration.java | 1 + .../sns/subscription/CreateHandler.java | 78 +-- .../amazon/sns/subscription/Definitions.java | 4 +- .../sns/subscription/DeleteHandler.java | 90 ++- .../amazon/sns/subscription/ListHandler.java | 63 +- .../amazon/sns/subscription/ReadHandler.java | 40 +- .../subscription/SnsSubscriptionUtils.java | 1 + .../subscription/SubscriptionAttribute.java | 2 + .../amazon/sns/subscription/Translator.java | 46 +- .../sns/subscription/UpdateHandler.java | 150 ++--- .../sns/subscription/AbstractTestBase.java | 2 +- .../sns/subscription/CreateHandlerTest.java | 388 +++--------- .../sns/subscription/DeleteHandlerTest.java | 354 ++--------- .../sns/subscription/ListHandlerTest.java | 155 +---- .../sns/subscription/ReadHandlerTest.java | 83 +-- .../SnsSubscriptionUtilsTest.java | 4 +- .../sns/subscription/UpdateHandlerTest.java | 573 ++---------------- 25 files changed, 607 insertions(+), 1761 deletions(-) diff --git a/aws-sns-subscription/.gitignore b/aws-sns-subscription/.gitignore index 8a229a6..5eb6238 100644 --- a/aws-sns-subscription/.gitignore +++ b/aws-sns-subscription/.gitignore @@ -12,11 +12,12 @@ out.java out/ .settings .project -inputs/ # auto-generated files target/ # our logs rpdk.log -/bin/ + +# contains credentials +sam-tests/ diff --git a/aws-sns-subscription/.rpdk-config b/aws-sns-subscription/.rpdk-config index 43d0cef..c557ede 100644 --- a/aws-sns-subscription/.rpdk-config +++ b/aws-sns-subscription/.rpdk-config @@ -4,7 +4,7 @@ "language": "java", "runtime": "java8", "entrypoint": "software.amazon.sns.subscription.HandlerWrapper::handleRequest", - "testEntrypoint": "software.amazon.sns.subscription.HandlerWrapper::testEntrypoint", + "testEntrypoint": "software.amazon.sns.subscription.HandlerWrapper::handleRequest", "settings": { "namespace": [ "software", @@ -15,5 +15,6 @@ "codegen_template_path": "guided_aws", "protocolVersion": "2.0.0" }, + "logProcessorEnabled": "true", "executableEntrypoint": "software.amazon.sns.subscription.HandlerWrapperExecutable" } diff --git a/aws-sns-subscription/aws-sns-subscription.json b/aws-sns-subscription/aws-sns-subscription.json index e08fe01..d5f4c43 100644 --- a/aws-sns-subscription/aws-sns-subscription.json +++ b/aws-sns-subscription/aws-sns-subscription.json @@ -50,6 +50,10 @@ "description": "The filter policy JSON assigned to the subscription. Enables the subscriber to filter out unwanted messages. For more information, see [GetSubscriptionAttributes](https://docs.aws.amazon.com/sns/latest/api/API_GetSubscriptionAttributes.html) in the Amazon Simple Notification Service API Reference and [Message Filtering](https://docs.aws.amazon.com/sns/latest/dg/sns-message-filtering.html) in the Amazon SNS Developer Guide.", "type": "object" }, + "FilterPolicyScope": { + "description": "This attribute lets you choose the filtering scope.", + "type": "string" + }, "RedrivePolicy": { "description": "When specified, sends undeliverable messages to the specified Amazon SQS dead-letter queue. Messages that can't be delivered due to client errors (for example, when the subscribed endpoint is unreachable) or server errors (for example, when the service that powers the subscribed endpoint becomes unavailable) are held in the dead-letter queue for further analysis or reprocessing.", "type": "object" @@ -77,47 +81,35 @@ "readOnlyProperties": [ "/properties/SubscriptionArn" ], + "writeOnlyProperties": [ + "/properties/Region" + ], "handlers": { "create": { "permissions": [ - "sns:Subscribe", - "sns:GetSubscriptionAttributes", - "sns:GetTopicAttributes", - "iam:GetRole", - "iam:PassRole", - "iam:PutRolePolicy" + "sns:Subscribe" ] }, "read": { "permissions": [ - "sns:GetSubscriptionAttributes", - "sns:GetTopicAttributes" + "sns:GetSubscriptionAttributes" ] }, "update": { "permissions": [ "sns:SetSubscriptionAttributes", - "sns:GetSubscriptionAttributes", - "sns:GetTopicAttributes", - "iam:GetRole", - "iam:PassRole", - "iam:PutRolePolicy", - "iam:UpdateRole" + "sns:GetSubscriptionAttributes" ] }, "delete": { "permissions": [ - "sns:GetSubscriptionAttributes", - "sns:GetTopicAttributes", "sns:Unsubscribe", - "iam:DeleteRolePolicy", - "iam:DetachRolePolicy" + "sns:GetSubscriptionAttributes" ] }, "list": { "permissions": [ - "sns:GetTopicAttributes", - "sns:ListSubscriptionsByTopic" + "sns:ListSubscriptions" ] } } diff --git a/aws-sns-subscription/docs/README.md b/aws-sns-subscription/docs/README.md index fc8c640..2af6c09 100644 --- a/aws-sns-subscription/docs/README.md +++ b/aws-sns-subscription/docs/README.md @@ -20,6 +20,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy "SubscriptionRoleArn" : String, "TopicArn" : String, "FilterPolicy" : Map, + "FilterPolicyScope" : String, "RedrivePolicy" : Map } } @@ -38,6 +39,7 @@ Properties: SubscriptionRoleArn: String TopicArn: String FilterPolicy: Map + FilterPolicyScope: String RedrivePolicy: Map @@ -134,6 +136,16 @@ _Type_: Map _Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) +#### FilterPolicyScope + +This attribute lets you choose the filtering scope. + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + #### RedrivePolicy When specified, sends undeliverable messages to the specified Amazon SQS dead-letter queue. Messages that can't be delivered due to client errors (for example, when the subscribed endpoint is unreachable) or server errors (for example, when the service that powers the subscribed endpoint becomes unavailable) are held in the dead-letter queue for further analysis or reprocessing. @@ -159,3 +171,4 @@ For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::G #### SubscriptionArn This is the subscription amazon resource name generated at creation time. + diff --git a/aws-sns-subscription/resource-role.yaml b/aws-sns-subscription/resource-role.yaml index 8193901..409f94e 100644 --- a/aws-sns-subscription/resource-role.yaml +++ b/aws-sns-subscription/resource-role.yaml @@ -30,15 +30,8 @@ Resources: Statement: - Effect: Allow Action: - - "iam:DeleteRolePolicy" - - "iam:DetachRolePolicy" - - "iam:GetRole" - - "iam:PassRole" - - "iam:PutRolePolicy" - - "iam:UpdateRole" - "sns:GetSubscriptionAttributes" - - "sns:GetTopicAttributes" - - "sns:ListSubscriptionsByTopic" + - "sns:ListSubscriptions" - "sns:SetSubscriptionAttributes" - "sns:Subscribe" - "sns:Unsubscribe" diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/BaseHandlerStd.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/BaseHandlerStd.java index f75abe8..86a48f9 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/BaseHandlerStd.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/BaseHandlerStd.java @@ -1,186 +1,131 @@ package software.amazon.sns.subscription; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesResponse; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; import software.amazon.cloudformation.exceptions.CfnServiceLimitExceededException; import software.amazon.cloudformation.exceptions.CfnNotFoundException; import software.amazon.cloudformation.exceptions.CfnInternalFailureException; import software.amazon.cloudformation.exceptions.CfnAccessDeniedException; import software.amazon.cloudformation.exceptions.CfnInvalidCredentialsException; - +import software.amazon.cloudformation.exceptions.CfnGeneralServiceException; +import software.amazon.cloudformation.exceptions.BaseHandlerException; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesResponse; -import software.amazon.awssdk.services.sns.model.GetTopicAttributesRequest; -import software.amazon.awssdk.services.sns.model.GetTopicAttributesResponse; -import software.amazon.awssdk.services.sns.model.InternalErrorException; -import software.amazon.awssdk.services.sns.model.InvalidParameterException; -import software.amazon.awssdk.services.sns.model.InvalidSecurityException; -import software.amazon.awssdk.services.sns.model.NotFoundException; -import software.amazon.awssdk.services.sns.model.SubscriptionLimitExceededException; -import software.amazon.awssdk.services.sns.model.AuthorizationErrorException; -import software.amazon.awssdk.services.sns.model.FilterPolicyLimitExceededException; -import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesRequest; -import software.amazon.awssdk.awscore.AwsRequest; -import software.amazon.awssdk.awscore.AwsResponse; +import software.amazon.awssdk.services.sns.model.SnsRequest; import software.amazon.awssdk.regions.Region; +import java.util.Objects; -public abstract class BaseHandlerStd extends BaseHandler { - @Override - public final ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final Logger logger) { - - return handleRequest( - proxy, - request, - callbackContext != null ? callbackContext : new CallbackContext(), - proxy.newProxy(() -> {return (request.getDesiredResourceState().getRegion() != null) ? - ClientBuilder.getClient(Region.of(request.getDesiredResourceState().getRegion())) : - ClientBuilder.getClient();}), - logger - ); - } - - protected ProgressEvent checkTopicExists( - AmazonWebServicesClientProxy proxy, - ProxyClient proxyClient, - ResourceModel model, - ProgressEvent progress, - Logger logger) { - - - logger.log("checking if topic exists"); - return proxy.initiate("AWS-SNS-Subscription::CheckTopicExists", proxyClient, model, progress.getCallbackContext()) - .translateToServiceRequest(Translator::translateToCheckTopicRequest) - .makeServiceCall(this::retrieveTopicAttributes) - .progress(); - } - - protected ProgressEvent checkSubscriptionExists( - AmazonWebServicesClientProxy proxy, - ProxyClient proxyClient, - ResourceModel model, - ProgressEvent progress, - Logger logger) { - - logger.log(String.format("checking if subscription ARN %s exists ", model.getSubscriptionArn())); - return proxy.initiate("AWS-SNS-Subscription::CheckSubscriptionExists", proxyClient, model, progress.getCallbackContext()) - .translateToServiceRequest(Translator::translateToReadRequest) - .makeServiceCall(this::readSubscriptionAttributes) - .progress(); - } - - protected abstract ProgressEvent handleRequest( - final AmazonWebServicesClientProxy proxy, - final ResourceHandlerRequest request, - final CallbackContext callbackContext, - final ProxyClient proxyClient, - final Logger logger); - - protected GetSubscriptionAttributesResponse readSubscriptionAttributes(GetSubscriptionAttributesRequest getSubscriptionAttributesRequest, - final ProxyClient proxyClient) { - - final GetSubscriptionAttributesResponse getSubscriptionAttributesResponse; +public abstract class BaseHandlerStd extends BaseHandler { + private static String PENDING_CONFIRMATION_ERROR = "Invalid Arn \"%s\". Please verify that the subscription is confirmed before trying to update attributes"; + public static String AUTHORIZATION_ERROR= "AuthorizationError"; + public static String FILTER_POLICY_LIMIT_EXCEEDED = "FilterPolicyLimitExceeded"; + public static String INTERNAL_ERROR = "InternalError"; + public static String INVALID_PARAMETER = "InvalidParameter"; + public static String INVALID_SECURITY = "InvalidSecurity"; + public static String NOT_FOUND = "NotFound"; + public static String SUBSCRIPTION_LIMIT_EXCEEDED = "SubscriptionLimitExceeded"; + + protected abstract ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final ProxyClient proxyClient, + final Logger logger); + + @Override + public final ProgressEvent handleRequest( + final AmazonWebServicesClientProxy proxy, + final ResourceHandlerRequest request, + final CallbackContext callbackContext, + final Logger logger) { + + return handleRequest( + proxy, + request, + callbackContext != null ? callbackContext : new CallbackContext(), + proxy.newProxy(() -> {return (request.getDesiredResourceState().getRegion() != null) ? + ClientBuilder.getClient(Region.of(request.getDesiredResourceState().getRegion())) : + ClientBuilder.getClient();}), + logger + ); + } - try { - if (getSubscriptionAttributesRequest.subscriptionArn() == null) { - throw new CfnNotFoundException(new Exception("Subscription is null")); - } + protected ProgressEvent checkSubscriptionExistsAndSetSubscriptionArn( + AmazonWebServicesClientProxy proxy, + ProxyClient proxyClient, + ResourceHandlerRequest request, + ProgressEvent progress, + final CallbackContext callbackContext, + boolean successUponWaitingForConfirmation, + Logger logger) { + + ResourceModel resourceModel = request.getDesiredResourceState(); + logger.log(String.format("checking if subscription exists in Topic ARN %s ", resourceModel.getTopicArn())); + + return proxy.initiate("AWS-SNS-Subscription::CheckSubscriptionExists", proxyClient, resourceModel, progress.getCallbackContext()) + .translateToServiceRequest(m -> Translator.translateToReadRequest(resourceModel)) + .makeServiceCall((readRequest, client) -> { + GetSubscriptionAttributesResponse getSubscriptionAttributesResponse = client.injectCredentialsAndInvokeV2(readRequest, client.client()::getSubscriptionAttributes); + return getSubscriptionAttributesResponse; + }) + .handleError((awsRequest, exception, client, model, context) -> handleError(awsRequest, exception, client, model, context)) + .done(getSubscriptionAttributesResponse -> { + //If ARN is PendingConfirmation, it should fail for update and succeed for delete + if (getSubscriptionAttributesResponse.hasAttributes() && Objects.equals(getSubscriptionAttributesResponse.attributes().get(Definitions.pendingConfirmation), Definitions.subscriptionNotPending)) { + if (successUponWaitingForConfirmation) { + return ProgressEvent.success(request.getDesiredResourceState(), callbackContext, String.format(PENDING_CONFIRMATION_ERROR, resourceModel.getSubscriptionArn())); + } else { + //Fail the update + return ProgressEvent.failed(request.getDesiredResourceState(), callbackContext, HandlerErrorCode.NotFound, String.format(PENDING_CONFIRMATION_ERROR, resourceModel.getSubscriptionArn())); + } + //If ARN is valid, we can proceed with delete/update + } else { + return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext); + } + }); + } - getSubscriptionAttributesResponse = proxyClient.injectCredentialsAndInvokeV2(getSubscriptionAttributesRequest, proxyClient.client()::getSubscriptionAttributes); - if (!getSubscriptionAttributesResponse.hasAttributes()) { - throw new CfnNotFoundException(new Exception(String.format("Subscription %s does not exist.", getSubscriptionAttributesRequest.subscriptionArn()))); + protected ProgressEvent handleError( + final SnsRequest request, + final Exception e, + final ProxyClient proxyClient, + final ResourceModel resourceModel, + final CallbackContext callbackContext) { + + BaseHandlerException ex = null; + + if (AUTHORIZATION_ERROR.equals(getErrorCode(e))) { + ex = new CfnAccessDeniedException(e); + } else if(FILTER_POLICY_LIMIT_EXCEEDED.equals(getErrorCode(e))){ + ex = new CfnServiceLimitExceededException(e); + } else if(INTERNAL_ERROR.equals(getErrorCode(e))){ + ex = new CfnInternalFailureException(e); + } else if (INVALID_PARAMETER.equals(getErrorCode(e))) { + ex = new CfnInvalidRequestException(e); + } else if (INVALID_SECURITY.equals(getErrorCode(e))){ + ex = new CfnInvalidCredentialsException(e); + } else if (NOT_FOUND.equals(getErrorCode(e))){ + ex = new CfnNotFoundException(e); + } else if (SUBSCRIPTION_LIMIT_EXCEEDED.equals(getErrorCode(e))){ + ex = new CfnServiceLimitExceededException(e); + } else { + ex = new CfnGeneralServiceException(e); } - } catch (final SubscriptionLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final FilterPolicyLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final InvalidParameterException e) { - throw new CfnInvalidRequestException(e); - } catch (final InternalErrorException e) { - throw new CfnInternalFailureException(e); - } catch (final NotFoundException e) { - throw new CfnNotFoundException(e); - } catch (final AuthorizationErrorException e) { - throw new CfnAccessDeniedException(e); - } catch (final InvalidSecurityException e) { - throw new CfnInvalidCredentialsException(e); + return ProgressEvent.failed(resourceModel, callbackContext, ex.getErrorCode(), ex.getMessage()); } - return getSubscriptionAttributesResponse; - } - - protected GetTopicAttributesResponse retrieveTopicAttributes(GetTopicAttributesRequest getTopicAttributesRequest, - final ProxyClient proxyClient) { - - final GetTopicAttributesResponse getTopicAttributesResponse; - - try { - getTopicAttributesResponse = proxyClient.injectCredentialsAndInvokeV2(getTopicAttributesRequest, proxyClient.client()::getTopicAttributes); - - } catch (final SubscriptionLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final FilterPolicyLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final InvalidParameterException e) { - throw new CfnInvalidRequestException(e); - } catch (final InternalErrorException e) { - throw new CfnInternalFailureException(e); - } catch (final NotFoundException e) { - throw new CfnNotFoundException(e); - } catch (final AuthorizationErrorException e) { - throw new CfnAccessDeniedException(e); - } catch (final InvalidSecurityException e) { - throw new CfnInvalidCredentialsException(e); - } catch (final Exception e) { - throw new CfnInternalFailureException(e); + protected static String getErrorCode(Exception e) { + if (e instanceof AwsServiceException) { + return ((AwsServiceException) e).awsErrorDetails().errorCode(); + } + return e.getMessage(); } - - return getTopicAttributesResponse; - } - - protected boolean checkSubscriptionNotPending(final String subscriptionArn, final ProxyClient proxyClient, final Logger logger) { - - logger.log("Checking if a subscription is pending."); - final GetSubscriptionAttributesRequest getSubscriptionAttributesRequest = GetSubscriptionAttributesRequest.builder() - .subscriptionArn(subscriptionArn) - .build(); - - final GetSubscriptionAttributesResponse getSubscriptionAttributesResponse; - getSubscriptionAttributesResponse = readSubscriptionAttributes(getSubscriptionAttributesRequest, proxyClient); - - if (getSubscriptionAttributesResponse.hasAttributes() && - getSubscriptionAttributesResponse.attributes().get(Definitions.pendingConfirmation).equals(Definitions.subscriptionNotPending)) - return true; - - return false; - } - - protected boolean stabilizeSnsSubscription( - final AwsRequest awsRequest, - final AwsResponse awsResponse, - final ProxyClient proxyClient, - final ResourceModel model, - final CallbackContext callbackContext) { - - try { - final GetSubscriptionAttributesRequest getSubscriptionAttributesRequest = GetSubscriptionAttributesRequest.builder() - .subscriptionArn(model.getSubscriptionArn()) - .build(); - - readSubscriptionAttributes(getSubscriptionAttributesRequest, proxyClient); - } catch (CfnNotFoundException e) { - return false; - } - - return true; - } } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CallbackContext.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CallbackContext.java index 7377227..c1ee86d 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CallbackContext.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CallbackContext.java @@ -2,10 +2,12 @@ import software.amazon.cloudformation.proxy.StdCallbackContext; + @lombok.Getter @lombok.Setter @lombok.ToString @lombok.EqualsAndHashCode(callSuper = true) public class CallbackContext extends StdCallbackContext { - + private boolean isItFirstTime = true; + private boolean propagationDelay = false; } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ClientBuilder.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ClientBuilder.java index 9d69709..0331a48 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ClientBuilder.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ClientBuilder.java @@ -4,6 +4,7 @@ import software.amazon.cloudformation.LambdaWrapper; import software.amazon.awssdk.regions.Region; + public class ClientBuilder { public static SnsClient getClient() { diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Configuration.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Configuration.java index 388a967..de2f796 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Configuration.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Configuration.java @@ -1,5 +1,6 @@ package software.amazon.sns.subscription; + class Configuration extends BaseConfiguration { public Configuration() { diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CreateHandler.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CreateHandler.java index 87e01a4..ee71b1d 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CreateHandler.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/CreateHandler.java @@ -1,26 +1,14 @@ package software.amazon.sns.subscription; +import com.amazonaws.util.StringUtils; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.SubscribeResponse; -import software.amazon.awssdk.services.sns.model.SubscribeRequest; -import software.amazon.awssdk.services.sns.model.SubscriptionLimitExceededException; -import software.amazon.awssdk.services.sns.model.FilterPolicyLimitExceededException; -import software.amazon.awssdk.services.sns.model.InvalidParameterException; -import software.amazon.awssdk.services.sns.model.InternalErrorException; -import software.amazon.awssdk.services.sns.model.NotFoundException; -import software.amazon.awssdk.services.sns.model.AuthorizationErrorException; -import software.amazon.awssdk.services.sns.model.InvalidSecurityException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; -import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; -import software.amazon.cloudformation.exceptions.CfnServiceLimitExceededException; -import software.amazon.cloudformation.exceptions.CfnNotFoundException; -import software.amazon.cloudformation.exceptions.CfnInternalFailureException; -import software.amazon.cloudformation.exceptions.CfnAccessDeniedException; -import software.amazon.cloudformation.exceptions.CfnInvalidCredentialsException; +import software.amazon.cloudformation.proxy.HandlerErrorCode; + public class CreateHandler extends BaseHandlerStd { private Logger logger; @@ -33,52 +21,24 @@ protected ProgressEvent handleRequest( final Logger logger) { this.logger = logger; + ResourceModel resourceModel = request.getDesiredResourceState(); - final ResourceModel model = request.getDesiredResourceState(); - - return ProgressEvent.progress(model, callbackContext) - .then(progress -> - proxy.initiate("AWS-SNS-Subscription::Create", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::translateToCreateRequest) - .makeServiceCall((subscribeRequest, client) -> createSubscription(subscribeRequest, client, model)) - .progress()) - .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); - - } - - private SubscribeResponse createSubscription( - final SubscribeRequest subscribeRequest, - final ProxyClient proxyClient, - final ResourceModel model) { - - final SubscribeResponse subscribeResponse; - - // exception thrown if topic not found - retrieveTopicAttributes(Translator.translateToCheckTopicRequest(model), proxyClient); - - try { - subscribeResponse = proxyClient.injectCredentialsAndInvokeV2(subscribeRequest, proxyClient.client()::subscribe); - model.setSubscriptionArn(subscribeResponse.subscriptionArn()); - } catch (final SubscriptionLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final FilterPolicyLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final InvalidParameterException e) { - throw new CfnInvalidRequestException(e); - } catch (final InternalErrorException e) { - throw new CfnInternalFailureException(e); - } catch (final NotFoundException e) { - throw new CfnNotFoundException(e); - } catch (final AuthorizationErrorException e) { - throw new CfnAccessDeniedException(e); - } catch (final InvalidSecurityException e) { - throw new CfnInvalidCredentialsException(e); - } catch (final Exception e) { - throw new CfnInternalFailureException(e); + if (resourceModel == null || StringUtils.isNullOrEmpty(resourceModel.getProtocol())) { + return ProgressEvent.failed(resourceModel, callbackContext, HandlerErrorCode.InvalidRequest, "Protocol is required"); + } else if (StringUtils.isNullOrEmpty(resourceModel.getTopicArn())) { + return ProgressEvent.failed(resourceModel, callbackContext, HandlerErrorCode.InvalidRequest, "Topic ARN is required"); } - logger.log(String.format("%s successfully created.", ResourceModel.TYPE_NAME)); - return subscribeResponse; + logger.log(String.format("[StackId: %s, ClientRequestToken: %s] Calling Create SNS Subscription", request.getStackId(), + request.getClientRequestToken())); + + return proxy.initiate("AWS-SNS-Subscription::Create", proxyClient, resourceModel, callbackContext) + .translateToServiceRequest(Translator::translateToCreateRequest) + .makeServiceCall((subscribeRequest, client) -> client.injectCredentialsAndInvokeV2(subscribeRequest, client.client()::subscribe)) + .handleError((awsRequest, exception, client, model, context) -> handleError(awsRequest, exception, client, model, context)) + .done((subscribeRequest, subscribeResponse, client, model, callbackContext1) -> { + model.setSubscriptionArn(subscribeResponse.subscriptionArn()); + return ProgressEvent.success(model, callbackContext1); + }); } - } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Definitions.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Definitions.java index f9506c7..4f472af 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Definitions.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Definitions.java @@ -1,5 +1,6 @@ package software.amazon.sns.subscription; + public class Definitions { public static String topicArn = "TopicArn"; public static String endpoint = "Endpoint"; @@ -10,6 +11,7 @@ public class Definitions { public static String rawMessageDelivery = "RawMessageDelivery"; public static String subscriptionArn = "SubscriptionArn"; public static String pendingConfirmation = "PendingConfirmation"; - public static String subscriptionNotPending = "false"; + public static String subscriptionNotPending = "true"; public static String subscriptionRoleArn = "SubscriptionRoleArn"; + public static String filterPolicyScope = "FilterPolicyScope"; } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/DeleteHandler.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/DeleteHandler.java index 756a9b5..e8c11dd 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/DeleteHandler.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/DeleteHandler.java @@ -1,12 +1,17 @@ package software.amazon.sns.subscription; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.*; -import software.amazon.cloudformation.proxy.*; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.HandlerErrorCode; public class DeleteHandler extends BaseHandlerStd { + final private int EVENTUAL_CONSISTENCY_DELAY_SECONDS = 60; + final private String ARN_SEPERATOR =":"; private Logger logger; protected ProgressEvent handleRequest( @@ -17,56 +22,39 @@ protected ProgressEvent handleRequest( final Logger logger) { this.logger = logger; + ResourceModel resourceModel = request.getDesiredResourceState(); + final String subscriptionArn = resourceModel.getSubscriptionArn(); - final ResourceModel model = request.getDesiredResourceState(); - - return ProgressEvent.progress(model, callbackContext) - .then(progress -> checkTopicExists(proxy, proxyClient, model, progress, logger)) - .then(progress -> checkSubscriptionExists(proxy, proxyClient, model, progress, logger)) - .then(process -> proxy.initiate("AWS-SNS-Subscription::Check-Subscription-Not-Pending", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::translateToReadRequest) - .makeServiceCall((getSubscriptionAttributesRequest, client) -> { - if (!checkSubscriptionNotPending(model.getSubscriptionArn(), proxyClient, logger)) - throw new CfnInvalidRequestException(new Exception(String.format("subscription %s cannot be deleted if pending confirmation", model.getSubscriptionArn()))); - - return true; - }) - .progress()) - .then(process -> proxy.initiate("AWS-SNS-Subscription::Unsubscribe", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::translateToDeleteRequest) - .makeServiceCall(this::deleteSubscription) - .done(awsResponse -> ProgressEvent.builder() - .status(OperationStatus.SUCCESS) - .build())); - } - - private Boolean deleteSubscription( - final UnsubscribeRequest unsubscribeRequest, - final ProxyClient proxyClient) { - - try { - logger.log(String.format("Deleting subscription for subscription arn: %s", unsubscribeRequest.subscriptionArn())); - proxyClient.injectCredentialsAndInvokeV2(unsubscribeRequest, proxyClient.client()::unsubscribe); - } catch (final SubscriptionLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final FilterPolicyLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final InvalidParameterException e) { - throw new CfnInvalidRequestException(e); - } catch (final InternalErrorException e) { - throw new CfnInternalFailureException(e); - } catch (final NotFoundException e) { - throw new CfnNotFoundException(e); - } catch (final AuthorizationErrorException e) { - throw new CfnAccessDeniedException(e); - } catch (final InvalidSecurityException e) { - throw new CfnInvalidCredentialsException(e); - } catch (final Exception e) { - throw new CfnInternalFailureException(e); + //Note that although we check the existence of Subscription ARN, it does not necessarily mean it's a valid one + //Subscription could be PendingConfirmation + if (resourceModel == null || com.amazonaws.util.StringUtils.isNullOrEmpty(subscriptionArn)) { + return ProgressEvent.failed(resourceModel, callbackContext, HandlerErrorCode.InvalidRequest, "Subscription ARN is required"); } - logger.log(String.format("%s successfully deleted.", ResourceModel.IDENTIFIER_KEY_SUBSCRIPTIONARN)); - return true; + final String topicArn = subscriptionArn.substring(0, subscriptionArn.lastIndexOf(ARN_SEPERATOR)); + request.getDesiredResourceState().setTopicArn(topicArn); + + logger.log(String.format("[StackId: %s, ClientRequestToken: %s] Calling Delete SNS Subscription", request.getStackId(), + request.getClientRequestToken())); + + return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext) + .then(progress -> checkSubscriptionExistsAndSetSubscriptionArn(proxy, proxyClient, request, progress, callbackContext, true, logger)) + .then(progress -> proxy.initiate("AWS-SNS-Subscription::Delete", proxyClient, resourceModel, callbackContext) + .translateToServiceRequest(Translator::translateToDeleteRequest) + .makeServiceCall((unsubscribeRequest, client) -> client.injectCredentialsAndInvokeV2(unsubscribeRequest, proxyClient.client()::unsubscribe)) + .handleError((awsRequest, exception, client, model, context) -> handleError(awsRequest, exception, client, model, context)) + .progress()) + .then(progress -> { + if (progress.getCallbackContext().isPropagationDelay()) { + logger.log("Propagation delay completed"); + return ProgressEvent.progress(progress.getResourceModel(), progress.getCallbackContext()); + } + progress.getCallbackContext().setPropagationDelay(true); + callbackContext.setItFirstTime(false); + logger.log("Setting propagation delay"); + return ProgressEvent.defaultInProgressHandler(progress.getCallbackContext(), + EVENTUAL_CONSISTENCY_DELAY_SECONDS, progress.getResourceModel()); + }) + .then(progress -> ProgressEvent.defaultSuccessHandler(progress.getResourceModel())); } - } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ListHandler.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ListHandler.java index 6d88eed..35b6583 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ListHandler.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ListHandler.java @@ -4,28 +4,14 @@ import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; -import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.AuthorizationErrorException; -import software.amazon.awssdk.services.sns.model.FilterPolicyLimitExceededException; -import software.amazon.awssdk.services.sns.model.InternalErrorException; -import software.amazon.awssdk.services.sns.model.InvalidParameterException; -import software.amazon.awssdk.services.sns.model.InvalidSecurityException; -import software.amazon.awssdk.services.sns.model.ListSubscriptionsByTopicRequest; -import software.amazon.awssdk.services.sns.model.ListSubscriptionsByTopicResponse; -import software.amazon.awssdk.services.sns.model.NotFoundException; -import software.amazon.awssdk.services.sns.model.SubscriptionLimitExceededException; -import software.amazon.cloudformation.exceptions.CfnAccessDeniedException; -import software.amazon.cloudformation.exceptions.CfnInternalFailureException; -import software.amazon.cloudformation.exceptions.CfnInvalidCredentialsException; -import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; -import software.amazon.cloudformation.exceptions.CfnNotFoundException; -import software.amazon.cloudformation.exceptions.CfnServiceLimitExceededException; -import java.util.List; public class ListHandler extends BaseHandlerStd { + private Logger logger; @Override public ProgressEvent handleRequest( @@ -35,34 +21,25 @@ public ProgressEvent handleRequest( final ProxyClient proxyClient, final Logger logger) { - retrieveTopicAttributes(Translator.translateToCheckTopicRequest(request.getDesiredResourceState()), proxyClient); - final ListSubscriptionsByTopicRequest listSubscriptionsByTopicRequest = Translator.translateToListSubscriptionsByTopicRequest(request); - - final ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse; - try { - listSubscriptionsByTopicResponse = proxy.injectCredentialsAndInvokeV2(listSubscriptionsByTopicRequest, proxyClient.client()::listSubscriptionsByTopic); + this.logger = logger; + ResourceModel resourceModel = request.getDesiredResourceState(); - } catch (final SubscriptionLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final FilterPolicyLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final InvalidParameterException e) { - throw new CfnInvalidRequestException(e); - } catch (final InternalErrorException e) { - throw new CfnInternalFailureException(e); - } catch (final NotFoundException e) { - throw new CfnNotFoundException(e); - } catch (final AuthorizationErrorException e) { - throw new CfnAccessDeniedException(e); - } catch (final InvalidSecurityException e) { - throw new CfnInvalidCredentialsException(e); + if (resourceModel == null) { + return ProgressEvent.failed(resourceModel, callbackContext, HandlerErrorCode.InvalidRequest, "List request is invalid"); } - final List models = Translator.translateFromListRequest(listSubscriptionsByTopicResponse); - return ProgressEvent.builder() - .resourceModels(models) - .nextToken(listSubscriptionsByTopicResponse.nextToken()) - .status(OperationStatus.SUCCESS) - .build(); + logger.log(String.format("[StackId: %s, ClientRequestToken: %s] Calling List SNS Subscription", request.getStackId(), + request.getClientRequestToken())); + + return proxy.initiate("AWS-SNS-Subscription::List", proxyClient, resourceModel, callbackContext) + .translateToServiceRequest(m -> Translator.translateToListSubscriptionsRequest(request.getNextToken())) + .makeServiceCall((getSubscriptionsRequest, client) -> client.injectCredentialsAndInvokeV2(getSubscriptionsRequest, client.client()::listSubscriptions)) + .handleError((awsRequest, exception, client, model, context) -> handleError(awsRequest, exception, client, model, context)) + .done((getSubscriptionsRequest, getSubscriptionsResponse, client, resourceModel1, context) -> + ProgressEvent.builder() + .resourceModels(Translator.translateFromListRequest(getSubscriptionsResponse)) + .status(OperationStatus.SUCCESS) + .nextToken(getSubscriptionsResponse.nextToken()) + .build()); } } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ReadHandler.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ReadHandler.java index 65a7cea..1903b1b 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ReadHandler.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/ReadHandler.java @@ -1,9 +1,13 @@ package software.amazon.sns.subscription; - +import com.amazonaws.util.StringUtils; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesRequest; -import software.amazon.cloudformation.proxy.*; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.HandlerErrorCode; public class ReadHandler extends BaseHandlerStd { @@ -17,25 +21,19 @@ protected ProgressEvent handleRequest( final Logger logger) { this.logger = logger; + ResourceModel resourceModel = request.getDesiredResourceState(); - final ResourceModel model = request.getDesiredResourceState(); - logger.log("subscription arn: " + model.getSubscriptionArn()); - - return ProgressEvent.progress(model, callbackContext) - .then(progress -> checkTopicExists(proxy, proxyClient, model, progress, logger)) - .then(progress -> preliminaryGetSubscriptionCheck(Translator.translateToReadRequest(model), proxyClient, progress)) - .then(progress -> - proxy.initiate("AWS-SNS-Subscription::Read", proxyClient, model, callbackContext) - .translateToServiceRequest(Translator::translateToReadRequest) - .makeServiceCall((getSubscriptionAttributesRequest, client) -> readSubscriptionAttributes(getSubscriptionAttributesRequest, proxyClient)) - .done(getSubscriptionAttributesResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromReadResponse(getSubscriptionAttributesResponse)))); - } + if (resourceModel == null || StringUtils.isNullOrEmpty(resourceModel.getSubscriptionArn())) { + return ProgressEvent.failed(resourceModel, callbackContext, HandlerErrorCode.InvalidRequest, "Subscription ARN is required"); + } + + logger.log(String.format("[StackId: %s, ClientRequestToken: %s] Calling Read SNS Subscription", request.getStackId(), + request.getClientRequestToken())); - private ProgressEvent preliminaryGetSubscriptionCheck( - GetSubscriptionAttributesRequest getSubscriptionAttributesRequest, - ProxyClient proxyClient, - ProgressEvent progress) { - readSubscriptionAttributes(getSubscriptionAttributesRequest, proxyClient); - return progress; + return proxy.initiate("AWS-SNS-Subscription::Read", proxyClient, resourceModel, callbackContext) + .translateToServiceRequest(Translator::translateToReadRequest) + .makeServiceCall((getSubscriptionAttributesRequest, client) -> client.injectCredentialsAndInvokeV2(getSubscriptionAttributesRequest, client.client()::getSubscriptionAttributes)) + .handleError((awsRequest, exception, client, model, context) -> handleError(awsRequest, exception, client, model, context)) + .done(getSubscriptionAttributesResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromReadResponse(getSubscriptionAttributesResponse))); } } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SnsSubscriptionUtils.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SnsSubscriptionUtils.java index c958d11..db9cba3 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SnsSubscriptionUtils.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SnsSubscriptionUtils.java @@ -9,6 +9,7 @@ import java.util.Map; + public final class SnsSubscriptionUtils { public static Map convertToJson(String jsonString) { diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SubscriptionAttribute.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SubscriptionAttribute.java index fa34057..41674c6 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SubscriptionAttribute.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/SubscriptionAttribute.java @@ -1,6 +1,8 @@ package software.amazon.sns.subscription; + public enum SubscriptionAttribute { + FilterPolicyScope, DeliveryPolicy, FilterPolicy, RawMessageDelivery, diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Translator.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Translator.java index 09a2857..db02213 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Translator.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/Translator.java @@ -1,7 +1,12 @@ package software.amazon.sns.subscription; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesRequest; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesResponse; +import software.amazon.awssdk.services.sns.model.SetSubscriptionAttributesRequest; +import software.amazon.awssdk.services.sns.model.ListSubscriptionsResponse; +import software.amazon.awssdk.services.sns.model.ListSubscriptionsRequest; +import software.amazon.awssdk.services.sns.model.UnsubscribeRequest; +import software.amazon.awssdk.services.sns.model.SubscribeRequest; import java.util.Collection; import java.util.List; @@ -33,16 +38,17 @@ static GetSubscriptionAttributesRequest translateToReadRequest(final ResourceMod static ResourceModel translateFromReadResponse(final GetSubscriptionAttributesResponse getSubscriptionAttributesResponse) { final Map attributes = getSubscriptionAttributesResponse.attributes(); - Boolean rawMessageDelivery = attributes.get(Definitions.rawMessageDelivery) != null ? Boolean.valueOf(attributes.get(Definitions.rawMessageDelivery)) : null; - return ResourceModel.builder().subscriptionArn(attributes.get(Definitions.subscriptionArn)) + return ResourceModel.builder() + .subscriptionArn(attributes.get(Definitions.subscriptionArn)) .topicArn(attributes.get(Definitions.topicArn)) .endpoint(attributes.get(Definitions.endpoint)) .protocol(attributes.get(Definitions.protocol)) .filterPolicy(attributes.get(Definitions.filterPolicy) != null ? SnsSubscriptionUtils.convertToJson(attributes.get(Definitions.filterPolicy)) : null) .redrivePolicy(attributes.get(Definitions.redrivePolicy) != null ? SnsSubscriptionUtils.convertToJson(attributes.get(Definitions.redrivePolicy)) : null) .deliveryPolicy(attributes.get(Definitions.deliveryPolicy) != null ? SnsSubscriptionUtils.convertToJson(attributes.get(Definitions.deliveryPolicy)) : null) - .rawMessageDelivery(rawMessageDelivery) + .rawMessageDelivery(attributes.get(Definitions.rawMessageDelivery) != null ? Boolean.valueOf(attributes.get(Definitions.rawMessageDelivery)) : null) .subscriptionRoleArn(attributes.get(Definitions.subscriptionRoleArn)) + .filterPolicyScope(attributes.get(Definitions.filterPolicyScope)) .build(); } @@ -52,12 +58,6 @@ static UnsubscribeRequest translateToDeleteRequest(final ResourceModel model) { .build(); } - static GetTopicAttributesRequest translateToCheckTopicRequest(final ResourceModel model) { - return GetTopicAttributesRequest.builder() - .topicArn(model.getTopicArn()) - .build(); - } - static SetSubscriptionAttributesRequest translateToUpdateRequest(final SubscriptionAttribute subscriptionAttribute, final ResourceModel currentModel, final Map previousPolicy, final Map desiredPolicy) { Map mapAttributes = SnsSubscriptionUtils.getAttributesForUpdate(subscriptionAttribute, previousPolicy, desiredPolicy); SetSubscriptionAttributesRequest.Builder builder = SetSubscriptionAttributesRequest.builder().subscriptionArn(currentModel.getSubscriptionArn()); @@ -85,17 +85,19 @@ private static Stream streamOfOrEmpty(final Collection collection) { .orElseGet(Stream::empty); } + static List translateFromListRequest(final ListSubscriptionsResponse listSubscriptionsResponse) { + return streamOfOrEmpty(listSubscriptionsResponse.subscriptions()) + .map(subscription -> ResourceModel.builder() + .protocol(subscription.protocol()) + .topicArn(subscription.topicArn()) + .subscriptionArn(subscription.subscriptionArn()) + .build()) + .collect(Collectors.toList()); + } - static List translateFromListRequest(final ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse) { - return streamOfOrEmpty(listSubscriptionsByTopicResponse.subscriptions()).map(subscription -> - ResourceModel.builder().protocol(subscription.protocol()).topicArn(subscription.topicArn()).subscriptionArn(subscription.subscriptionArn()).build()) - .collect(Collectors.toList()); - } - - static ListSubscriptionsByTopicRequest translateToListSubscriptionsByTopicRequest(final ResourceHandlerRequest request) { - return ListSubscriptionsByTopicRequest.builder() - .nextToken(request.getNextToken()) - .topicArn(request.getDesiredResourceState().getTopicArn()) - .build(); + static ListSubscriptionsRequest translateToListSubscriptionsRequest(final String nextToken) { + return ListSubscriptionsRequest.builder() + .nextToken(nextToken) + .build(); } } diff --git a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/UpdateHandler.java b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/UpdateHandler.java index a674eec..1682d59 100644 --- a/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/UpdateHandler.java +++ b/aws-sns-subscription/src/main/java/software/amazon/sns/subscription/UpdateHandler.java @@ -1,15 +1,19 @@ package software.amazon.sns.subscription; - import org.apache.commons.lang3.StringUtils; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.*; -import software.amazon.cloudformation.proxy.*; +import software.amazon.cloudformation.exceptions.CfnNotUpdatableException; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.HandlerErrorCode; import java.util.Map; import java.util.Objects; + public class UpdateHandler extends BaseHandlerStd { private Logger logger; @@ -21,90 +25,88 @@ protected ProgressEvent handleRequest( final Logger logger) { this.logger = logger; + ResourceModel resourceModel = request.getDesiredResourceState(); + final String subscriptionArn = resourceModel.getSubscriptionArn(); + + //Note that although we check the existence of Subscription ARN, it does not necessarily mean it's a valid one + //Subscription could be PendingConfirmation + if (resourceModel == null || com.amazonaws.util.StringUtils.isNullOrEmpty(subscriptionArn)) { + return ProgressEvent.failed(resourceModel, callbackContext, HandlerErrorCode.InvalidRequest, "Subscription ARN is required"); + } + + logger.log(String.format("[StackId: %s, ClientRequestToken: %s] Calling Update SNS Subscription", request.getStackId(), + request.getClientRequestToken())); final ResourceModel currentModel = request.getDesiredResourceState(); final ResourceModel previousModel = request.getPreviousResourceState(); return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext) - .then(progress -> checkTopicExists(proxy, proxyClient, currentModel, progress, logger)) - .then(progress -> checkSubscriptionExists(proxy, proxyClient, previousModel, progress, logger)) - .then(progress -> validateCreateOnlyProperties(previousModel, currentModel, progress)) + .then(progress -> checkSubscriptionExistsAndSetSubscriptionArn(proxy, proxyClient, request, progress, callbackContext, false, logger)) + .then(progress -> validateRegionUpdate(previousModel, currentModel, request, progress)) + .then(progress -> modifyString(proxy, proxyClient, currentModel.getFilterPolicyScope(), currentModel, SubscriptionAttribute.FilterPolicyScope, previousModel.getFilterPolicyScope(), progress, logger)) .then(progress -> modifyPolicy(proxy, proxyClient, currentModel.getFilterPolicy(), currentModel, SubscriptionAttribute.FilterPolicy, previousModel.getFilterPolicy(), progress, logger)) .then(progress -> modifyPolicy(proxy, proxyClient, currentModel.getDeliveryPolicy(), currentModel, SubscriptionAttribute.DeliveryPolicy,previousModel.getDeliveryPolicy(), progress, logger)) .then(progress -> modifyPolicy(proxy, proxyClient, currentModel.getRedrivePolicy(), currentModel, SubscriptionAttribute.RedrivePolicy,previousModel.getRedrivePolicy(), progress, logger)) - .then(progress -> modifyRawMessageDelivery(proxy, proxyClient, currentModel.getRawMessageDelivery(), currentModel, SubscriptionAttribute.RawMessageDelivery,previousModel.getRawMessageDelivery(), progress, logger)) - .then(progress -> modifySubscriptionRoleArn(proxy, proxyClient, currentModel.getSubscriptionRoleArn(), currentModel, SubscriptionAttribute.SubscriptionRoleArn, previousModel.getSubscriptionRoleArn(), progress, logger)) - .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); + .then(progress -> modifyBoolean(proxy, proxyClient, currentModel.getRawMessageDelivery(), currentModel, SubscriptionAttribute.RawMessageDelivery,previousModel.getRawMessageDelivery(), progress, logger)) + .then(progress -> modifyString(proxy, proxyClient, currentModel.getSubscriptionRoleArn(), currentModel, SubscriptionAttribute.SubscriptionRoleArn, previousModel.getSubscriptionRoleArn(), progress, logger)) + .then(progress -> ProgressEvent.defaultSuccessHandler(progress.getResourceModel())); } - private ProgressEvent modifySubscriptionRoleArn( + private ProgressEvent validateRegionUpdate(ResourceModel previousModel, ResourceModel currentModel, ResourceHandlerRequest request, ProgressEvent progress) { + String prevRegion = previousModel.getRegion(); + String curRegion = currentModel.getRegion(); + final String currentRequestRegion = request.getRegion(); + + if (!StringUtils.equals( + prevRegion == null ? currentRequestRegion : prevRegion, + curRegion == null ? currentRequestRegion : curRegion)) { + throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, currentModel.getRegion()); + } + + return progress; + } + + private ProgressEvent modifyString( AmazonWebServicesClientProxy proxy, ProxyClient proxyClient, - String desiredSubscriptionRoleArn, + String desiredString, ResourceModel currentModel, SubscriptionAttribute subscriptionAttribute, - String previousSubscriptionRoleArn, + String previousString, ProgressEvent progress, Logger logger) { - if (StringUtils.equals(desiredSubscriptionRoleArn, previousSubscriptionRoleArn)) { + if (StringUtils.equals(desiredString, previousString)) { return progress; } - return proxy.initiate("AWS-SNS-Subscription::SubscriptionRoleArn", proxyClient, currentModel, progress.getCallbackContext()) - .translateToServiceRequest((resouceModel) -> Translator.translateToUpdateRequest(subscriptionAttribute, resouceModel, previousSubscriptionRoleArn, desiredSubscriptionRoleArn)) - .makeServiceCall(this::updateSubscription) - .stabilize(this::stabilizeSnsSubscription) + return proxy.initiate("AWS-SNS-Subscription::Update-"+subscriptionAttribute.name(), proxyClient, currentModel, progress.getCallbackContext()) + .translateToServiceRequest((resouceModel) -> Translator.translateToUpdateRequest(subscriptionAttribute, resouceModel, previousString, desiredString)) + .makeServiceCall((setSubscriptionAttributesRequest, client) -> proxyClient.injectCredentialsAndInvokeV2(setSubscriptionAttributesRequest, proxyClient.client()::setSubscriptionAttributes)) + .handleError((awsRequest, exception, client, resouceModel, context) -> handleError(awsRequest, exception, client, resouceModel, context)) .progress(); } - private ProgressEvent validateCreateOnlyProperties(ResourceModel previousModel, ResourceModel currentModel, ProgressEvent progress) { - - String prevEndpoint = previousModel.getEndpoint(); - String curEndpoint = currentModel.getEndpoint(); - - String prevProtocol = previousModel.getProtocol(); - String curProtocol = currentModel.getProtocol(); - - String prevTopicArn = previousModel.getTopicArn(); - String curTopicArn = currentModel.getTopicArn(); - - if (!arePropertiesEqual(prevEndpoint, curEndpoint)) { - throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, currentModel.getEndpoint()); - } else if (!arePropertiesEqual(prevProtocol, curProtocol)) { - throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, currentModel.getProtocol()); - } else if (!arePropertiesEqual(prevTopicArn, curTopicArn)) { - throw new CfnNotUpdatableException(ResourceModel.TYPE_NAME, currentModel.getTopicArn()); - } - - return progress; - } - - private boolean arePropertiesEqual(String value1, String value2) { - return StringUtils.equals(value1, value2); - } - - protected ProgressEvent modifyRawMessageDelivery( + protected ProgressEvent modifyBoolean( AmazonWebServicesClientProxy proxy, ProxyClient proxyClient, - Boolean rawMessageDelivery, + Boolean rawBoolean, ResourceModel model, SubscriptionAttribute subscriptionAttribute, - Boolean previousRawMessageDelivery, + Boolean previousBoolean, ProgressEvent progress, Logger logger) { - final Boolean desiredRawMessageDelivery = rawMessageDelivery != null ? rawMessageDelivery : Boolean.FALSE; - if (previousRawMessageDelivery == null || desiredRawMessageDelivery.equals(previousRawMessageDelivery)) { + final Boolean desiredRawMessageDelivery = rawBoolean != null ? rawBoolean : Boolean.FALSE; + if (previousBoolean == null || desiredRawMessageDelivery.equals(previousBoolean)) { return progress; } - return proxy.initiate("AWS-SNS-Subscription::RawMessageDelivery", proxyClient, model, progress.getCallbackContext()) - .translateToServiceRequest((resouceModel) -> Translator.translateToUpdateRequest(subscriptionAttribute, resouceModel, previousRawMessageDelivery, desiredRawMessageDelivery)) - .makeServiceCall(this::updateSubscription) - .stabilize(this::stabilizeSnsSubscription) + return proxy.initiate("AWS-SNS-Subscription::Update-"+subscriptionAttribute.name(), proxyClient, model, progress.getCallbackContext()) + .translateToServiceRequest((resouceModel) -> Translator.translateToUpdateRequest(subscriptionAttribute, resouceModel, previousBoolean, desiredRawMessageDelivery)) + .makeServiceCall((setSubscriptionAttributesRequest, client) -> proxyClient.injectCredentialsAndInvokeV2(setSubscriptionAttributesRequest, proxyClient.client()::setSubscriptionAttributes)) + .handleError((awsRequest, exception, client, resouceModel, context) -> handleError(awsRequest, exception, client, resouceModel, context)) .progress(); - } @@ -118,46 +120,14 @@ protected ProgressEvent modifyPolicy( ProgressEvent progress, Logger logger) { - if (Objects.equals(desiredPolicy,previousPolicy)) { + if (Objects.equals(desiredPolicy, previousPolicy)) { return progress; } - return proxy.initiate("AWS-SNS-Subscription::"+subscriptionAttribute.name(), proxyClient, model, progress.getCallbackContext()) + return proxy.initiate("AWS-SNS-Subscription::Update-"+subscriptionAttribute.name(), proxyClient, model, progress.getCallbackContext()) .translateToServiceRequest((resouceModel) -> Translator.translateToUpdateRequest(subscriptionAttribute, resouceModel, previousPolicy, desiredPolicy)) - .makeServiceCall(this::updateSubscription) - .stabilize(this::stabilizeSnsSubscription) + .makeServiceCall((setSubscriptionAttributesRequest, client) -> proxyClient.injectCredentialsAndInvokeV2(setSubscriptionAttributesRequest, proxyClient.client()::setSubscriptionAttributes)) + .handleError((awsRequest, exception, client, resouceModel, context) -> handleError(awsRequest, exception, client, resouceModel, context)) .progress(); - - } - - private SetSubscriptionAttributesResponse updateSubscription( - final SetSubscriptionAttributesRequest setSubscriptionAttributesRequest, - final ProxyClient proxyClient) { - - - final SetSubscriptionAttributesResponse setSubscriptionAttributesResponse; - try { - setSubscriptionAttributesResponse = proxyClient.injectCredentialsAndInvokeV2(setSubscriptionAttributesRequest, proxyClient.client()::setSubscriptionAttributes); - - } catch (final SubscriptionLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final FilterPolicyLimitExceededException e) { - throw new CfnServiceLimitExceededException(e); - } catch (final InvalidParameterException e) { - throw new CfnInvalidRequestException(e); - } catch (final InternalErrorException e) { - throw new CfnInternalFailureException(e); - } catch (final NotFoundException e) { - throw new CfnNotFoundException(e); - } catch (final AuthorizationErrorException e) { - throw new CfnAccessDeniedException(e); - } catch (final InvalidSecurityException e) { - throw new CfnInvalidCredentialsException(e); - } catch (final Exception e) { - throw new CfnInternalFailureException(e); - } - - logger.log(String.format("Subscription Arn %s is updated successfully", setSubscriptionAttributesRequest.subscriptionArn())); - return setSubscriptionAttributesResponse; } } diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/AbstractTestBase.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/AbstractTestBase.java index 241ef97..c3b25b0 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/AbstractTestBase.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/AbstractTestBase.java @@ -4,7 +4,6 @@ import java.util.function.Function; import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.AwsResponse; -import software.amazon.awssdk.core.SdkClient; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.pagination.sync.SdkIterable; @@ -14,6 +13,7 @@ import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.awssdk.services.sns.SnsClient; + public class AbstractTestBase { protected static final Credentials MOCK_CREDENTIALS; protected static final LoggerProxy logger; diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/CreateHandlerTest.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/CreateHandlerTest.java index ff484cc..9c54b70 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/CreateHandlerTest.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/CreateHandlerTest.java @@ -8,19 +8,37 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.*; -import software.amazon.cloudformation.proxy.*; +import software.amazon.awssdk.services.sns.model.SubscribeResponse; +import software.amazon.awssdk.services.sns.model.SubscribeRequest; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.OperationStatus; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.GENERAL_SERVICE_EXCEPTION; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.INVALID_REQUEST; +import static software.amazon.sns.subscription.BaseHandlerStd.INTERNAL_ERROR; +import static software.amazon.sns.subscription.BaseHandlerStd.INVALID_PARAMETER; import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) public class CreateHandlerTest extends AbstractTestBase { @@ -51,7 +69,7 @@ public void setup() throws JsonProcessingException { @AfterEach public void tear_down() { - verify(SnsClient, atLeastOnce()).serviceName(); + verify(SnsClient, atLeast(0)).serviceName(); verifyNoMoreInteractions(SnsClient); } @@ -86,54 +104,11 @@ private void buildObjects() throws JsonProcessingException { .build(); } - private ResourceModel buildObjectsSimpleAttributes() throws JsonProcessingException { - final ObjectMapper objectMapper = new ObjectMapper(); - - filterPolicyString = objectMapper.writeValueAsString(null); - redrivePolicyString = objectMapper.writeValueAsString(null); - deliveryPolicyString = objectMapper.writeValueAsString(null); - - model = ResourceModel.builder() - .protocol("email") - .endpoint("end1") - .topicArn("topicArn") - .filterPolicy(null) - .redrivePolicy(null) - .deliveryPolicy(null) - .rawMessageDelivery(null) - .build(); - - return model; - } - @Test - public void handleRequest_Success_TopicArnExists() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - + public void handleRequest_SimpleSuccess() { final SubscribeResponse subscribeResponse = SubscribeResponse.builder().subscriptionArn("testarn").build(); when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenReturn(subscribeResponse); - final Map attributes = new HashMap<>(); - - attributes.put("SubscriptionArn", subscribeResponse.subscriptionArn()); - attributes.put("TopicArn", model.getTopicArn()); - attributes.put("Protocol", model.getProtocol()); - attributes.put("Endpoint", model.getEndpoint()); - attributes.put("RawMessageDelivery", Boolean.toString(model.getRawMessageDelivery())); - attributes.put("FilterPolicy", filterPolicyString); - attributes.put("RedrivePolicy", redrivePolicyString); - attributes.put("DeliveryPolicy", deliveryPolicyString); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(model) .build(); @@ -141,7 +116,6 @@ public void handleRequest_Success_TopicArnExists() { final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); @@ -149,289 +123,103 @@ public void handleRequest_Success_TopicArnExists() { assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - - // create and read invocations - verify(proxyClient.client(), times(2)).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), times(2)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - } - @Test - public void handleRequest_Success_TopicArnExists_SimpleAttributes() throws JsonProcessingException { - - final ResourceModel model = buildObjectsSimpleAttributes(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final SubscribeResponse subscribeResponse = SubscribeResponse.builder().subscriptionArn("testarn").build(); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenReturn(subscribeResponse); - - final Map attributes = new HashMap<>(); - - attributes.put("SubscriptionArn", subscribeResponse.subscriptionArn()); - attributes.put("TopicArn", model.getTopicArn()); - attributes.put("Protocol", model.getProtocol()); - attributes.put("Endpoint", model.getEndpoint()); - attributes.put("RawMessageDelivery", null); - attributes.put("FilterPolicy", null); - attributes.put("RedrivePolicy", null); - attributes.put("DeliveryPolicy", null); - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - + public void handleRequest_Error() { final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(model) .build(); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); - - assertThat(response).isNotNull(); - - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - - // create and read invocations - verify(proxyClient.client(), times(2)).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), times(2)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - - } - - @Test - public void handleRequest_InvalidRequestExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(InvalidParameterException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionGeneral = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(GENERAL_SERVICE_EXCEPTION.toString()).build()) .build(); - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_InternalErrorExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(InternalErrorException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionInvalidRequest = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(INVALID_PARAMETER).build()) .build(); - - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_SubscriptionLimitExceededExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(SubscriptionLimitExceededException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionUnauth = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.AUTHORIZATION_ERROR).build()) .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_FilterPolicyLimitExceededExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(FilterPolicyLimitExceededException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionInvalidSecurity = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.INVALID_SECURITY).build()) .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_NotFoundExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(NotFoundException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionInternalError = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(INTERNAL_ERROR).build()) .build(); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_AuthorizationErrorExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(AuthorizationErrorException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionFilterPolicyLimit = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.FILTER_POLICY_LIMIT_EXCEEDED).build()) .build(); - - assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_GeneralRunTimeExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(RuntimeException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) + AwsServiceException exceptionSubsciptionLimit = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.SUBSCRIPTION_LIMIT_EXCEEDED).build()) + .build(); + AwsServiceException exceptionNotFound = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.NOT_FOUND).build()) .build(); - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - } - - @Test - public void handleRequest_InvalidSecurityExceptionThrown() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); + List exceptions = new ArrayList<>(); + exceptions.add(exceptionGeneral); + exceptions.add(exceptionInvalidRequest); + exceptions.add(exceptionUnauth); + exceptions.add(exceptionInvalidSecurity); + exceptions.add(exceptionInternalError); + exceptions.add(exceptionFilterPolicyLimit); + exceptions.add(exceptionSubsciptionLimit); + exceptions.add(exceptionNotFound); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenThrow(InvalidSecurityException.class); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); + for(Exception e : exceptions){ + lenient().when(proxyClient.client().subscribe(any(SubscribeRequest.class))) + .thenThrow(e); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + } - assertThrows(CfnInvalidCredentialsException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); + String ex = BaseHandlerStd.getErrorCode(new Exception("exception")); + assertThat(ex).isEqualTo("exception"); + verify(proxyClient.client(), atLeast(8)).subscribe(any(SubscribeRequest.class)); } @Test public void handleRequest_TopicArnDoesNotExist() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(NotFoundException.class); - + model = ResourceModel.builder() + .protocol("email") + .endpoint("end1") + .topicArn(null) + .filterPolicy(null) + .redrivePolicy(null) + .deliveryPolicy(null) + .rawMessageDelivery(null) + .build(); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(model) .build(); - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); - verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(INVALID_REQUEST.toString()); + verify(proxyClient.client(), never()).subscribe(any(SubscribeRequest.class)); } @Test - public void handleRequest_Success_SubscriptionRoleArn() { - - final String FIREHOSE_PROTOCOL = "firehose"; - final String DELIVERY_STREAM_ARN = "deliverStreamArn"; - final String TOPIC_ARN = "topicarn"; - final String SUBSCRIPTION_ARN = "testarn"; - final String SUBSCRIPTION_ROLE_ARN = "subscription-role-arn"; - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn",TOPIC_ARN); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final SubscribeResponse subscribeResponse = SubscribeResponse.builder().subscriptionArn(SUBSCRIPTION_ARN).build(); - when(proxyClient.client().subscribe(any(SubscribeRequest.class))).thenReturn(subscribeResponse); - - final Map attributes = new HashMap<>(); - - attributes.put("SubscriptionArn", subscribeResponse.subscriptionArn()); - attributes.put("TopicArn", TOPIC_ARN); - attributes.put("Protocol", FIREHOSE_PROTOCOL); - attributes.put("Endpoint", DELIVERY_STREAM_ARN); - attributes.put("SubscriptionRoleArn", SUBSCRIPTION_ROLE_ARN); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - - ResourceModel desiredModel = ResourceModel.builder() - .protocol(FIREHOSE_PROTOCOL) - .endpoint(DELIVERY_STREAM_ARN) - .topicArn(TOPIC_ARN) - .subscriptionArn(SUBSCRIPTION_ARN) - .subscriptionRoleArn(SUBSCRIPTION_ROLE_ARN) + public void handleRequest_ProtocolDoesNotExist() { + model = ResourceModel.builder() + .protocol(null) + .endpoint("end1") + .topicArn("topicArn") + .filterPolicy(null) + .redrivePolicy(null) + .deliveryPolicy(null) + .rawMessageDelivery(null) .build(); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) + .desiredResourceState(model) .build(); final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); - - assertThat(response).isNotNull(); - - assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); - assertThat(response.getResourceModels()).isNull(); - assertThat(response.getMessage()).isNull(); - assertThat(response.getErrorCode()).isNull(); - - - verify(proxyClient.client()).subscribe(any(SubscribeRequest.class)); - - // create and read invocations - verify(proxyClient.client(), times(2)).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), times(2)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(INVALID_REQUEST.toString()); + verify(proxyClient.client(), never()).subscribe(any(SubscribeRequest.class)); } } diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/DeleteHandlerTest.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/DeleteHandlerTest.java index 380df04..5d89f70 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/DeleteHandlerTest.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/DeleteHandlerTest.java @@ -6,19 +6,35 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.*; -import software.amazon.cloudformation.proxy.*; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesResponse; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesRequest; +import software.amazon.awssdk.services.sns.model.UnsubscribeRequest; +import software.amazon.awssdk.services.sns.model.UnsubscribeResponse; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.OperationStatus; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.INVALID_REQUEST; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.NOT_FOUND; import java.time.Duration; import java.util.HashMap; import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) public class DeleteHandlerTest extends AbstractTestBase { @@ -47,13 +63,13 @@ public void setup() { @AfterEach public void tear_down() { - verify(snsClient, atLeastOnce()).serviceName(); + verify(snsClient, atLeast(0)).serviceName(); verifyNoMoreInteractions(snsClient); } private void buildObjects() { - model = ResourceModel.builder().subscriptionArn("testArn").topicArn("topicArn").build(); + model = ResourceModel.builder().subscriptionArn("topicArn:testArn").topicArn("topicArn").build(); attributes = new HashMap<>(); attributes.put("SubscriptionArn", model.getSubscriptionArn()); attributes.put("TopicArn", "topicArn"); @@ -65,7 +81,7 @@ private void buildObjects() { private HashMap buildObjects_PendingTrue() { - model = ResourceModel.builder().subscriptionArn("testArn").topicArn("topicArn").build(); + model = ResourceModel.builder().subscriptionArn("topicArn:testArn").topicArn("topicArn").build(); HashMap attributes = new HashMap<>(); attributes.put("SubscriptionArn", model.getSubscriptionArn()); attributes.put("TopicArn", "topicArn"); @@ -79,16 +95,12 @@ private HashMap buildObjects_PendingTrue() { @Test public void handleRequest_SimpleSuccess() { + final CallbackContext context = new CallbackContext(); + context.setPropagationDelay(true); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); + .desiredResourceState(model) + .build(); final UnsubscribeResponse unsubscribeResponse = UnsubscribeResponse.builder().build(); when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenReturn(unsubscribeResponse); @@ -96,321 +108,77 @@ public void handleRequest_SimpleSuccess() { final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + final ProgressEvent response = handler.handleRequest(proxy, request, context, proxyClient, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); - assertThat(response.getResourceModel()).isNull(); - assertThat(response.getResourceModels()).isNull(); + assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); verify(proxyClient.client()).unsubscribe(any(UnsubscribeRequest.class)); - verify(proxyClient.client(), times(2)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test public void handleRequest_SubscriptionPending() { + final CallbackContext context = new CallbackContext(); + context.setPropagationDelay(true); final HashMap attributes = buildObjects_PendingTrue(); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); + .desiredResourceState(model) + .build(); final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); + final ProgressEvent response = handler.handleRequest(proxy, request, context, proxyClient, logger); + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); + assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); + assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); + assertThat(response.getMessage()).isEqualTo(String.format("Invalid Arn \"%s\". Please verify that the subscription is confirmed before trying to update attributes", model.getSubscriptionArn())); + assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); + verify(proxyClient.client(), times(0)).unsubscribe(any(UnsubscribeRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } - @Test - public void handleRequest_TopicArnDoesNotExist() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(NotFoundException.class); + public void handleRequest_SubscriptionArnDoesNotExist() { + model.setSubscriptionArn(null); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); + .desiredResourceState(model) + .build(); - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(INVALID_REQUEST.toString()); verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - - } - - @Test - public void handleRequest_TopicArnSubscriptionLimitExceededException() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(SubscriptionLimitExceededException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - } - - @Test - public void handleRequest_TopicArnFilterPolicyLimitExceededException() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(FilterPolicyLimitExceededException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - } - - @Test - public void handleRequest_TopicArnInvalidParameterExceptionException() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(InvalidParameterException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - } - - @Test - public void handleRequest_TopicArnInternalErrorException() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(InternalErrorException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - } - - @Test - public void handleRequest_TopicArnAuthorizationErrorException() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(AuthorizationErrorException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - } - - @Test - public void handleRequest_TopicArnInvalidSecurityException() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(InvalidSecurityException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnInvalidCredentialsException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); } @Test public void handleRequest_SubscriptionDoesNotExist() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(NotFoundException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - + AwsServiceException exception = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.NOT_FOUND).build()) + .build(); + when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(exception); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); + .desiredResourceState(model) + .build(); - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(NOT_FOUND.toString()); verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); verify(proxyClient.client()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - - } - - - @Test - public void handleRequest_SubscriptionLimitExceededExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(SubscriptionLimitExceededException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_FilterPolicyLimitExceededExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(FilterPolicyLimitExceededException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InvalidParameterExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(InvalidParameterException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InternalErrorExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(InternalErrorException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_AuthorizationErrorExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(AuthorizationErrorException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InvalidSecurityExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(InvalidSecurityException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnInvalidCredentialsException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_NotFoundExceptionUnsubscribe() { - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - when(proxyClient.client().unsubscribe(any(UnsubscribeRequest.class))).thenThrow(NotFoundException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(attributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } } diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ListHandlerTest.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ListHandlerTest.java index 3b2bfcd..9a99ad5 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ListHandlerTest.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ListHandlerTest.java @@ -5,21 +5,29 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.*; -import software.amazon.cloudformation.proxy.*; +import software.amazon.awssdk.services.sns.model.ListSubscriptionsResponse; +import software.amazon.awssdk.services.sns.model.ListSubscriptionsRequest; +import software.amazon.awssdk.services.sns.model.Subscription; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.OperationStatus; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + @ExtendWith(MockitoExtension.class) public class ListHandlerTest extends AbstractTestBase { @@ -48,7 +56,7 @@ public void setup() { private void buildObjects() { - model = ResourceModel.builder().subscriptionArn("testArn").topicArn("topicArn").build(); + model = ResourceModel.builder().subscriptionArn("topicArn:testArn").topicArn("topicArn").build(); attributes = new HashMap<>(); attributes.put("SubscriptionArn", model.getSubscriptionArn()); attributes.put("TopicArn", "topicArn"); @@ -66,14 +74,11 @@ public void handleRequest_SimpleSuccess() { listSubscriptions.add(subscription1); listSubscriptions.add(subscription2); - setupGetTopicAttributeMock(); - - - final ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse = ListSubscriptionsByTopicResponse.builder() + final ListSubscriptionsResponse listSubscriptionsResponse = ListSubscriptionsResponse.builder() .subscriptions(listSubscriptions) .build(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenReturn(listSubscriptionsByTopicResponse); + when(proxyClient.client().listSubscriptions(any(ListSubscriptionsRequest.class))).thenReturn(listSubscriptionsResponse); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(model) @@ -91,9 +96,7 @@ public void handleRequest_SimpleSuccess() { assertThat(response.getNextToken()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client(), times(1)).getTopicAttributes(any(GetTopicAttributesRequest.class)); - // verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - verify(proxyClient.client(), times(1)).listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class)); + verify(proxyClient.client(), times(1)).listSubscriptions(any(ListSubscriptionsRequest.class)); } @Test @@ -104,9 +107,7 @@ public void handleRequest_WithToken() { listSubscriptions.add(subscription1); listSubscriptions.add(subscription2); - setupGetTopicAttributeMock(); - - final ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse = ListSubscriptionsByTopicResponse.builder() + final ListSubscriptionsResponse listSubscriptionsResponse = ListSubscriptionsResponse.builder() .subscriptions(listSubscriptions) .nextToken("nextToken") .build(); @@ -115,12 +116,12 @@ public void handleRequest_WithToken() { final List listSubscriptions2 = new ArrayList<>(); listSubscriptions2.add(subscription3); - final ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse2 = ListSubscriptionsByTopicResponse.builder() + final ListSubscriptionsResponse listSubscriptionsResponse2 = ListSubscriptionsResponse.builder() .subscriptions(listSubscriptions2) .nextToken("") .build(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenReturn(listSubscriptionsByTopicResponse).thenReturn(listSubscriptionsByTopicResponse2); + when(proxyClient.client().listSubscriptions(any(ListSubscriptionsRequest.class))).thenReturn(listSubscriptionsResponse).thenReturn(listSubscriptionsResponse2); final ResourceModel model = ResourceModel.builder() .topicArn("topicArn") @@ -167,118 +168,6 @@ public void handleRequest_WithToken() { assertThat(response2.getErrorCode()).isNull(); assertThat(response2.getNextToken()).isEqualTo(""); - verify(proxyClient.client(), times(2)).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), times(2)).listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class)); - } - - @Test - public void handleRequest_TopicArnDoesNotExist() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(NotFoundException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); - verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - - } - - @Test - public void handleRequest_SubscriptionLimitExceededException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(SubscriptionLimitExceededException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_FilterPolicyLimitExceededException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(FilterPolicyLimitExceededException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_InvalidParameterException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(InvalidParameterException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_InternalErrorException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(InternalErrorException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_NotFoundException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(NotFoundException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_AuthorizationErrorException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(AuthorizationErrorException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_InvalidSecurityException() { - - setupGetTopicAttributeMock(); - when(proxyClient.client().listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class))).thenThrow(InvalidSecurityException.class); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); - assertThrows(CfnInvalidCredentialsException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - private void setupGetTopicAttributeMock() { - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); + verify(proxyClient.client(), times(2)).listSubscriptions(any(ListSubscriptionsRequest.class)); } } diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ReadHandlerTest.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ReadHandlerTest.java index c38a834..3aadfff 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ReadHandlerTest.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/ReadHandlerTest.java @@ -8,19 +8,32 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.CfnNotFoundException; -import software.amazon.cloudformation.proxy.*; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesResponse; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesRequest; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.OperationStatus; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.INVALID_REQUEST; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.NOT_FOUND; import java.time.Duration; import java.util.HashMap; import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.never; @ExtendWith(MockitoExtension.class) @@ -50,7 +63,7 @@ public void setup() { @AfterEach public void tear_down() { - verify(snsClient, atLeastOnce()).serviceName(); + verify(snsClient, atLeast(0)).serviceName(); verifyNoMoreInteractions(snsClient); } @@ -69,13 +82,6 @@ private void buildObjects() { @Test public void handleRequest_SimpleSuccess_SimpleAttributes() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - final Map attributes = new HashMap<>(); attributes.put("SubscriptionArn", model.getSubscriptionArn()); @@ -108,24 +114,14 @@ public void handleRequest_SimpleSuccess_SimpleAttributes() { assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), times(2)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test public void handleRequest_SimpleSuccess() throws JsonProcessingException { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - final ObjectMapper objectMapper = new ObjectMapper(); - final Map filterPolicy = new HashMap<>(); filterPolicy.put("store", "[\"example_corp\"]"); filterPolicy.put("event", "[\"order_placed\"]"); @@ -183,27 +179,38 @@ public void handleRequest_SimpleSuccess() throws JsonProcessingException { assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), times(2)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } - @Test - public void handleRequest_TopicArnDoesNotExist() { + public void handleRequest_SubscriptionArnDoesNotExist() { + model.setSubscriptionArn(null); + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(model) + .build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(NotFoundException.class); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(INVALID_REQUEST.toString()); + verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + } - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(model) - .build(); + @Test + public void handleRequest_SubscriptionDoesNotExist() { + AwsServiceException exception = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.NOT_FOUND).build()) + .build(); + when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(exception); - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); + final ResourceHandlerRequest request = ResourceHandlerRequest.builder() + .desiredResourceState(model) + .build(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); - verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(NOT_FOUND.toString()); + verify(proxyClient.client()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } } diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/SnsSubscriptionUtilsTest.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/SnsSubscriptionUtilsTest.java index 49e9f61..9da085e 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/SnsSubscriptionUtilsTest.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/SnsSubscriptionUtilsTest.java @@ -1,11 +1,9 @@ package software.amazon.sns.subscription; - -import org.junit.jupiter.api.Test; import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; import java.util.Map; - +import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/UpdateHandlerTest.java b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/UpdateHandlerTest.java index 56db4b7..51816f7 100644 --- a/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/UpdateHandlerTest.java +++ b/aws-sns-subscription/src/test/java/software/amazon/sns/subscription/UpdateHandlerTest.java @@ -6,19 +6,34 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.services.sns.SnsClient; -import software.amazon.awssdk.services.sns.model.*; -import software.amazon.cloudformation.exceptions.*; -import software.amazon.cloudformation.proxy.*; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesResponse; +import software.amazon.awssdk.services.sns.model.GetSubscriptionAttributesRequest; +import software.amazon.awssdk.services.sns.model.SetSubscriptionAttributesRequest; +import software.amazon.awssdk.services.sns.model.SetSubscriptionAttributesResponse; +import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; +import software.amazon.cloudformation.proxy.ProgressEvent; +import software.amazon.cloudformation.proxy.ProxyClient; +import software.amazon.cloudformation.proxy.ResourceHandlerRequest; +import software.amazon.cloudformation.proxy.OperationStatus; import java.time.Duration; import java.util.HashMap; import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.INVALID_REQUEST; +import static software.amazon.awssdk.services.cloudformation.model.HandlerErrorCode.NOT_FOUND; @ExtendWith(MockitoExtension.class) @@ -49,7 +64,7 @@ public void setup() { @AfterEach public void tear_down() { - verify(snsClient, atLeastOnce()).serviceName(); + verify(snsClient, atLeast(0)).serviceName(); verifyNoMoreInteractions(snsClient); } @@ -109,9 +124,6 @@ private ResourceModel buildDesiredObjects() { public void handleRequest_UpdateBooleandAttributes() { final UpdateHandler handler = new UpdateHandler(); - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - Map subscriptionAttributes = new HashMap<>(); subscriptionAttributes.put("SubscriptionArn", "arn"); subscriptionAttributes.put("TopicArn", "topicArn"); @@ -120,14 +132,10 @@ public void handleRequest_UpdateBooleandAttributes() { subscriptionAttributes.put("RawMessageDelivery", "true"); subscriptionAttributes.put("PendingConfirmation", "false"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - // only raw message deivery should be different + // only raw message delivery should be different ResourceModel currentModel = buildCurrentObjects(); ResourceModel desiredModel = buildCurrentObjects(); desiredModel.setRawMessageDelivery(false); @@ -146,23 +154,19 @@ public void handleRequest_UpdateBooleandAttributes() { assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModel()).isNotNull(); - assertThat(response.getResourceModel().getRawMessageDelivery()).isEqualTo(true); + assertThat(response.getResourceModel().getRawMessageDelivery()).isEqualTo(false); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); verify(proxyClient.client()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); - verify(proxyClient.client(), times(4)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test public void handleRequest_UpdateBooleandAttributes_RawMessageNotChanged() { final UpdateHandler handler = new UpdateHandler(); - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - Map subscriptionAttributes = new HashMap<>(); subscriptionAttributes.put("SubscriptionArn", "arn"); subscriptionAttributes.put("TopicArn", "topicArn"); @@ -171,14 +175,10 @@ public void handleRequest_UpdateBooleandAttributes_RawMessageNotChanged() { subscriptionAttributes.put("RawMessageDelivery", "true"); subscriptionAttributes.put("PendingConfirmation", "false"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse);//.thenReturn(getSubscriptionResponse); + when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - // only raw message deivery should be different + // only raw message delivery should be different ResourceModel currentModel = buildCurrentObjects(); ResourceModel desiredModel = buildCurrentObjects(); desiredModel.setRawMessageDelivery(currentModel.getRawMessageDelivery()); @@ -199,18 +199,14 @@ public void handleRequest_UpdateBooleandAttributes_RawMessageNotChanged() { assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); verify(proxyClient.client(), never()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); - verify(proxyClient.client(), times(3)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test public void handleRequest_UpdateBooleandAttributes_RawMessageIsNullForLambda() { final UpdateHandler handler = new UpdateHandler(); - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - Map subscriptionAttributes = new HashMap<>(); subscriptionAttributes.put("SubscriptionArn", "arn"); subscriptionAttributes.put("TopicArn", "topicArn"); @@ -218,17 +214,13 @@ public void handleRequest_UpdateBooleandAttributes_RawMessageIsNullForLambda() { subscriptionAttributes.put("Endpoint", "lambdaArn"); subscriptionAttributes.put("PendingConfirmation", "false"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse);//.thenReturn(getSubscriptionResponse); + when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); - // only raw message deivery should be different + // only raw message delivery should be different ResourceModel currentModel = buildCurrentObjects(); ResourceModel desiredModel = buildCurrentObjects(); - desiredModel.setRawMessageDelivery(currentModel.getRawMessageDelivery()); + desiredModel.setRawMessageDelivery(false); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(desiredModel) @@ -241,14 +233,13 @@ public void handleRequest_UpdateBooleandAttributes_RawMessageIsNullForLambda() { assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModel()).isNotNull(); - assertThat(response.getResourceModel().getRawMessageDelivery()).isNull(); + assertThat(response.getResourceModel().getRawMessageDelivery()).isEqualTo(false); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), never()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); - verify(proxyClient.client(), times(3)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + verify(proxyClient.client()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @@ -256,9 +247,6 @@ public void handleRequest_UpdateBooleandAttributes_RawMessageIsNullForLambda() { public void handleRequest_UpdateMapBasedAttributes() { final UpdateHandler handler = new UpdateHandler(); - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn","topicarn"); - Map subscriptionAttributes = new HashMap<>(); subscriptionAttributes.put("SubscriptionArn", "arn1"); subscriptionAttributes.put("TopicArn", "topicArn1"); @@ -267,10 +255,12 @@ public void handleRequest_UpdateMapBasedAttributes() { subscriptionAttributes.put("RawMessageDelivery", "false"); subscriptionAttributes.put("PendingConfirmation", "false"); - desiredModel.setRawMessageDelivery(currentModel.getRawMessageDelivery()); + final Map DesiredRedrivePolicy = new HashMap<>(); + DesiredRedrivePolicy.put("deadLetterTargetArn", "arn2"); + DesiredRedrivePolicy.put("maxReceiveCount", "2"); - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); + desiredModel.setRawMessageDelivery(currentModel.getRawMessageDelivery()); + desiredModel.setRedrivePolicy(DesiredRedrivePolicy); final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); @@ -290,435 +280,16 @@ public void handleRequest_UpdateMapBasedAttributes() { assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModel()).isNotNull(); assertThat(response.getResourceModels()).isNull(); + assertThat(response.getResourceModel().getRedrivePolicy()).isEqualTo(DesiredRedrivePolicy); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); verify(proxyClient.client(), times(3)).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); - verify(proxyClient.client(), times(6)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - } - - @Test - public void handleRequest_TopicArnDoesNotExist() { - - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenThrow(NotFoundException.class); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); - verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); - - } - - @Test - public void handleRequest_SubscriptionDoesNotExist() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - // GetSubscriptionAttributesResponse getSubscriptionAttributesResponse = GetSubscriptionAttributesResponse.builder().build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(NotFoundException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); - verify(proxyClient.client(), never()).unsubscribe(any(UnsubscribeRequest.class)); - } - - @Test - public void handleRequest_InvalidParameterExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(InvalidParameterException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_SubscriptionLimitExceededExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(SubscriptionLimitExceededException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_FilterPolicyLimitExceededExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(FilterPolicyLimitExceededException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_AuthorizationErrorExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(AuthorizationErrorException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test - public void handleRequest_InternalErrorExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(InternalErrorException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_NotFoundExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(NotFoundException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InvalidSecurityExceptionSetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - Map subscriptionAttributes = new HashMap<>(); - subscriptionAttributes.put("SubscriptionArn", "arn1"); - subscriptionAttributes.put("TopicArn", "topicArn1"); - subscriptionAttributes.put("Protocol", "email1"); - subscriptionAttributes.put("Endpoint", "end1"); - subscriptionAttributes.put("RawMessageDelivery", "false"); - subscriptionAttributes.put("PendingConfirmation", "false"); - - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(InvalidSecurityException.class); - - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse).thenReturn(getSubscriptionResponse); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnInvalidCredentialsException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InvalidParameterExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(InvalidParameterException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnInvalidRequestException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_SubscriptionLimitExceededExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(SubscriptionLimitExceededException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_FilterPolicyLimitExceededExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(FilterPolicyLimitExceededException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnServiceLimitExceededException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_AuthorizationErrorExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(AuthorizationErrorException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnAccessDeniedException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InternalErrorExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(InternalErrorException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_NotFoundExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(NotFoundException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnNotFoundException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_InvalidSecurityExceptionGetSubscription() { - - final Map topicAttributes = new HashMap<>(); - topicAttributes.put("TopicArn", "topicArn"); - when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(InvalidSecurityException.class); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - - assertThrows(CfnInvalidCredentialsException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - - } - - @Test - public void handleRequest_UpdateSubscriptionRoleArnAttribute() { - + public void handleRequest_UpdateStringAttributes() { setupUpdateMocks(); // only subscription role arn should be different @@ -746,62 +317,42 @@ public void handleRequest_UpdateSubscriptionRoleArnAttribute() { assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); - verify(proxyClient.client()).getTopicAttributes(any(GetTopicAttributesRequest.class)); verify(proxyClient.client()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); - verify(proxyClient.client(), times(4)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), times(1)).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test - public void handleRequest_invalidUpdateEndpoint() { - - setupUpdateMocks(); - desiredModel.setEndpoint("updated"); - + public void handleRequest_SubscriptionArnDoesNotExist() { + desiredModel.setSubscriptionArn(null); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(desiredModel) - .previousResourceState(currentModel) .build(); - assertThrows(CfnNotUpdatableException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - @Test - public void handleRequest_invalidUpdateProtocol() { - - setupUpdateMocks(); - desiredModel.setProtocol("sqs"); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - assertThrows(CfnNotUpdatableException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(INVALID_REQUEST.toString()); + verify(proxyClient.client(), never()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); + verify(proxyClient.client(), never()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } @Test - public void handleRequest_invalidUpdateTopicArn() { - - setupUpdateMocks(); - desiredModel.setTopicArn("newTopicArn"); + public void handleRequest_SubscriptionDoesNotExist() { + AwsServiceException exception = AwsServiceException.builder() + .awsErrorDetails(AwsErrorDetails.builder().errorCode(BaseHandlerStd.NOT_FOUND).build()) + .build(); + when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenThrow(exception); final ResourceHandlerRequest request = ResourceHandlerRequest.builder() .desiredResourceState(desiredModel) - .previousResourceState(currentModel) .build(); - assertThrows(CfnNotUpdatableException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); - } - - @Test - public void handleRequest_generalRuntimeException() { - setupUpdateMocks(); - - when(proxyClient.client().setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class))).thenThrow(RuntimeException.class); + final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger); - final ResourceHandlerRequest request = ResourceHandlerRequest.builder() - .desiredResourceState(desiredModel) - .previousResourceState(currentModel) - .build(); - assertThrows(CfnInternalFailureException.class, () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); + assertThat(response.getErrorCode().toString()).isEqualTo(NOT_FOUND.toString()); + verify(proxyClient.client(), never()).setSubscriptionAttributes(any(SetSubscriptionAttributesRequest.class)); + verify(proxyClient.client()).getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class)); } private void setupUpdateMocks() { @@ -815,10 +366,6 @@ private void setupUpdateMocks() { subscriptionAttributes.put("Endpoint", "end"); subscriptionAttributes.put("SubscriptionRoleArn", "New-Subscription-Role-Arn"); - - final GetTopicAttributesResponse getTopicAttributesResponse = GetTopicAttributesResponse.builder().attributes(topicAttributes).build(); - when(proxyClient.client().getTopicAttributes(any(GetTopicAttributesRequest.class))).thenReturn(getTopicAttributesResponse); - final GetSubscriptionAttributesResponse getSubscriptionResponse = GetSubscriptionAttributesResponse.builder().attributes(subscriptionAttributes).build(); when(proxyClient.client().getSubscriptionAttributes(any(GetSubscriptionAttributesRequest.class))).thenReturn(getSubscriptionResponse); }