package com.amazonaws.iot.dimension; import static com.amazonaws.iot.dimension.TestConstants.DIMENSION_ARN; import static com.amazonaws.iot.dimension.TestConstants.DIMENSION_NAME; import static com.amazonaws.iot.dimension.TestConstants.DIMENSION_TYPE; import static com.amazonaws.iot.dimension.TestConstants.DIMENSION_VALUE_CFN; import static com.amazonaws.iot.dimension.TestConstants.DIMENSION_VALUE_IOT; import static com.amazonaws.iot.dimension.TestConstants.SDK_SYSTEM_TAG; import static com.amazonaws.iot.dimension.TestConstants.SYSTEM_TAG_MAP; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.services.iot.model.InternalFailureException; import software.amazon.awssdk.services.iot.model.InvalidRequestException; import software.amazon.awssdk.services.iot.model.IotRequest; import software.amazon.awssdk.services.iot.model.ResourceNotFoundException; import software.amazon.awssdk.services.iot.model.TagResourceRequest; import software.amazon.awssdk.services.iot.model.UntagResourceRequest; import software.amazon.awssdk.services.iot.model.UpdateDimensionRequest; import software.amazon.awssdk.services.iot.model.UpdateDimensionResponse; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.Logger; import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; @ExtendWith(MockitoExtension.class) public class UpdateHandlerTest { protected static final software.amazon.awssdk.services.iot.model.Tag PREVIOUS_SDK_RESOURCE_TAG = software.amazon.awssdk.services.iot.model.Tag.builder() .key("PreviousTagKey") .value("PreviousTagValue") .build(); protected static final software.amazon.awssdk.services.iot.model.Tag DESIRED_SDK_RESOURCE_TAG = software.amazon.awssdk.services.iot.model.Tag.builder() .key("DesiredTagKey") .value("DesiredTagValue") .build(); protected static final Set PREVIOUS_DIMENSION_VALUE = ImmutableSet.of("previousValue"); @Mock private AmazonWebServicesClientProxy proxy; @Mock private Logger logger; @Spy private UpdateHandler handler; @Test public void handleRequest_BothValueAndTagsAreUpdated_VerifyRequests() { ResourceModel previousModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(PREVIOUS_DIMENSION_VALUE) .arn(DIMENSION_ARN) .build(); ResourceModel desiredModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(DIMENSION_VALUE_CFN) .build(); Map desiredTags = ImmutableMap.of("DesiredTagKey", "DesiredTagValue"); ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceState(previousModel) .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .desiredResourceState(desiredModel) .desiredResourceTags(desiredTags) .systemTags(SYSTEM_TAG_MAP) .build(); doReturn(ImmutableSet.of(PREVIOUS_SDK_RESOURCE_TAG, SDK_SYSTEM_TAG)) .when(handler) .listTags(proxy, DIMENSION_ARN, logger); when(proxy.injectCredentialsAndInvokeV2(any(), any())) .thenReturn(UpdateDimensionResponse.builder().arn(DIMENSION_ARN).build()); ProgressEvent response = handler.handleRequest(proxy, request, null, logger); assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); assertThat(response.getCallbackContext()).isNull(); assertThat(response.getCallbackDelaySeconds()).isEqualTo(0); assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState()); assertThat(response.getResourceModels()).isNull(); assertThat(response.getMessage()).isNull(); assertThat(response.getErrorCode()).isNull(); desiredModel.setArn(DIMENSION_ARN); assertThat(response.getResourceModel()).isEqualTo(desiredModel); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(IotRequest.class); verify(proxy, times(3)).injectCredentialsAndInvokeV2(requestCaptor.capture(), any()); List submittedIotRequests = requestCaptor.getAllValues(); UpdateDimensionRequest submittedUpdateRequest = (UpdateDimensionRequest) submittedIotRequests.get(0); assertThat(submittedUpdateRequest.name()).isEqualTo(DIMENSION_NAME); assertThat(submittedUpdateRequest.stringValues()).isEqualTo(DIMENSION_VALUE_IOT); TagResourceRequest submittedTagRequest = (TagResourceRequest) submittedIotRequests.get(1); assertThat(submittedTagRequest.tags()).isEqualTo(Collections.singletonList(DESIRED_SDK_RESOURCE_TAG)); assertThat(submittedTagRequest.resourceArn()).isEqualTo(DIMENSION_ARN); UntagResourceRequest submittedUntagRequest = (UntagResourceRequest) submittedIotRequests.get(2); assertThat(submittedUntagRequest.tagKeys()).isEqualTo(Collections.singletonList("PreviousTagKey")); assertThat(submittedUntagRequest.resourceArn()).isEqualTo(DIMENSION_ARN); } @Test public void updateTags_SameKeyDifferentValue_OnlyTagCall() { software.amazon.awssdk.services.iot.model.Tag previousTag = software.amazon.awssdk.services.iot.model.Tag.builder() .key("DesiredTagKey") .value("PreviousTagValue") .build(); Map desiredTags = ImmutableMap.of("DesiredTagKey", "DesiredTagValue"); ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceState(ResourceModel.builder().build()) .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .desiredResourceTags(desiredTags) .systemTags(SYSTEM_TAG_MAP) .build(); doReturn(ImmutableSet.of(previousTag, SDK_SYSTEM_TAG)) .when(handler) .listTags(proxy, DIMENSION_ARN, logger); handler.updateTags(proxy, request, DIMENSION_ARN, logger); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(TagResourceRequest.class); verify(proxy).injectCredentialsAndInvokeV2(requestCaptor.capture(), any()); TagResourceRequest submittedTagRequest = requestCaptor.getValue(); assertThat(submittedTagRequest.tags()).isEqualTo(Collections.singletonList(DESIRED_SDK_RESOURCE_TAG)); } @Test public void updateTags_NoDesiredTags_OnlyUntagCall() { Map desiredTags = Collections.emptyMap(); ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceState(ResourceModel.builder().build()) .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .desiredResourceTags(desiredTags) .systemTags(SYSTEM_TAG_MAP) .build(); doReturn(ImmutableSet.of(PREVIOUS_SDK_RESOURCE_TAG, SDK_SYSTEM_TAG)) .when(handler) .listTags(proxy, DIMENSION_ARN, logger); handler.updateTags(proxy, request, DIMENSION_ARN, logger); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UntagResourceRequest.class); verify(proxy).injectCredentialsAndInvokeV2(requestCaptor.capture(), any()); UntagResourceRequest submittedUntagRequest = requestCaptor.getValue(); assertThat(submittedUntagRequest.tagKeys()).isEqualTo(Collections.singletonList("PreviousTagKey")); } @Test public void handleRequest_UpdateThrowsIRE_VerifyTranslation() { ResourceModel desiredModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(DIMENSION_VALUE_CFN) .build(); ResourceModel previousModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(PREVIOUS_DIMENSION_VALUE) .arn(DIMENSION_ARN) .build(); ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .previousResourceState(previousModel) .desiredResourceState(desiredModel) .systemTags(SYSTEM_TAG_MAP) .build(); when(proxy.injectCredentialsAndInvokeV2(any(), any())) .thenThrow(InvalidRequestException.builder().build()); ProgressEvent progressEvent = handler.handleRequest(proxy, request, null, logger); assertThat(progressEvent.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest); } @Test public void updateTags_ApiThrowsIFE_BubbleUp() { ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceState(ResourceModel.builder().build()) .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .desiredResourceTags(ImmutableMap.of("DesiredTagKey", "DesiredTagValue")) .systemTags(SYSTEM_TAG_MAP) .build(); when(proxy.injectCredentialsAndInvokeV2(any(), any())) .thenThrow(InternalFailureException.builder().build()); assertThatThrownBy(() -> handler.updateTags(proxy, request, DIMENSION_ARN, logger)) .isInstanceOf(InternalFailureException.class); } @Test void handleRequest_ResourceAlreadyDeleted_VerifyException() { ResourceModel desiredModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(DIMENSION_VALUE_CFN) .build(); ResourceModel previousModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(PREVIOUS_DIMENSION_VALUE) .arn(DIMENSION_ARN) .build(); ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .previousResourceState(previousModel) .desiredResourceState(desiredModel) .systemTags(SYSTEM_TAG_MAP) .build(); // If the resource is already deleted, the update API throws ResourceNotFoundException. Mocking that here. when(proxy.injectCredentialsAndInvokeV2(any(), any())) .thenThrow(ResourceNotFoundException.builder().build()); ProgressEvent progressEvent = handler.handleRequest(proxy, request, null, logger); assertThat(progressEvent.getErrorCode()).isEqualTo(HandlerErrorCode.NotFound); } @Test void handleRequest_DesiredArnIsPopulatedAndSame_ReturnFailed() { ResourceModel desiredModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(DIMENSION_VALUE_CFN) .arn(DIMENSION_ARN) .build(); ResourceModel previousModel = ResourceModel.builder() .name(DIMENSION_NAME) .type(DIMENSION_TYPE) .stringValues(PREVIOUS_DIMENSION_VALUE) .build(); ResourceHandlerRequest request = ResourceHandlerRequest.builder() .previousResourceTags(ImmutableMap.of("doesn't", "matter")) .previousResourceState(previousModel) .desiredResourceState(desiredModel) .systemTags(SYSTEM_TAG_MAP) .build(); ProgressEvent result = handler.handleRequest(proxy, request, null, logger); assertThat(result).isEqualTo(ProgressEvent.failed( desiredModel, null, HandlerErrorCode.InvalidRequest, "Arn cannot be updated.")); } }