Skip to content

Commit bd8fdf2

Browse files
committed
Fix routes
1 parent a96edab commit bd8fdf2

File tree

6 files changed

+267
-217
lines changed

6 files changed

+267
-217
lines changed

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using System.Text;
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Text;
25

36
namespace Amazon.Lambda.TestTool.Utilities;
47

Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/Helpers/ApiGatewayHelper.cs

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ public ApiGatewayHelper(IAmazonAPIGateway apiGatewayV1Client, IAmazonApiGatewayV
2323
_httpClient = new HttpClient();
2424
}
2525

26-
public async Task WaitForApiAvailability(string apiId, string apiUrl, bool isHttpApi, int maxWaitTimeSeconds = 30)
26+
public async Task WaitForApiAvailability(string apiId, string apiUrl, bool isHttpApi, int maxWaitTimeSeconds = 60)
2727
{
2828
var startTime = DateTime.UtcNow;
29+
var successStartTime = DateTime.UtcNow;
30+
var requiredSuccessDuration = TimeSpan.FromSeconds(10);
31+
bool hasBeenSuccessful = false;
32+
2933
while ((DateTime.UtcNow - startTime).TotalSeconds < maxWaitTimeSeconds)
3034
{
3135
try
@@ -47,77 +51,107 @@ public async Task WaitForApiAvailability(string apiId, string apiUrl, bool isHtt
4751
{
4852
var response = await httpClient.PostAsync(apiUrl, new StringContent("{}"));
4953

50-
// Check if we get a response, even if it's an error
51-
if (response.StatusCode != HttpStatusCode.NotFound && response.StatusCode != HttpStatusCode.Forbidden)
54+
// Check if we get a successful response
55+
if (response.StatusCode != HttpStatusCode.Forbidden && response.StatusCode != HttpStatusCode.NotFound)
56+
{
57+
if (!hasBeenSuccessful)
58+
{
59+
successStartTime = DateTime.UtcNow;
60+
hasBeenSuccessful = true;
61+
}
62+
63+
if ((DateTime.UtcNow - successStartTime) >= requiredSuccessDuration)
64+
{
65+
return; // API has been responding successfully for at least 10 seconds
66+
}
67+
}
68+
else
5269
{
53-
return; // API is available and responding
70+
// Reset the success timer if we get a non-successful response
71+
hasBeenSuccessful = false;
72+
Console.WriteLine($"API responded with status code: {response.StatusCode}");
5473
}
5574
}
5675
}
5776
catch (Amazon.ApiGatewayV2.Model.NotFoundException) when (isHttpApi)
5877
{
5978
// HTTP API not found yet, continue waiting
79+
hasBeenSuccessful = false;
6080
}
6181
catch (Amazon.APIGateway.Model.NotFoundException) when (!isHttpApi)
6282
{
6383
// REST API not found yet, continue waiting
84+
hasBeenSuccessful = false;
6485
}
6586
catch (Exception ex)
6687
{
67-
// Log unexpected exceptions
88+
// Log unexpected exceptions and reset success timer
6889
Console.WriteLine($"Unexpected error while checking API availability: {ex.Message}");
90+
hasBeenSuccessful = false;
6991
}
7092
await Task.Delay(1000); // Wait for 1 second before checking again
7193
}
72-
throw new TimeoutException($"API {apiId} did not become available within {maxWaitTimeSeconds} seconds");
94+
throw new TimeoutException($"API {apiId} did not become consistently available within {maxWaitTimeSeconds} seconds");
7395
}
7496

