Skip to content

Commit 5bfe161

Browse files
committed
Phase 4: End-to-end source generator test for FunctionUrl
- FunctionUrlExample.cs test source with [FunctionUrl] + [FromQuery] + IHttpResult - Generated wrapper snapshot using HttpApi V2 payload format - Serverless template snapshot with FunctionUrlConfig - Full Roslyn source generator verification test
1 parent 44dd3a6 commit 5bfe161

File tree

6 files changed

+182
-2
lines changed

6 files changed

+182
-2
lines changed

Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Import Project="..\..\..\buildtools\common.props" />
44

55
<PropertyGroup>
6-
<TargetFrameworks>netstandard2.0;net6.0;net8.0;net9.0</TargetFrameworks>
6+
<TargetFrameworks>netstandard2.0;net6.0;net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
77
<Version>1.14.2</Version>
88
<Description>Provides a bootstrap and Lambda Runtime API Client to help you to develop custom .NET Core Lambda Runtimes.</Description>
99
<AssemblyTitle>Amazon.Lambda.RuntimeSupport</AssemblyTitle>

Libraries/src/SnapshotRestore.Registry/SnapshotRestore.Registry.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Import Project="..\..\..\buildtools\common.props" />
44

55
<PropertyGroup>
6-
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
6+
<TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
77
<Version>1.0.1</Version>
88
<Description>Provides a Restore Hooks library to help you register before snapshot and after restore hooks.</Description>
99
<AssemblyTitle>SnapshotRestore.Registry</AssemblyTitle>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// <auto-generated/>
2+
3+
using System;
4+
using System.Linq;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using System.IO;
9+
using Amazon.Lambda.Core;
10+
using Amazon.Lambda.Annotations.APIGateway;
11+
12+
namespace TestServerlessApp
13+
{
14+
public class FunctionUrlExample_GetItems_Generated
15+
{
16+
private readonly FunctionUrlExample functionUrlExample;
17+
private readonly Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer serializer;
18+
19+
/// <summary>
20+
/// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment
21+
/// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the
22+
/// region the Lambda function is executed in.
23+
/// </summary>
24+
public FunctionUrlExample_GetItems_Generated()
25+
{
26+
SetExecutionEnvironment();
27+
functionUrlExample = new FunctionUrlExample();
28+
serializer = new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer();
29+
}
30+
31+
/// <summary>
32+
/// The generated Lambda function handler for <see cref="GetItems(string, Amazon.Lambda.Core.ILambdaContext)"/>
33+
/// </summary>
34+
/// <param name="__request__">The Function URL request object that will be processed by the Lambda function handler.</param>
35+
/// <param name="__context__">The ILambdaContext that provides methods for logging and describing the Lambda environment.</param>
36+
/// <returns>Result of the Lambda function execution</returns>
37+
public System.IO.Stream GetItems(Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest __request__, Amazon.Lambda.Core.ILambdaContext __context__)
38+
{
39+
var validationErrors = new List<string>();
40+
41+
var category = default(string);
42+
if (__request__.QueryStringParameters?.ContainsKey("category") == true)
43+
{
44+
try
45+
{
46+
category = (string)Convert.ChangeType(__request__.QueryStringParameters["category"], typeof(string));
47+
}
48+
catch (Exception e) when (e is InvalidCastException || e is FormatException || e is OverflowException || e is ArgumentException)
49+
{
50+
validationErrors.Add($"Value {__request__.QueryStringParameters["category"]} at 'category' failed to satisfy constraint: {e.Message}");
51+
}
52+
}
53+
54+
// return 400 Bad Request if there exists a validation error
55+
if (validationErrors.Any())
56+
{
57+
var errorResult = new Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse
58+
{
59+
Body = @$"{{""message"": ""{validationErrors.Count} validation error(s) detected: {string.Join(",", validationErrors)}""}}",
60+
Headers = new Dictionary<string, string>
61+
{
62+
{"Content-Type", "application/json"},
63+
{"x-amzn-ErrorType", "ValidationException"}
64+
},
65+
StatusCode = 400
66+
};
67+
var errorStream = new System.IO.MemoryStream();
68+
serializer.Serialize(errorResult, errorStream);
69+
errorStream.Position = 0;
70+
return errorStream;
71+
}
72+
73+
var httpResults = functionUrlExample.GetItems(category, __context__);
74+
HttpResultSerializationOptions.ProtocolFormat serializationFormat = HttpResultSerializationOptions.ProtocolFormat.HttpApi;
75+
HttpResultSerializationOptions.ProtocolVersion serializationVersion = HttpResultSerializationOptions.ProtocolVersion.V2;
76+
var serializationOptions = new HttpResultSerializationOptions { Format = serializationFormat, Version = serializationVersion, Serializer = serializer };
77+
var response = httpResults.Serialize(serializationOptions);
78+
return response;
79+
}
80+
81+
private static void SetExecutionEnvironment()
82+
{
83+
const string envName = "AWS_EXECUTION_ENV";
84+
85+
var envValue = new StringBuilder();
86+
87+
// If there is an existing execution environment variable add the annotations package as a suffix.
88+
if(!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(envName)))
89+
{
90+
envValue.Append($"{Environment.GetEnvironmentVariable(envName)}_");
91+
}
92+
93+
envValue.Append("lib/amazon-lambda-annotations#{ANNOTATIONS_ASSEMBLY_VERSION}");
94+
95+
Environment.SetEnvironmentVariable(envName, envValue.ToString());
96+
}
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"AWSTemplateFormatVersion": "2010-09-09",
3+
"Transform": "AWS::Serverless-2016-10-31",
4+
"Description": "This template is partially managed by Amazon.Lambda.Annotations (v{ANNOTATIONS_ASSEMBLY_VERSION}).",
5+
"Resources": {
6+
"TestServerlessAppFunctionUrlExampleGetItemsGenerated": {
7+
"Type": "AWS::Serverless::Function",
8+
"Metadata": {
9+
"Tool": "Amazon.Lambda.Annotations"
10+
},
11+
"Properties": {
12+
"Runtime": "dotnet6",
13+
"CodeUri": ".",
14+
"MemorySize": 512,
15+
"Timeout": 30,
16+
"Policies": [
17+
"AWSLambdaBasicExecutionRole"
18+
],
19+
"PackageType": "Zip",
20+
"Handler": "TestProject::TestServerlessApp.FunctionUrlExample_GetItems_Generated::GetItems",
21+
"FunctionUrlConfig": {
22+
"AuthType": "NONE"
23+
}
24+
}
25+
}
26+
}
27+
}

Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/SourceGeneratorTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,44 @@ public async Task IAuthorizerResultHttpApiTest()
18681868
Assert.Equal(expectedTemplateContent, actualTemplateContent);
18691869
}
18701870

