Skip to content

Add Integration test for verifying ApiGatewayProxyRequest format for binary content for REST api #1929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
1 commit merged into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

namespace Amazon.Lambda.TestTool.Extensions;

using System.Text;
using System.Web;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.TestTool.Models;
Expand Down Expand Up @@ -152,6 +151,13 @@ public static async Task<APIGatewayProxyRequest> ToApiGatewayRequest(
multiValueHeaders["content-type"] = ["text/plain; charset=utf-8"];
}


if (HttpRequestUtility.IsBinaryContent(request.ContentType) && emulatorMode == ApiGatewayEmulatorMode.Rest) // Rest mode with binary content never sends content length
{
headers.Remove("content-length");
multiValueHeaders.Remove("content-length");
}

// This is the decoded value
var path = request.Path.Value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public class ApiGatewayIntegrationTestFixture : IAsyncLifetime
public string ReturnFullEventHttpApiV2Url { get; private set; }

// ReturnDecodedParseBin
public string ReturnDecodedParseBinRestApiId { get; private set; }
public string ReturnDecodedParseBinRestApiUrl { get; private set; }
public string BinaryMediaTypeRestApiId { get; private set; }
public string BinaryMediaTypeRestApiUrl { get; private set; }

// Lambda Function ARNs
public string ParseAndReturnBodyLambdaFunctionArn { get; private set; }
Expand Down Expand Up @@ -86,9 +86,9 @@ public ApiGatewayIntegrationTestFixture()
ReturnFullEventHttpApiV1Url = string.Empty;
ReturnFullEventHttpApiV2Url = string.Empty;

// ReturnDecodedParseBin
ReturnDecodedParseBinRestApiId = string.Empty;
ReturnDecodedParseBinRestApiUrl = string.Empty;
// BinaryMediaTypeRestApiId
BinaryMediaTypeRestApiId = string.Empty;
BinaryMediaTypeRestApiUrl = string.Empty;

// Lambda Function ARNs
ParseAndReturnBodyLambdaFunctionArn = string.Empty;
Expand Down Expand Up @@ -170,8 +170,8 @@ private async Task RetrieveStackOutputs()
ReturnFullEventHttpApiV2Url = await CloudFormationHelper.GetOutputValueAsync(StackName, "ReturnFullEventHttpApiV2Url");

// ReturnDecodedParseBin
ReturnDecodedParseBinRestApiId = await CloudFormationHelper.GetOutputValueAsync(StackName, "ReturnDecodedParseBinRestApiId");
ReturnDecodedParseBinRestApiUrl = await CloudFormationHelper.GetOutputValueAsync(StackName, "ReturnDecodedParseBinRestApiUrl");
BinaryMediaTypeRestApiId = await CloudFormationHelper.GetOutputValueAsync(StackName, "BinaryMediaTypeRestApiId");
BinaryMediaTypeRestApiUrl = await CloudFormationHelper.GetOutputValueAsync(StackName, "BinaryMediaTypeRestApiUrl");

// Lambda Function ARNs
ParseAndReturnBodyLambdaFunctionArn = await CloudFormationHelper.GetOutputValueAsync(StackName, "ParseAndReturnBodyLambdaFunctionArn");
Expand All @@ -196,7 +196,7 @@ private async Task WaitForApisAvailability()
await ApiGatewayHelper.WaitForApiAvailability(ReturnFullEventHttpApiV1Id, ReturnFullEventHttpApiV1Url, true);
await ApiGatewayHelper.WaitForApiAvailability(ReturnFullEventHttpApiV2Id, ReturnFullEventHttpApiV2Url, true);

await ApiGatewayHelper.WaitForApiAvailability(ReturnDecodedParseBinRestApiId, ReturnDecodedParseBinRestApiUrl, false);
await ApiGatewayHelper.WaitForApiAvailability(BinaryMediaTypeRestApiId, BinaryMediaTypeRestApiUrl, false);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public async Task ToHttpResponse_RestAPIGatewayV1DecodesBase64()

var httpContext = new DefaultHttpContext();
testResponse.ToHttpResponse(httpContext, ApiGatewayEmulatorMode.Rest);
var actualResponse = await _httpClient.PostAsync(_fixture.ReturnDecodedParseBinRestApiUrl, new StringContent(JsonSerializer.Serialize(testResponse)));
var actualResponse = await _httpClient.PostAsync(_fixture.BinaryMediaTypeRestApiUrl, new StringContent(JsonSerializer.Serialize(testResponse)));
await _fixture.ApiGatewayTestHelper.AssertResponsesEqual(actualResponse, httpContext.Response);
Assert.Equal(200, (int)actualResponse.StatusCode);
var content = await actualResponse.Content.ReadAsStringAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,49 @@ await RunApiGatewayTest<APIGatewayProxyRequest>(
);
}

