Пример #1
0
        private async Task TestCURDelete(string reportName)
        {
            // ARRANGE
            string Json = GenerateDeleteJson(reportName);

            CustomResourceRequest Request = JsonConvert.DeserializeObject <CustomResourceRequest>(Json);

            TestLambdaLogger  TestLogger    = new TestLambdaLogger();
            TestClientContext ClientContext = new TestClientContext();

            SharedCredentialsFile Creds = new SharedCredentialsFile();

            Creds.TryGetProfile($"{Environment.UserName}-dev", out CredentialProfile Profile);

            ImmutableCredentials Cr = Profile.GetAWSCredentials(Creds).GetCredentials();

            TestLambdaContext Context = new TestLambdaContext()
            {
                FunctionName       = "CostAndUsageReportResource",
                FunctionVersion    = "1",
                Logger             = TestLogger,
                ClientContext      = ClientContext,
                InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:FunctionName"
            };

            // ACT
            Entrypoint Ep = new Entrypoint();
            await Ep.Execute(Request, Context);

            // ASSERT
        }
Пример #2
0
 private void FailIfRequested(CustomResourceRequest <ResourceProperties> request)
 {
     if (request.ResourceProperties.ShouldFail)
     {
         throw new Exception(request.ResourceProperties.ErrorMessage);
     }
 }
Пример #3
0
        public void DeleteCustomResourceRequestTest()
        {
            // ARRANGE
            string Json = @"
{
""requestType"":""delete"",
""responseUrl"":""https://s3.us-east-1.amazonaws.com/presigned-url/response.txt?X-Amz-Date=20180531T182534Z&X-Amz-SignedHeaders=host&X-Amz-Credential=AKIAIYLQNVRRFNZOCFFR%2F20170720%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=604800&X-Amz-Security-Token=FQoDYXdzEJP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDOLWx95j90zPxGh7WSLdAVnoYoKC4gjrrR1xbokFWRRwutmuAmOxaIVcQqOy%2Fqxy%2FXQt3Iz%2FohuEEmI7%2FHPzShy%2BfgQtvfUeDaojrAx5q8fG9P1KuIfcedfkiU%2BCxpM2foyCGlXzoZuNlcF8ohm%2BaM3wh4%2BxQ%2FpShLl18cKiKEiw0QF1UQGj%2FsiEqzoM81vOSUVWL9SpTTkVq8EQHY1chYKBkBWt7eIQcxjTI2dQeYOohlrbnZ5Y1%2F1cxPgrbk6PkNFO3whAoliSjyRC8e4TSjIY2j3V6d9fUy4%2Fp6nLZIf9wuERL7xW9PjE6eZbKOHnw8sF&X-Amz-Signature=a14b3065ab822105e8d7892eb5dcc455ddd603c61e47520774a7289178af9ecc"",
""stackId"":""arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/f449b250-b969-11e0-a185-5081d0136786"",
""requestId"":""12345678"",
""resourceType"":""Custom::TestResource"",
""logicalResourceId"":""MyTestResource"",
""physicalResourceId"":""TestResource1"",
""resourceProperties"":{
""name"":""Value"",
""digits"":[1,2,3],
""address"":{
""street"":""MyStreetName"",
""number"":1234,
""city"":""Washington D.C.""
}
}
}";

            Json = Json.Trim().Replace("\r", "").Replace("\n", "").Replace("\t", "");

            // ACT
            CustomResourceRequest Request = JsonConvert.DeserializeObject <CustomResourceRequest>(Json);
            string Content = JsonConvert.SerializeObject(Request, Formatting.None);

            // ASSERT
            Assert.Equal(Json, Content, true, true, true);
        }