1871+
[Fact]
1872+
public async Task FunctionUrlExample()
1873+
{
1874+
var expectedTemplateContent = await ReadSnapshotContent(Path.Combine("Snapshots", "ServerlessTemplates", "functionUrlExample.template"));
1875+
var expectedGetItemsGenerated = await ReadSnapshotContent(Path.Combine("Snapshots", "FunctionUrlExample_GetItems_Generated.g.cs"));
1876+
1877+
await new VerifyCS.Test
1878+
{
1879+
TestState =
1880+
{
1881+
Sources =
1882+
{
1883+
(Path.Combine("TestServerlessApp", "FunctionUrlExample.cs"), await File.ReadAllTextAsync(Path.Combine("TestServerlessApp", "FunctionUrlExample.cs"))),
1884+
(Path.Combine("Amazon.Lambda.Annotations", "LambdaFunctionAttribute.cs"), await File.ReadAllTextAsync(Path.Combine("Amazon.Lambda.Annotations", "LambdaFunctionAttribute.cs"))),
1885+
(Path.Combine("Amazon.Lambda.Annotations", "LambdaStartupAttribute.cs"), await File.ReadAllTextAsync(Path.Combine("Amazon.Lambda.Annotations", "LambdaStartupAttribute.cs"))),
1886+
(Path.Combine("Amazon.Lambda.Annotations", "APIGateway", "FunctionUrlAttribute.cs"), await File.ReadAllTextAsync(Path.Combine("Amazon.Lambda.Annotations", "APIGateway", "FunctionUrlAttribute.cs"))),
1887+
(Path.Combine("TestServerlessApp", "AssemblyAttributes.cs"), await File.ReadAllTextAsync(Path.Combine("TestServerlessApp", "AssemblyAttributes.cs"))),
1888+
},
1889+
GeneratedSources =
1890+
{
1891+
(
1892+
typeof(SourceGenerator.Generator),
1893+
"FunctionUrlExample_GetItems_Generated.g.cs",
1894+
SourceText.From(expectedGetItemsGenerated, Encoding.UTF8, SourceHashAlgorithm.Sha256)
1895+
)
1896+
},
1897+
ExpectedDiagnostics =
1898+
{
1899+
new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments("FunctionUrlExample_GetItems_Generated.g.cs", expectedGetItemsGenerated),
1900+
new DiagnosticResult("AWSLambda0103", DiagnosticSeverity.Info).WithArguments($"TestServerlessApp{Path.DirectorySeparatorChar}serverless.template", expectedTemplateContent)
1901+
}
1902+
}
1903+
}.RunAsync();
1904+
1905+
var actualTemplateContent = await File.ReadAllTextAsync(Path.Combine("TestServerlessApp", "serverless.template"));
1906+
Assert.Equal(expectedTemplateContent, actualTemplateContent);
1907+
}
1908+
18711909
public void Dispose()
18721910
{
18731911
File.Delete(Path.Combine("TestServerlessApp", "serverless.template"));
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Amazon.Lambda.Annotations;
2+
using Amazon.Lambda.Annotations.APIGateway;
3+
using Amazon.Lambda.Core;
4+
5+
namespace TestServerlessApp
6+
{
7+
public class FunctionUrlExample
8+
{
9+
[LambdaFunction]
10+
[FunctionUrl(AuthType = FunctionUrlAuthType.NONE)]
11+
public IHttpResult GetItems([FromQuery] string category, ILambdaContext context)
12+
{
13+
context.Logger.LogLine($"Getting items for category: {category}");
14+
return HttpResults.Ok(new { items = new[] { "item1", "item2" }, category });
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)