public async Task EnqueueAsync_Throws_If_Not_Started()
        {
            // Arrange
            using var target = new LambdaTestServer();
            var request = new LambdaTestRequest(Array.Empty <byte>());

            // Act and Assert
            await Assert.ThrowsAsync <InvalidOperationException>(async() => await target.EnqueueAsync(request));
        }
        public async Task EnqueueAsync_Validates_Parameters()
        {
            // Arrange
            using var target = new LambdaTestServer();
            LambdaTestRequest request = null;

            // Act
            await Assert.ThrowsAsync <ArgumentNullException>("request", () => target.EnqueueAsync(request));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Enqueues a request for the Lambda function to process as an asynchronous operation.
        /// </summary>
        /// <param name="request">The request to invoke the function with.</param>
        /// <returns>
        /// A <see cref="Task"/> representing the asynchronous operation to enqueue the request
        /// which returns a channel reader which completes once the request is processed by the function.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="request"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// A request with the Id specified by <paramref name="request"/> is currently in-flight or the test server has not been started.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The instance has been disposed.
        /// </exception>
        public async Task <ChannelReader <LambdaTestResponse> > EnqueueAsync(LambdaTestRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            ThrowIfDisposed();
            ThrowIfNotStarted();

            return(await _handler.EnqueueAsync(request, _onStopped.Token).ConfigureAwait(false));
        }
        public async Task EnqueueAsync_Throws_If_Disposed()
        {
            // Arrange
            var target = new LambdaTestServer();

            target.Dispose();

            var request = new LambdaTestRequest(Array.Empty <byte>());

            // Act
            await Assert.ThrowsAsync <ObjectDisposedException>(() => target.EnqueueAsync(request));
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Enqueues a request for the Lambda function to process as an asynchronous operation.
        /// </summary>
        /// <param name="server">The server to enqueue the request with.</param>
        /// <param name="content">The request content to process.</param>
        /// <returns>
        /// A <see cref="Task"/> representing the asynchronous operation to enqueue the request
        /// which returns a channel reader which completes once the request is processed by the function.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="server"/> or <paramref name="content"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        /// The instance has been disposed.
        /// </exception>
        public static async Task <ChannelReader <LambdaTestResponse> > EnqueueAsync(
            this LambdaTestServer server,
            byte[] content)
        {
            if (server == null)
            {
                throw new ArgumentNullException(nameof(server));
            }

            var request = new LambdaTestRequest(content);

            return(await server.EnqueueAsync(request).ConfigureAwait(false));
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Enqueues a request for the Lambda function to process as an asynchronous operation.
        /// </summary>
        /// <param name="request">The request to invoke the function with.</param>
        /// <param name="cancellationToken">The cancellation token to use when enqueuing the item.</param>
        /// <returns>
        /// A <see cref="Task"/> representing the asynchronous operation to enqueue the request
        /// which returns a channel reader which completes once the request is processed by the function.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// A request with the Id specified by <paramref name="request"/> is currently in-flight.
        /// </exception>
        internal async Task <ChannelReader <LambdaTestResponse> > EnqueueAsync(
            LambdaTestRequest request,
            CancellationToken cancellationToken)
        {
            // There is only one response per request, so the channel is bounded to one item
            var channel = Channel.CreateBounded <LambdaTestResponse>(1);

            if (!_responses.TryAdd(request.AwsRequestId, channel))
            {
                throw new InvalidOperationException($"A request with AWS request Id '{request.AwsRequestId}' is currently in-flight.");
            }

            // Enqueue the request for the Lambda runtime to process
            await _requests.Writer.WriteAsync(request, cancellationToken);

            // Return the reader to the caller to await the function being handled
            return(channel.Reader);
        }
        public async Task Function_Can_Process_Request_With_Mobile_Sdk_Headers()
        {
            // Arrange
            void Configure(IServiceCollection services)
            {
                services.AddLogging((builder) => builder.AddXUnit(this));
            }

            using var server = new LambdaTestServer(Configure);
            using var cts    = new CancellationTokenSource(TimeSpan.FromSeconds(2));

            await server.StartAsync(cts.Token);

            byte[] content = Encoding.UTF8.GetBytes(@"{""Values"": [ 1, 2, 3 ]}");

            var request = new LambdaTestRequest(content)
            {
                ClientContext   = "{}",
                CognitoIdentity = "{}",
            };

            var reader = await server.EnqueueAsync(request);

            _ = Task.Run(async() =>
            {
                await reader.WaitToReadAsync(cts.Token);

                if (!cts.IsCancellationRequested)
                {
                    cts.Cancel();
                }
            });

            using var httpClient = server.CreateClient();

            // Act
            await MyFunctionEntrypoint.RunAsync(httpClient, cts.Token);

            // Assert
            reader.TryRead(out var response).ShouldBeTrue();

            response.ShouldNotBeNull();
            response.IsSuccessful.ShouldBeTrue();
        }
        public static void Constructor_Sets_Properties()
        {
            // Arrange
            byte[] content = new[] { (byte)1 };

            // Act
            var actual = new LambdaTestRequest(content);

            // Assert
            actual.Content.ShouldBeSameAs(content);
            actual.AwsRequestId.ShouldNotBeNullOrEmpty();
            Guid.TryParse(actual.AwsRequestId, out var requestId).ShouldBeTrue();
            requestId.ShouldNotBe(Guid.Empty);

            // Arrange
            string awsRequestId = "my-request-id";

            // Act
            actual = new LambdaTestRequest(content, awsRequestId);

            // Assert
            actual.Content.ShouldBeSameAs(content);
            actual.AwsRequestId.ShouldBe(awsRequestId);
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Handles a request for the next invocation for the Lambda function.
        /// </summary>
        /// <param name="httpContext">The HTTP context.</param>
        /// <returns>
        /// A <see cref="Task"/> representing the asynchronous operation to get the next invocation request.
        /// </returns>
        internal async Task HandleNextAsync(HttpContext httpContext)
        {
            Logger.LogInformation(
                "Waiting for new request for Lambda function with ARN {FunctionArn}.",
                _options.FunctionArn);

            LambdaTestRequest request;

            try
            {
                // Additionally cancel the listen loop if the processing is stopped
                using var cts = CancellationTokenSource.CreateLinkedTokenSource(httpContext.RequestAborted, _cancellationToken);

                // Wait until there is a request to process
                await _requests.Reader.WaitToReadAsync(cts.Token);

                request = await _requests.Reader.ReadAsync();
            }
            catch (OperationCanceledException ex)
            {
                Logger.LogInformation(
                    ex,
                    "Stopped listening for additional requests for Lambda function with ARN {FunctionArn}.",
                    _options.FunctionArn);

                // Send a dummy response to prevent the listen loop from erroring
                request = new LambdaTestRequest(new[] { (byte)'{', (byte)'}' }, "xx-lambda-test-server-stopped-xx");

                // This dummy request wasn't enqueued, so it needs manually adding
                _responses.GetOrAdd(request.AwsRequestId, (_) => Channel.CreateBounded <LambdaTestResponse>(1));
            }

            // Write the response for the Lambda runtime to pass to the function to invoke
            string traceId = Guid.NewGuid().ToString();

            Logger.LogInformation(
                "Invoking Lambda function with ARN {FunctionArn} for request Id {AwsRequestId} and trace Id {AwsTraceId}.",
                _options.FunctionArn,
                request.AwsRequestId,
                traceId);

            _responses.GetOrAdd(request.AwsRequestId, (_) => Channel.CreateBounded <LambdaTestResponse>(1));

            // These headers are required, as otherwise an exception is thrown
            httpContext.Response.Headers.Add("Lambda-Runtime-Aws-Request-Id", request.AwsRequestId);
            httpContext.Response.Headers.Add("Lambda-Runtime-Invoked-Function-Arn", _options.FunctionArn);

            // These headers are optional
            httpContext.Response.Headers.Add("Lambda-Runtime-Trace-Id", traceId);

            if (request.ClientContext != null)
            {
                httpContext.Response.Headers.Add("Lambda-Runtime-Client-Context", request.ClientContext);
            }

            if (request.CognitoIdentity != null)
            {
                httpContext.Response.Headers.Add("Lambda-Runtime-Cognito-Identity", request.CognitoIdentity);
            }

            var deadline = DateTimeOffset.UtcNow.Add(_options.FunctionTimeout).ToUnixTimeMilliseconds();

            httpContext.Response.Headers.Add("Lambda-Runtime-Deadline-Ms", deadline.ToString("F0", CultureInfo.InvariantCulture));

            httpContext.Response.ContentType = MediaTypeNames.Application.Json;
            httpContext.Response.StatusCode  = StatusCodes.Status200OK;

            await httpContext.Response.BodyWriter.WriteAsync(request.Content, httpContext.RequestAborted);
        }