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)); }
/// <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)); }
/// <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)); }
/// <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); }
/// <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); }