Пример #4
0
            public async Task ShouldCallCreate_IfRequestTypeIsCreate(
                ServiceCollection serviceCollection,
                CustomResourceRequest <object> request,
                JsonSerializer serializer,
                ILogger logger,
                [Substitute] TestCustomResourceLambda lambda,
                [Substitute] IHttpClient httpClient
                )
            {
                serviceCollection.AddSingleton <ISerializer>(serializer);
                serviceCollection.AddSingleton(httpClient);

                var serviceProvider   = serviceCollection.BuildServiceProvider();
                var cancellationToken = new CancellationToken(false);
                var host = new TestCustomResourceLambdaHost(lambdaHost =>
                {
                    lambdaHost.Lambda     = lambda;
                    lambdaHost.Scope      = serviceProvider.CreateScope();
                    lambdaHost.Serializer = serializer;
                    lambdaHost.Logger     = logger;
                });

                request.RequestType = CustomResourceRequestType.Create;

                using var inputStream = await StreamUtils.CreateJsonStream(request);

                await host.InvokeLambda(inputStream, cancellationToken);

                await lambda.DidNotReceiveWithAnyArgs().Update(default !, default);
        private async Task RespondToCloudFormation <T>(CustomResourceRequest request, string physicalResourceId, T data = null, Exception exception = null) where T : class
        {
            var response = new CustomResourceResponse <T>
            {
                Status             = exception != null ? "FAILED" : "SUCCESS",
                Reason             = exception?.Message ?? string.Empty,
                PhysicalResourceId = physicalResourceId,
                StackId            = request.StackId,
                RequestId          = request.RequestId,
                LogicalResourceId  = request.LogicalResourceId,
                Data = data
            };

            try
            {
                var client = new HttpClient();

                var body        = JsonSerializer.Serialize(response);
                var jsonContent = new StringContent(body);
                jsonContent.Headers.Remove("Content-Type");

                LambdaLogger.Log(body);

                var postResponse = await client.PutAsync(request.ResponseURL, jsonContent);

                postResponse.EnsureSuccessStatusCode();
            }
            catch (Exception ex)
            {
                LambdaLogger.Log("Exception: " + ex);
            }
        }
Пример #6
0
        public Task <Response> Update(CustomResourceRequest <Request> request, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var password = GeneratePassword(request.ResourceProperties?.Length ?? options.DefaultLength);
            var response = new Response(password);

            return(Task.FromResult(response));
        }
Пример #7
0
        public override async Task <CustomResourceResponse> CreateAsync(CustomResourceRequest request, ILambdaContext context)
        {
            try
            {
                context.LogInfo("Attempting to create a pipeline.");
                CreatePipelineRequest  PipelineRequest = JsonConvert.DeserializeObject <CreatePipelineRequest>(JsonConvert.SerializeObject(request.ResourceProperties));
                CreatePipelineResponse CreateResponse  = await this._ETClient.CreatePipelineAsync(PipelineRequest);

                if ((int)CreateResponse.HttpStatusCode < 200 || (int)CreateResponse.HttpStatusCode > 299)
                {
                    return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, $"Received HTTP status code {(int)CreateResponse.HttpStatusCode}.", request));
                }
                else
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.SUCCESS,
                               $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                               CreateResponse.Pipeline.Id,
                               request.StackId,
                               request.RequestId,
                               request.LogicalResourceId,
                               false,
                               new Dictionary <string, object>()
                    {
                        { "Name", CreateResponse.Pipeline.Name },
                        { "Arn", CreateResponse.Pipeline.Arn },
                        { "Id", CreateResponse.Pipeline.Id }
                    }
                               ));
                }
            }
            catch (AmazonElasticTranscoderException e)
            {
                context.LogError(e);

                return(new CustomResourceResponse(
                           CustomResourceResponse.RequestStatus.FAILED,
                           e.Message,
                           Guid.NewGuid().ToString(),
                           request.StackId,
                           request.RequestId,
                           request.LogicalResourceId
                           ));
            }
            catch (Exception e)
            {
                context.LogError(e);

                return(new CustomResourceResponse(
                           CustomResourceResponse.RequestStatus.FAILED,
                           e.Message,
                           Guid.NewGuid().ToString(),
                           request.StackId,
                           request.RequestId,
                           request.LogicalResourceId
                           ));
            }
        }
Пример #8
0
 public Task <ResponseData> Delete(CustomResourceRequest <ResourceProperties> request, CancellationToken cancellationToken = default)
 {
     FailIfRequested(request);
     return(Task.FromResult(new ResponseData
     {
         Id = request.ResourceProperties.Name,
         MethodCalled = "Delete",
     }));
 }