75-
public async Task<string> AddRouteToRestApi(string restApiId, string lambdaArn, string route = "/test")
97+
98+
public async Task<string> AddRouteToRestApi(string restApiId, string lambdaArn, string route = "/test", string httpMethod = "ANY")
7699
{
77-
var rootResourceId = (await _apiGatewayV1Client.GetResourcesAsync(new GetResourcesRequest { RestApiId = restApiId })).Items[0].Id;
100+
// Get all resources and find the root resource
101+
var resources = await _apiGatewayV1Client.GetResourcesAsync(new GetResourcesRequest { RestApiId = restApiId });
102+
var rootResource = resources.Items.First(r => r.Path == "/");
103+
var rootResourceId = rootResource.Id;
78104

79-
var pathParts = route.Trim('/').Split('/');
80-
var currentResourceId = rootResourceId;
81-
foreach (var pathPart in pathParts)
105+
// Split the route into parts and create each part
106+
var routeParts = route.Trim('/').Split('/');
107+
string currentPath = "";
108+
string parentResourceId = rootResourceId;
109+
110+
foreach (var part in routeParts)
82111
{
83-
var resources = await _apiGatewayV1Client.GetResourcesAsync(new GetResourcesRequest { RestApiId = restApiId });
84-
var existingResource = resources.Items.FirstOrDefault(r => r.ParentId == currentResourceId && r.PathPart == pathPart);
112+
currentPath += "/" + part;
85113

114+
// Check if the resource already exists
115+
var existingResource = resources.Items.FirstOrDefault(r => r.Path == currentPath);
86116
if (existingResource == null)
87117
{
118+
// Create the resource if it doesn't exist
88119
var createResourceResponse = await _apiGatewayV1Client.CreateResourceAsync(new CreateResourceRequest
89120
{
90121
RestApiId = restApiId,
91-
ParentId = currentResourceId,
92-
PathPart = pathPart
122+
ParentId = parentResourceId,
123+
PathPart = part
93124
});
94-
currentResourceId = createResourceResponse.Id;
125+
parentResourceId = createResourceResponse.Id;
95126
}
96127
else
97128
{
98-
currentResourceId = existingResource.Id;
129+
parentResourceId = existingResource.Id;
99130
}
100131
}
101132

133+
// Create the method for the final resource
102134
await _apiGatewayV1Client.PutMethodAsync(new PutMethodRequest
103135
{
104136
RestApiId = restApiId,
105-
ResourceId = currentResourceId,
106-
HttpMethod = "ANY",
137+
ResourceId = parentResourceId,
138+
HttpMethod = httpMethod,
107139
AuthorizationType = "NONE"
108140
});
109141

142+
// Create the integration for the method
110143
await _apiGatewayV1Client.PutIntegrationAsync(new PutIntegrationRequest
111144
{
112145
RestApiId = restApiId,
113-
ResourceId = currentResourceId,
114-
HttpMethod = "ANY",
146+
ResourceId = parentResourceId,
147+
HttpMethod = httpMethod,
115148
Type = APIGateway.IntegrationType.AWS_PROXY,
116149
IntegrationHttpMethod = "POST",
117150
Uri = $"arn:aws:apigateway:{_apiGatewayV1Client.Config.RegionEndpoint.SystemName}:lambda:path/2015-03-31/functions/{lambdaArn}/invocations"
118151
});
119152

120-
await _apiGatewayV1Client.CreateDeploymentAsync(new APIGateway.Model.CreateDeploymentRequest
153+
// Deploy the API
154+
var deploymentResponse = await _apiGatewayV1Client.CreateDeploymentAsync(new APIGateway.Model.CreateDeploymentRequest
121155
{
122156
RestApiId = restApiId,
123157
StageName = "test"

Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/HttpContextExtensionsTests.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,40 @@ public HttpContextExtensionsTests(ApiGatewayIntegrationTestFixture fixture)
2525

2626
[Theory]
2727
[MemberData(nameof(HttpContextTestCases.V1TestCases), MemberType = typeof(HttpContextTestCases))]
28+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")]
2829
public async Task IntegrationTest_APIGatewayV1_REST(string testName, ApiGatewayTestCaseForRequest testCase)
2930
{
3031
var route = testCase.ApiGatewayRouteConfig?.Path ?? "/test";
3132
await _fixture.ApiGatewayHelper.AddRouteToRestApi(_fixture.ReturnFullEventRestApiId, _fixture.ReturnFullEventLambdaFunctionArn, route);
32-
await RunApiGatewayTest<APIGatewayProxyRequest>(testCase, _fixture.ReturnFullEventRestApiUrl,
33+
await RunApiGatewayTest<APIGatewayProxyRequest>(testCase, _fixture.ReturnFullEventRestApiUrl, _fixture.ReturnFullEventRestApiId,
3334
(context, config) => context.ToApiGatewayRequest(config), true);
3435
}
3536

3637
[Theory]
3738
[MemberData(nameof(HttpContextTestCases.V1TestCases), MemberType = typeof(HttpContextTestCases))]
39+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")]
3840
public async Task IntegrationTest_APIGatewayV1_HTTP(string testName, ApiGatewayTestCaseForRequest testCase)
3941
{
4042
var route = testCase.ApiGatewayRouteConfig?.Path ?? "/test";
4143
var routeKey = testCase.ApiGatewayRouteConfig?.HttpMethod ?? "POST";
4244
await _fixture.ApiGatewayHelper.AddRouteToHttpApi(_fixture.ReturnFullEventHttpApiV1Id, _fixture.ReturnFullEventLambdaFunctionArn, "1.0", route, routeKey);
43-
await RunApiGatewayTest<APIGatewayProxyRequest>(testCase, _fixture.ReturnFullEventHttpApiV1Url,
45+
await RunApiGatewayTest<APIGatewayProxyRequest>(testCase, _fixture.ReturnFullEventHttpApiV1Url, _fixture.ReturnFullEventHttpApiV1Id,
4446
(context, config) => context.ToApiGatewayRequest(config), false);
4547
}
4648

4749
[Theory]
4850
[MemberData(nameof(HttpContextTestCases.V2TestCases), MemberType = typeof(HttpContextTestCases))]
51+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")]
4952
public async Task IntegrationTest_APIGatewayV2(string testName, ApiGatewayTestCaseForRequest testCase)
5053
{
5154
var route = testCase.ApiGatewayRouteConfig?.Path ?? "/test";
5255
var routeKey = testCase.ApiGatewayRouteConfig?.HttpMethod ?? "POST";
5356
await _fixture.ApiGatewayHelper.AddRouteToHttpApi(_fixture.ReturnFullEventHttpApiV2Id, _fixture.ReturnFullEventLambdaFunctionArn, "2.0", route, routeKey);
54-
await RunApiGatewayTest<APIGatewayHttpApiV2ProxyRequest>(testCase, _fixture.ReturnFullEventHttpApiV2Url,
57+
await RunApiGatewayTest<APIGatewayHttpApiV2ProxyRequest>(testCase, _fixture.ReturnFullEventHttpApiV2Url, _fixture.ReturnFullEventHttpApiV2Id,
5558
(context, config) => context.ToApiGatewayHttpV2Request(config), false);
5659
}
5760

58-
private async Task RunApiGatewayTest<T>(ApiGatewayTestCaseForRequest testCase, string apiUrl, Func<HttpContext, ApiGatewayRouteConfig, T> toApiGatewayRequest, bool isRestAPI)
61+
private async Task RunApiGatewayTest<T>(ApiGatewayTestCaseForRequest testCase, string apiUrl, string apiId, Func<HttpContext, ApiGatewayRouteConfig, T> toApiGatewayRequest, bool isRestAPI)
5962
where T : class
6063
{
6164
var httpClient = new HttpClient();
@@ -65,7 +68,7 @@ private async Task RunApiGatewayTest<T>(ApiGatewayTestCaseForRequest testCase, s
6568
var stageName = isRestAPI ? "/test" : ""; // matching hardcoded test stage name for rest api. TODO update this logic later to not be hardcoded
6669
var actualPath = ResolveActualPath(testCase.ApiGatewayRouteConfig.Path, testCase.HttpContext.Request.Path);
6770
var fullUrl = baseUrl + stageName + actualPath + testCase.HttpContext.Request.QueryString;
68-
71+
await _fixture.ApiGatewayHelper.WaitForApiAvailability(apiId, fullUrl, !isRestAPI);
6972

7073
var httpRequest = CreateHttpRequestMessage(testCase.HttpContext, fullUrl);
7174

Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/cloudformation-template-apigateway.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Resources:
8282
Type: 'AWS::ApiGateway::RestApi'
8383
Properties:
8484
Name: !Sub '${AWS::StackName}-ParseAndReturnBodyRestAPI'
85+
EndpointConfiguration:
86+
Types:
87+
- REGIONAL
8588

8689
ParseAndReturnBodyRestApiResource:
8790
Type: 'AWS::ApiGateway::Resource'
@@ -176,6 +179,9 @@ Resources:
176179
Type: 'AWS::ApiGateway::RestApi'
177180
Properties:
178181
Name: !Sub '${AWS::StackName}-ReturnRawBodyRestAPI'
182+
EndpointConfiguration:
183+
Types:
184+
- REGIONAL
179185

180186
ReturnRawBodyRestApiResource:
181187
Type: 'AWS::ApiGateway::Resource'
@@ -270,6 +276,9 @@ Resources:
270276
Type: 'AWS::ApiGateway::RestApi'
271277
Properties:
272278
Name: !Sub '${AWS::StackName}-ReturnFullEventRestAPI'
279+
EndpointConfiguration:
280+
Types:
281+
- REGIONAL
273282

274283
ReturnFullEventRestApiResource:
275284
Type: 'AWS::ApiGateway::Resource'
@@ -436,6 +445,9 @@ Resources:
436445
Type: 'AWS::ApiGateway::RestApi'
437446
Properties:
438447
Name: !Sub '${AWS::StackName}-ReturnDecodedParseBinRestAPI'
448+
EndpointConfiguration:
449+
Types:
450+
- REGIONAL
439451
BinaryMediaTypes:
440452
- '*/*'
441453

Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/HttpContextExtensionsTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using Amazon.Lambda.APIGatewayEvents;
45
using Amazon.Lambda.TestTool.Extensions;
6+
using Amazon.Lambda.TestTool.Models;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Mvc;
9+
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
510
using static Amazon.Lambda.TestTool.UnitTests.Extensions.HttpContextTestCases;
611

712
namespace Amazon.Lambda.TestTool.UnitTests.Extensions
@@ -35,5 +40,52 @@ public void ToApiGatewayHttpV2Request_ConvertsCorrectly(string testName, ApiGate
3540
// Assert
3641
testCase.Assertions(result);
3742
}
43+
44+
[Fact]
45+
public void ToApiGatewayHttpV2Request_EmptyCollections()
46+
{
47+
48+
var httpContext = CreateHttpContext("POST", "/test10/api/notmatchingpath/123/orders");
49+
var config = new ApiGatewayRouteConfig
50+
{
51+
LambdaResourceName = "TestLambdaFunction",
52+
Endpoint = "/test10/api/users/{userId}/orders",
53+
HttpMethod = "POST",
54+
Path = "/test10/api/users/{userId}/orders"
55+
};
56+
57+
// Act
58+
var result = httpContext.ToApiGatewayHttpV2Request(config);
59+
Assert.Equal(2, result.Headers.Count);
60+
Assert.Equal("0", result.Headers["content-length"]);
61+
Assert.Equal("text/plain; charset=utf-8", result.Headers["content-type"]);
62+
Assert.Null(result.QueryStringParameters);
63+
Assert.Null(result.PathParameters);
64+
Assert.Null(result.Cookies);
65+
}
66+
67+
[Fact]
68+
public void ToApiGatewayHttpV1Request_EmptyCollections()
69+
{
70+
var httpContext = CreateHttpContext("POST", "/test10/api/notmatchingpath/123/orders");
71+
var config = new ApiGatewayRouteConfig
72+
{
73+
LambdaResourceName = "TestLambdaFunction",
74+
Endpoint = "/test10/api/users/{userId}/orders",
75+
HttpMethod = "POST",
76+
Path = "/test10/api/users/{userId}/orders"
77+
};
78+
79+
// Act
80+
var result = httpContext.ToApiGatewayRequest(config);
81+
Assert.Equal(2, result.Headers.Count);
82+
Assert.Equal("0", result.Headers["content-length"]);
83+
Assert.Equal("text/plain; charset=utf-8", result.Headers["content-type"]);
84+
Assert.Equal(new List<string> { "0" }, result.MultiValueHeaders["content-length"]);
85+
Assert.Equal(new List<string> { "text/plain; charset=utf-8" }, result.MultiValueHeaders["content-type"]);
86+
Assert.Null(result.QueryStringParameters);
87+
Assert.Null(result.MultiValueQueryStringParameters);
88+
Assert.Null(result.PathParameters);
89+
}
3890
}
3991
}

0 commit comments

Comments
 (0)