[Fact]
public async Task BinaryContentRest()
{
var httpContext = CreateHttpContext("POST", "/test4/api/users/123/avatar",
new Dictionary<string, StringValues> { { "Content-Type", "application/octet-stream" } },
body: new byte[] { 1, 2, 3, 4, 5 });

var config = new ApiGatewayRouteConfig
{
LambdaResourceName = "UploadAvatarFunction",
Endpoint = "/test4/api/users/{userId}/avatar",
HttpMethod = "POST",
Path = "/test4/api/users/{userId}/avatar"
};

var testCase = new HttpContextTestCase
{
HttpContext = httpContext,
ApiGatewayRouteConfig = config,
Assertions = (actualRequest, emulatorMode) =>
{
var typedRequest = (APIGatewayProxyRequest)actualRequest;
Assert.True(typedRequest.IsBase64Encoded);
Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3, 4, 5 }), typedRequest.Body);
Assert.Equal("123", typedRequest.PathParameters["userId"]);
Assert.Equal("/test4/api/users/{userId}/avatar", typedRequest.Resource);
Assert.Equal("POST", typedRequest.HttpMethod);
}
};

var route = testCase.ApiGatewayRouteConfig?.Path ?? "/test";
var routeKey = testCase.ApiGatewayRouteConfig?.HttpMethod ?? "POST";
await _fixture.ApiGatewayHelper.AddRouteToRestApi(_fixture.BinaryMediaTypeRestApiId, _fixture.ReturnFullEventLambdaFunctionArn, route, routeKey);

await RunApiGatewayTest<APIGatewayProxyRequest>(
testCase,
_fixture.BinaryMediaTypeRestApiUrl,
_fixture.BinaryMediaTypeRestApiId,
async (context, cfg) => await context.ToApiGatewayRequest(cfg, ApiGatewayEmulatorMode.Rest),
ApiGatewayEmulatorMode.Rest
);
}

private async Task RunApiGatewayTest<T>(HttpContextTestCase testCase, string apiUrl, string apiId, Func<HttpContext, ApiGatewayRouteConfig, Task<T>> toApiGatewayRequest, ApiGatewayEmulatorMode emulatorMode)
where T : class
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,49 +441,57 @@ Resources:
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ReturnFullEventHttpApiV2}/*'

ReturnDecodedParseBinRestApi:
BinaryMediaTypeRestApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: !Sub '${AWS::StackName}-ReturnDecodedParseBinRestAPI'
Name: !Sub '${AWS::StackName}-BinaryMediaTypeRestApi'
EndpointConfiguration:
Types:
- REGIONAL
BinaryMediaTypes:
- '*/*'

ReturnDecodedParseBinRestApiResource:
BinaryMediaTypeRestApiResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
ParentId: !GetAtt ReturnDecodedParseBinRestApi.RootResourceId
ParentId: !GetAtt BinaryMediaTypeRestApi.RootResourceId
PathPart: 'test'
RestApiId: !Ref ReturnDecodedParseBinRestApi
RestApiId: !Ref BinaryMediaTypeRestApi

ReturnDecodedParseBinRestApiMethod:
BinaryMediaTypeRestApiMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
HttpMethod: POST
ResourceId: !Ref ReturnDecodedParseBinRestApiResource
RestApiId: !Ref ReturnDecodedParseBinRestApi
ResourceId: !Ref BinaryMediaTypeRestApiResource
RestApiId: !Ref BinaryMediaTypeRestApi
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ReturnDecodedParseBinLambdaFunction.Arn}/invocations'

ReturnDecodedParseBinRestApiDeployment:
BinaryMediaTypeRestApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn: ReturnDecodedParseBinRestApiMethod
DependsOn: BinaryMediaTypeRestApiMethod
Properties:
RestApiId: !Ref ReturnDecodedParseBinRestApi
RestApiId: !Ref BinaryMediaTypeRestApi
StageName: 'test'

LambdaPermissionReturnDecodedParseBinRestApi:
LambdaPermissionBinaryMediaTypeRestApi:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !GetAtt ReturnDecodedParseBinLambdaFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ReturnDecodedParseBinRestApi}/*'
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${BinaryMediaTypeRestApi}/*'

LambdaPermissionBinaryMediaTypeRestApi2:
Type: 'AWS::Lambda::Permission'
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !GetAtt ReturnFullEventLambdaFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${BinaryMediaTypeRestApi}/*'

Outputs:
ParseAndReturnBodyRestApiId:
Expand Down Expand Up @@ -570,10 +578,10 @@ Outputs:
Description: 'ARN of the Return Full Event Lambda Function'
Value: !GetAtt ReturnFullEventLambdaFunction.Arn

ReturnDecodedParseBinRestApiId:
Description: 'ID of the ReturnDecodedParseBin Media REST API'
Value: !Ref ReturnDecodedParseBinRestApi
BinaryMediaTypeRestApiId:
Description: 'ID of the BinaryMediaTypeRest Media REST API'
Value: !Ref BinaryMediaTypeRestApi

ReturnDecodedParseBinRestApiUrl:
Description: 'URL of the ReturnDecodedParseBin Media REST API'
Value: !Sub 'https://${ReturnDecodedParseBinRestApi}.execute-api.${AWS::Region}.amazonaws.com/test/test'
BinaryMediaTypeRestApiUrl:
Description: 'URL of the BinaryMediaTypeRest Media REST API'
Value: !Sub 'https://${BinaryMediaTypeRestApi}.execute-api.${AWS::Region}.amazonaws.com/test/test'
Loading