Пример #9
0
        /// <summary>
        /// Entrypoint for the Lambda function, calls the correct create, update, or delete function
        /// </summary>
        /// <param name="request">The custom resource request</param>
        /// <param name="context">The ILambdaContext object</param>
        /// <returns></returns>
        public async Task Execute(CustomResourceRequest request, ILambdaContext context)
        {
            context.LogInfo($"Received request:\n{JsonConvert.SerializeObject(request)}");

            CustomResourceResult Result = await this._Handler.ExecuteAsync(request, context);

            if (Result.IsSuccess)
            {
                context.LogInfo("Successfully ran custom resource handler.");
            }
            else
            {
                context.LogError("Custom resource handler failed to run successfully.");
            }
        }
        public async Task DatabaseTableCustomResource(CustomResourceRequest <DatabaseTableRequestData> request, ILambdaContext context)
        {
            var physicalResourceId = "unassigned-physical-resource-id";

            try
            {
                context.Logger.LogLine(JsonSerializer.Serialize(request));

                DatabaseTableResponseData response = new DatabaseTableResponseData {
                    Name = request.ResourceProperties.Name
                };

                switch (request.RequestType)
                {
                case "Create":
                    physicalResourceId = $"{request.StackId}/{request.ResourceType}/{request.LogicalResourceId}";
                    await CreateTable(request.ResourceProperties);

                    break;

                case "Update":
                    physicalResourceId = request.PhysicalResourceId;
                    await UpdateTable(request.OldResourceProperties, request.ResourceProperties);

                    break;

                case "Delete":
                    physicalResourceId = request.PhysicalResourceId;
                    await DropTable(request.ResourceProperties);

                    break;

                default:
                    throw new NotSupportedException($"Cannot handle RequestType {request.RequestType}");
                }

                await RespondToCloudFormation(request, physicalResourceId, response);
            }
            catch (Exception ex)
            {
                await RespondToCloudFormation(request, physicalResourceId, ex);

                LambdaLogger.Log("Exception: " + ex);
            }
        }
Пример #11
0
        /// <summary>
        /// Called when the Kinesis Stream Awaiter is created in the CF script, it will wait on the specified stream to enter
        /// ACTIVE status
        /// </summary>
        /// <param name="request"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task <CustomResourceResponse> CreateAsync(CustomResourceRequest request, ILambdaContext context)
        {
            if (request.ResourceProperties.ContainsKey("StreamName"))
            {
                context.LogInfo($"Beginning await for Kinesis stream {request.ResourceProperties["StreamName"]}.");

                DescribeStreamRequest Request = new DescribeStreamRequest()
                {
                    StreamName = request.ResourceProperties["StreamName"].ToString()
                };

                while (true)
                {
                    if (context.RemainingTime.TotalMilliseconds < 1500)
                    {
                        return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, "Timeout waiting for stream to become active.", request));
                    }

                    DescribeStreamResponse Response = await this._KinesisClient.DescribeStreamAsync(Request);

                    if ((int)Response.HttpStatusCode < 300)
                    {
                        if (Response.StreamDescription.StreamStatus == StreamStatus.ACTIVE)
                        {
                            break;
                        }
                    }
                    else
                    {
                        context.LogWarning($"Received an unsuccessful response to the describe stream request: {(int)Response.HttpStatusCode}.");
                    }

                    Thread.Sleep(_WaitTimeInMillis);
                }

                context.LogInfo($"Successfully created Kinesis stream {Request.StreamName}.");

                return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.SUCCESS, "Created", Request.StreamName, request.StackId, request.RequestId, request.LogicalResourceId));
            }
            else
            {
                return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, "The StreamName property was not provided.", "stream", request.StackId, request.RequestId, request.LogicalResourceId));
            }
        }
Пример #12
0
        private async Task TestCURCreateParquet(string reportName)
        {
            string Json = GenerateCreateJsonParquet(reportName);

            CustomResourceRequest Request = JsonConvert.DeserializeObject <CustomResourceRequest>(Json);

            TestLambdaLogger  TestLogger    = new TestLambdaLogger();
            TestClientContext ClientContext = new TestClientContext();

            TestLambdaContext Context = new TestLambdaContext()
            {
                FunctionName       = "CostAndUsageReportResource",
                FunctionVersion    = "1",
                Logger             = TestLogger,
                ClientContext      = ClientContext,
                InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:FunctionName"
            };

            // ACT
            Entrypoint Ep = new Entrypoint();
            await Ep.Execute(Request, Context);
        }
Пример #13
0
        public Task <Response> Delete(CustomResourceRequest <Request> request, CancellationToken cancellationToken = default)
        {
            var response = new Response(string.Empty);

            return(Task.FromResult(response));
        }
Пример #14
0
 public virtual bool RequiresReplacement(CustomResourceRequest <TestLambdaMessage> request)
 {
     return(false);
 }
 private async Task RespondToCloudFormation(CustomResourceRequest request, string physicalResourceId, Exception exception = null)
 {
     await RespondToCloudFormation <object>(request, physicalResourceId, exception : exception);
 }
Пример #16
0
 public virtual Task <TestCustomResourceOutputData> Create(CustomResourceRequest <TestLambdaMessage> request, CancellationToken cancellationToken)
 {
     return(Task.FromResult((TestCustomResourceOutputData)null !));
 }
Пример #17
0
 public virtual void Validate(CustomResourceRequest <TestLambdaMessage> request)
 {
 }
Пример #18
0
        public override async Task <CustomResourceResponse> DeleteAsync(CustomResourceRequest request, ILambdaContext context)
        {
            try
            {
                context.LogInfo("Attempting to delete a pipeline.");

                ListPipelinesRequest Listing = new ListPipelinesRequest();

                List <Pipeline>       Pipelines = new List <Pipeline>();
                ListPipelinesResponse Pipes;

                do
                {
                    Pipes = await this._ETClient.ListPipelinesAsync(Listing);

                    Pipelines.AddRange(Pipes.Pipelines.Where(x => x.Name.Equals(request.ResourceProperties["Name"] as string) &&
                                                             x.InputBucket.Equals(request.ResourceProperties["InputBucket"]) &&
                                                             x.Role.Equals(request.ResourceProperties["Role"])
                                                             ));
                } while (Pipes.NextPageToken != null);

                if (Pipelines.Count > 1)
                {
                    context.LogWarning($"{Pipelines.Count} pipelines were found matching the Name, InputBucket, and Role specified.");
                }

                if (Pipelines.Count > 0)
                {
                    DeletePipelineRequest PipelineRequest = new DeletePipelineRequest()
                    {
                        Id = Pipelines.First().Id
                    };

                    DeletePipelineResponse DeleteResponse = await this._ETClient.DeletePipelineAsync(PipelineRequest);

                    if ((int)DeleteResponse.HttpStatusCode < 200 || (int)DeleteResponse.HttpStatusCode > 299)
                    {
                        return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, $"Received HTTP status code {(int)DeleteResponse.HttpStatusCode}.", request));
                    }
                    else
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.SUCCESS,
                                   $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                                   request,
                                   false
                                   ));
                    }
                }
                else
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.SUCCESS,
                               "No pipelines could be found with the matching characteristics.",
                               request
                               ));
                }
            }
            catch (AmazonElasticTranscoderException e)
            {
                // If the pipeline doesn't exist, consider it deleted
                if (e.StatusCode == HttpStatusCode.NotFound)
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.SUCCESS,
                               $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                               request
                               ));
                }
                else
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               e.Message,
                               request
                               ));
                }
            }
            catch (Exception e)
            {
                return(new CustomResourceResponse(
                           CustomResourceResponse.RequestStatus.FAILED,
                           e.Message,
                           request
                           ));
            }
        }
Пример #19
0
 /// <summary>
 /// Called when the Kinesis Stream Awaiter is deleted in the CF script, no action is taken
 /// </summary>
 /// <param name="request"></param>
 /// <param name="context"></param>
 /// <returns></returns>
 public override async Task <CustomResourceResponse> DeleteAsync(CustomResourceRequest request, ILambdaContext context)
 {
     context.LogInfo("Delete called on KinesisStreamAwaiter");
     return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.SUCCESS, "Deleted", request));
 }
Пример #20
0
        public override async Task <CustomResourceResponse> UpdateAsync(CustomResourceRequest request, ILambdaContext context)
        {
            try
            {
                context.LogInfo("Initiating update for pipeline.");

                UpdatePipelineRequest PipelineRequest = JsonConvert.DeserializeObject <UpdatePipelineRequest>(JsonConvert.SerializeObject(request.ResourceProperties));

                ListPipelinesRequest Listing = new ListPipelinesRequest();

                List <Pipeline>       Pipelines = new List <Pipeline>();
                ListPipelinesResponse Pipes;

                do
                {
                    Pipes = await this._ETClient.ListPipelinesAsync(Listing);

                    Pipelines.AddRange(Pipes.Pipelines.Where(x => x.Name.Equals(request.ResourceProperties["Name"] as string) &&
                                                             x.InputBucket.Equals(request.ResourceProperties["InputBucket"]) &&
                                                             x.Role.Equals(request.ResourceProperties["Role"])
                                                             ));
                } while (Pipes.NextPageToken != null);

                if (Pipelines.Count > 1)
                {
                    context.LogWarning($"{Pipelines.Count} pipelines were found matching the Name, InputBucket, and Role specified.");
                }

                if (Pipelines.Count > 0)
                {
                    PipelineRequest.Id = Pipelines.First().Id;

                    UpdatePipelineResponse UpdateResponse = await this._ETClient.UpdatePipelineAsync(PipelineRequest);

                    if ((int)UpdateResponse.HttpStatusCode < 200 || (int)UpdateResponse.HttpStatusCode > 299)
                    {
                        return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, $"Received HTTP status code {(int)UpdateResponse.HttpStatusCode}.", request));
                    }
                    else
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.SUCCESS,
                                   $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                                   request,
                                   false,
                                   new Dictionary <string, object>()
                        {
                            { "Name", UpdateResponse.Pipeline.Name },
                            { "Arn", UpdateResponse.Pipeline.Arn },
                            { "Id", UpdateResponse.Pipeline.Id }
                        }
                                   ));
                    }
                }
                else
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               "No pipelines could be found with the matching characteristics.",
                               request
                               ));
                }
            }
            catch (AmazonElasticTranscoderException e)
            {
                return(new CustomResourceResponse(
                           CustomResourceResponse.RequestStatus.FAILED,
                           e.Message,
                           request
                           ));
            }
            catch (Exception e)
            {
                return(new CustomResourceResponse(
                           CustomResourceResponse.RequestStatus.FAILED,
                           e.Message,
                           request
                           ));
            }
        }
Пример #21
0
        public async Task TestCreate()
        {
            // ARRANGE
            AWSConfigs.AWSProfilesLocation = $"{Environment.GetEnvironmentVariable("UserProfile")}\\.aws\\credentials";

            string StreamName         = "test-stream";
            string PresignedUrlBucket = "pre-sign-url-bucket";
            string AccountNumber      = "123456789012";
            string Region             = "us-east-1";

            IAmazonS3 S3Client = new AmazonS3Client();

            GetPreSignedUrlRequest Req = new GetPreSignedUrlRequest()
            {
                BucketName = PresignedUrlBucket,
                Key        = "result.txt",
                Expires    = DateTime.Now.AddMinutes(2),
                Protocol   = Protocol.HTTPS,
                Verb       = HttpVerb.PUT
            };

            string PreSignedUrl = S3Client.GetPreSignedURL(Req);

            string Json = $@"
{{
""requestType"":""create"",
""responseUrl"":""{PreSignedUrl}"",
""stackId"":""arn:aws:cloudformation:{Region}:{AccountNumber}:stack/stack-name/{Guid.NewGuid().ToString()}"",
""requestId"":""12345678"",
""resourceType"":""Custom::KinesisStreamAwaiter"",
""logicalResourceId"":""KinesisStreamAwaiter"",
""resourceProperties"":{{
""StreamName"":""{StreamName}""
}}
}}";


            CustomResourceRequest Request = JsonConvert.DeserializeObject <CustomResourceRequest>(Json);

            TestLambdaLogger  TestLogger    = new TestLambdaLogger();
            TestClientContext ClientContext = new TestClientContext();

            TestLambdaContext Context = new TestLambdaContext()
            {
                FunctionName    = "KinesisStreamAwaiter",
                FunctionVersion = "1",
                Logger          = TestLogger,
                ClientContext   = ClientContext,
                LogGroupName    = "aws/lambda/KinesisStreamAwaiter",
                LogStreamName   = Guid.NewGuid().ToString(),
                RemainingTime   = TimeSpan.FromSeconds(300)
            };


            Entrypoint Entrypoint = new Entrypoint();

            // ACT
            IAmazonKinesis      KinesisClient = new AmazonKinesisClient();
            CreateStreamRequest CreateReq     = new CreateStreamRequest()
            {
                ShardCount = 1,
                StreamName = StreamName
            };


            CreateStreamResponse CreateResponse = await KinesisClient.CreateStreamAsync(CreateReq);

            try
            {
                CustomResourceResult Response = await Entrypoint.ExecuteAsync(Request, Context);

                // ASSERT

                Assert.True(Response.IsSuccess);
            }
            finally
            {
                DeleteStreamRequest DeleteReq = new DeleteStreamRequest()
                {
                    StreamName = StreamName
                };

                await KinesisClient.DeleteStreamAsync(DeleteReq);
            }
        }
Пример #22
0
        public async Task CreateCustomResourceWithHandlerTest()
        {
            // ARRANGE
            string AccountNumber      = "123456789012";
            string Region             = "us-east-1";
            string InputBucket        = $"{Environment.UserName}-rawvideo";
            string OutputBucket       = $"{Environment.UserName}-video";
            string PresignedUrlBucket = $"{Environment.UserName}-presigned-url-test";
            string ThumbnailBucket    = $"{Environment.UserName}-thumbnails";
            string IAMRole            = $"arn:aws:iam::{AccountNumber}:role/LambdaElasticTranscoderPipeline";
            string NotificationTopic  = $"arn:aws:sns:{Region}:{AccountNumber}:ElasticTranscoderNotifications";
            string Key = "result.txt";

            AWSConfigs.AWSProfilesLocation = $"{Environment.GetEnvironmentVariable("UserProfile")}\\.aws\\credentials";

            Mock <IAmazonS3> s3Client = new Mock <IAmazonS3>();

            s3Client.Setup(x => x.GetPreSignedURL(It.IsAny <GetPreSignedUrlRequest>())).Returns($"https://{PresignedUrlBucket}.s3.amazonaws.com/{Key}?AWSAccessKeyId=AKIA1234567890123456&Expires=1559247929&Signature=OTgL4H7i%2FQOcTFpLM%2AV2LsFjONE%3D");

            GetPreSignedUrlRequest preSignedUrlRequest = new GetPreSignedUrlRequest()
            {
                BucketName = PresignedUrlBucket,
                Key        = Key,
                Expires    = DateTime.Now.AddMinutes(2),
                Protocol   = Protocol.HTTPS,
                Verb       = HttpVerb.PUT
            };

            string PreSignedUrl = s3Client.Object.GetPreSignedURL(preSignedUrlRequest);
            string Json         = $@"
{{
""requestType"":""create"",
""responseUrl"":""{PreSignedUrl}"",
""stackId"":""arn:aws:cloudformation:{Region}:{AccountNumber}:stack/stack-name/{Guid.NewGuid().ToString()}"",
""requestId"":""12345678"",
""resourceType"":""Custom::TestResource"",
""logicalResourceId"":""MyTestResource"",
""resourceProperties"":{{
""Role"":""{IAMRole}"",
""Name"":""TestPipeline"",
""InputBucket"":""{InputBucket}"",
""Notifications"":{{
""Error"": ""{NotificationTopic}"",
}},
""ContentConfig"":{{
""Bucket"":""{OutputBucket}""
}},
""ThumbnailConfig"":{{
""Bucket"":""{ThumbnailBucket}""
}}
}}
}}";

            Json = Json.Trim().Replace("\r", "").Replace("\n", "").Replace("\t", "");

            Func <CustomResourceRequest, ILambdaContext, Task <CustomResourceResponse> > Create = async(request, context) =>
            {
                try
                {
                    //AmazonElasticTranscoderConfig Config = new AmazonElasticTranscoderConfig();
                    //IAmazonElasticTranscoder Client = new AmazonElasticTranscoderClient(Config);
                    Mock <IAmazonElasticTranscoder> mockClient = new Mock <IAmazonElasticTranscoder>();
                    mockClient.Setup(x => x.CreatePipelineAsync(It.IsAny <CreatePipelineRequest>(), default(CancellationToken)))
                    .ReturnsAsync(new CreatePipelineResponse()
                    {
                        HttpStatusCode = HttpStatusCode.OK
                    });

                    context.LogInfo("Attempting to create a pipeline.");
                    CreatePipelineRequest  PipelineRequest = JsonConvert.DeserializeObject <CreatePipelineRequest>(JsonConvert.SerializeObject(request.ResourceProperties));
                    CreatePipelineResponse CreateResponse  = await mockClient.Object.CreatePipelineAsync(PipelineRequest);

                    if ((int)CreateResponse.HttpStatusCode < 200 || (int)CreateResponse.HttpStatusCode > 299)
                    {
                        return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, $"Received HTTP status code {(int)CreateResponse.HttpStatusCode}.", request));
                    }
                    else
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.SUCCESS,
                                   $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                                   CreateResponse.Pipeline.Id,
                                   request.StackId,
                                   request.RequestId,
                                   request.LogicalResourceId,
                                   false,
                                   new Dictionary <string, object>()
                        {
                            { "Name", CreateResponse.Pipeline.Name },
                            { "Arn", CreateResponse.Pipeline.Arn },
                            { "Id", CreateResponse.Pipeline.Id }
                        }
                                   ));
                    }
                }
                catch (AmazonElasticTranscoderException e)
                {
                    context.LogError(e);

                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               e.Message,
                               Guid.NewGuid().ToString(),
                               request.StackId,
                               request.RequestId,
                               request.LogicalResourceId
                               ));
                }
                catch (Exception e)
                {
                    context.LogError(e);

                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               e.Message,
                               Guid.NewGuid().ToString(),
                               request.StackId,
                               request.RequestId,
                               request.LogicalResourceId
                               ));
                }
            };

            Func <CustomResourceRequest, ILambdaContext, Task <CustomResourceResponse> > Update = async(request, context) =>
            {
                try
                {
                    context.LogInfo("Initiating update for pipeline.");

                    UpdatePipelineRequest PipelineRequest = JsonConvert.DeserializeObject <UpdatePipelineRequest>(JsonConvert.SerializeObject(request.ResourceProperties));

                    ListPipelinesRequest Listing = new ListPipelinesRequest();

                    List <Pipeline>       Pipelines = new List <Pipeline>();
                    ListPipelinesResponse Pipes;

                    AmazonElasticTranscoderConfig Config = new AmazonElasticTranscoderConfig();
                    IAmazonElasticTranscoder      Client = new AmazonElasticTranscoderClient(Config);

                    do
                    {
                        Pipes = await Client.ListPipelinesAsync(Listing);

                        Pipelines.AddRange(Pipes.Pipelines.Where(x => x.Name.Equals(request.ResourceProperties["Name"] as string) &&
                                                                 x.InputBucket.Equals(request.ResourceProperties["InputBucket"]) &&
                                                                 x.Role.Equals(request.ResourceProperties["Role"])
                                                                 ));
                    } while (Pipes.NextPageToken != null);

                    if (Pipelines.Count > 1)
                    {
                        context.LogWarning($"{Pipelines.Count} pipelines were found matching the Name, InputBucket, and Role specified.");
                    }

                    if (Pipelines.Count > 0)
                    {
                        PipelineRequest.Id = Pipelines.First().Id;

                        UpdatePipelineResponse UpdateResponse = await Client.UpdatePipelineAsync(PipelineRequest);

                        if ((int)UpdateResponse.HttpStatusCode < 200 || (int)UpdateResponse.HttpStatusCode > 299)
                        {
                            return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, $"Received HTTP status code {(int)UpdateResponse.HttpStatusCode}.", request));
                        }
                        else
                        {
                            return(new CustomResourceResponse(
                                       CustomResourceResponse.RequestStatus.SUCCESS,
                                       $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                                       request,
                                       false,
                                       new Dictionary <string, object>()
                            {
                                { "Name", UpdateResponse.Pipeline.Name },
                                { "Arn", UpdateResponse.Pipeline.Arn },
                                { "Id", UpdateResponse.Pipeline.Id }
                            }
                                       ));
                        }
                    }
                    else
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.FAILED,
                                   "No pipelines could be found with the matching characteristics.",
                                   request
                                   ));
                    }
                }
                catch (AmazonElasticTranscoderException e)
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               e.Message,
                               request
                               ));
                }
                catch (Exception e)
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               e.Message,
                               request
                               ));
                }
            };

            Func <CustomResourceRequest, ILambdaContext, Task <CustomResourceResponse> > Delete = async(request, context) =>
            {
                try
                {
                    context.LogInfo("Attempting to delete a pipeline.");

                    ListPipelinesRequest Listing = new ListPipelinesRequest();

                    List <Pipeline>       Pipelines = new List <Pipeline>();
                    ListPipelinesResponse Pipes;

                    AmazonElasticTranscoderConfig Config = new AmazonElasticTranscoderConfig();
                    IAmazonElasticTranscoder      Client = new AmazonElasticTranscoderClient(Config);

                    do
                    {
                        Pipes = await Client.ListPipelinesAsync(Listing);

                        Pipelines.AddRange(Pipes.Pipelines.Where(x => x.Name.Equals(request.ResourceProperties["Name"] as string) &&
                                                                 x.InputBucket.Equals(request.ResourceProperties["InputBucket"]) &&
                                                                 x.Role.Equals(request.ResourceProperties["Role"])
                                                                 ));
                    } while (Pipes.NextPageToken != null);

                    if (Pipelines.Count > 1)
                    {
                        context.LogWarning($"{Pipelines.Count} pipelines were found matching the Name, InputBucket, and Role specified.");
                    }

                    if (Pipelines.Count > 0)
                    {
                        DeletePipelineRequest PipelineRequest = new DeletePipelineRequest()
                        {
                            Id = Pipelines.First().Id
                        };

                        DeletePipelineResponse DeleteResponse = await Client.DeletePipelineAsync(PipelineRequest);

                        if ((int)DeleteResponse.HttpStatusCode < 200 || (int)DeleteResponse.HttpStatusCode > 299)
                        {
                            return(new CustomResourceResponse(CustomResourceResponse.RequestStatus.FAILED, $"Received HTTP status code {(int)DeleteResponse.HttpStatusCode}.", request));
                        }
                        else
                        {
                            return(new CustomResourceResponse(
                                       CustomResourceResponse.RequestStatus.SUCCESS,
                                       $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                                       request,
                                       false
                                       ));
                        }
                    }
                    else
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.SUCCESS,
                                   "No pipelines could be found with the matching characteristics.",
                                   request
                                   ));
                    }
                }
                catch (AmazonElasticTranscoderException e)
                {
                    // If the pipeline doesn't exist, consider it deleted
                    if (e.StatusCode == HttpStatusCode.NotFound)
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.SUCCESS,
                                   $"See the details in CloudWatch Log Stream: {context.LogStreamName}.",
                                   request
                                   ));
                    }
                    else
                    {
                        return(new CustomResourceResponse(
                                   CustomResourceResponse.RequestStatus.FAILED,
                                   e.Message,
                                   request
                                   ));
                    }
                }
                catch (Exception e)
                {
                    return(new CustomResourceResponse(
                               CustomResourceResponse.RequestStatus.FAILED,
                               e.Message,
                               request
                               ));
                }
            };

            CustomResourceRequest        customResourceRequest = JsonConvert.DeserializeObject <CustomResourceRequest>(Json);
            Mock <ICustomResourceHelper> mockHelper            = new Mock <ICustomResourceHelper>();

            mockHelper.Setup(x => x.PutCustomResourceResponseAsync(It.IsAny <CustomResourceRequest>(), It.IsAny <CustomResourceResponse>()))
            .ReturnsAsync(new CustomResourceResult(customResourceRequest, new CustomResourceResponse(RequestStatus.SUCCESS, "", customResourceRequest), new HttpResponseMessage(HttpStatusCode.OK)));

            ICustomResourceHandler Handler = new CustomResourceFactory(Create, Update, Delete, mockHelper.Object);

            TestLambdaLogger  TestLogger    = new TestLambdaLogger();
            TestClientContext ClientContext = new TestClientContext();

            TestLambdaContext Context = new TestLambdaContext()
            {
                FunctionName    = "ElasticTranscoderPipelineCreation",
                FunctionVersion = "1",
                Logger          = TestLogger,
                ClientContext   = ClientContext,
                LogGroupName    = "aws/lambda/ElasticTranscoderPipeline",
                LogStreamName   = Guid.NewGuid().ToString()
            };

            // ACT

            CustomResourceResult Response = await Handler.ExecuteAsync(customResourceRequest, Context);

            // ASSERT
            Assert.NotNull(Response);
            Assert.NotNull(Response.Response);
            Assert.NotNull(Response.S3Response);
            Assert.True(Response.IsSuccess);
        }