Skip to content

Commit 69937e2

Browse files
authored
Add integration test project for end to end validation. (#973)
1 parent 69077e9 commit 69937e2

File tree

12 files changed

+167
-13
lines changed

12 files changed

+167
-13
lines changed

Aspire.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.MySqlConnector", "sr
156156
EndProject
157157
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.MySqlConnector.Tests", "tests\Aspire.MySqlConnector.Tests\Aspire.MySqlConnector.Tests.csproj", "{C8079F06-304F-49B1-A0C1-45AA3782A923}"
158158
EndProject
159+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.IntegrationServiceA", "tests\testproject\TestProject.IntegrationServiceA\TestProject.IntegrationServiceA.csproj", "{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}"
160+
EndProject
159161
Global
160162
GlobalSection(SolutionConfigurationPlatforms) = preSolution
161163
Debug|Any CPU = Debug|Any CPU
@@ -418,6 +420,10 @@ Global
418420
{C8079F06-304F-49B1-A0C1-45AA3782A923}.Debug|Any CPU.Build.0 = Debug|Any CPU
419421
{C8079F06-304F-49B1-A0C1-45AA3782A923}.Release|Any CPU.ActiveCfg = Release|Any CPU
420422
{C8079F06-304F-49B1-A0C1-45AA3782A923}.Release|Any CPU.Build.0 = Release|Any CPU
423+
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
424+
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
425+
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
426+
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Release|Any CPU.Build.0 = Release|Any CPU
421427
EndGlobalSection
422428
GlobalSection(SolutionProperties) = preSolution
423429
HideSolutionNode = FALSE
@@ -491,6 +497,7 @@ Global
491497
{165411FE-755E-4869-A756-F87F455860AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
492498
{CA283D7F-EB95-4353-B196-C409965D2B42} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
493499
{C8079F06-304F-49B1-A0C1-45AA3782A923} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
500+
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8} = {975F6F41-B455-451D-A312-098DE4A167B6}
494501
EndGlobalSection
495502
GlobalSection(ExtensibilityGlobals) = postSolution
496503
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}

src/Aspire.Hosting/SqlServer/SqlServerBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static class SqlServerBuilderExtensions
2222
/// <returns>A reference to the <see cref="IResourceBuilder{SqlServerContainerResource}"/>.</returns>
2323
public static IResourceBuilder<SqlServerContainerResource> AddSqlServerContainer(this IDistributedApplicationBuilder builder, string name, string? password = null, int? port = null)
2424
{
25-
password = password ?? Guid.NewGuid().ToString("N");
25+
password = password ?? Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N").ToUpper();
2626
var sqlServer = new SqlServerContainerResource(name, password);
2727

2828
return builder.AddResource(sqlServer)

tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,5 +251,37 @@ public async void TestServicesWithMultipleReplicas()
251251
await Task.Delay(100, cts.Token);
252252
}
253253
}
254-
private static TestProgram CreateTestProgram(string[]? args = null) => TestProgram.Create<DistributedApplicationTests>(args);
254+
255+
[LocalOnlyFact]
256+
public async void VerifyHealthyOnIntegrationServiceA()
257+
{
258+
var testProgram = CreateTestProgram(includeIntegrationServices: true);
259+
testProgram.AppBuilder.Services.AddLogging(b => b.AddXunit(testOutputHelper));
260+
261+
testProgram.AppBuilder.Services
262+
.AddHttpClient()
263+
.ConfigureHttpClientDefaults(b =>
264+
{
265+
b.UseSocketsHttpHandler((handler, sp) => handler.PooledConnectionLifetime = TimeSpan.FromSeconds(5));
266+
});
267+
268+
await using var app = testProgram.Build();
269+
270+
var client = app.Services.GetRequiredService<IHttpClientFactory>().CreateClient();
271+
272+
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
273+
274+
await app.StartAsync(cts.Token);
275+
276+
// Make sure all services are running
277+
await testProgram.ServiceABuilder.HttpGetPidAsync(client, "http", cts.Token);
278+
await testProgram.ServiceBBuilder.HttpGetPidAsync(client, "http", cts.Token);
279+
await testProgram.ServiceCBuilder.HttpGetPidAsync(client, "http", cts.Token);
280+
await testProgram.IntegrationServiceA!.HttpGetPidAsync(client, "http", cts.Token);
281+
282+
// We wait until timeout for the /health endpoint to return successfully. We assume
283+
// that components wired up into this project have health checks enabled.
284+
await testProgram.IntegrationServiceA!.WaitForHealthyStatus(client, "http", cts.Token);
285+
}
286+
private static TestProgram CreateTestProgram(string[]? args = null, bool includeIntegrationServices = false) => TestProgram.Create<DistributedApplicationTests>(args, includeIntegrationServices);
255287
}

tests/Aspire.Hosting.Tests/Helpers/AllocatedEndpointAnnotationTestExtensions.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,43 @@
44
namespace Aspire.Hosting.Tests.Helpers;
55
public static class AllocatedEndpointAnnotationTestExtensions
66
{
7-
public static async Task<string> HttpGetPidAsync(this IResourceBuilder<ProjectResource> builder, HttpClient client, string bindingName, CancellationToken cancellationToken)
7+
public static async Task<string> HttpGetAsync(this IResourceBuilder<ProjectResource> builder, HttpClient client, string bindingName, string path, CancellationToken cancellationToken)
8+
{
9+
// We have to get the allocated endpoint each time through the loop
10+
// because it may not be populated yet by the time we get here.
11+
var allocatedEndpoint = builder.Resource.Annotations.OfType<AllocatedEndpointAnnotation>().Single(a => a.Name == bindingName);
12+
var url = $"{allocatedEndpoint.UriString}{path}";
13+
14+
var response = await client.GetStringAsync(url, cancellationToken);
15+
return response;
16+
}
17+
18+
public static async Task<string> WaitForHealthyStatus(this IResourceBuilder<ProjectResource> builder, HttpClient client, string bindingName, CancellationToken cancellationToken)
819
{
920
while (true)
1021
{
1122
try
1223
{
13-
// We have to get the allocated endpoint each time through the loop
14-
// because it may not be populated yet by the time we get here.
15-
var allocatedEndpoint = builder.Resource.Annotations.OfType<AllocatedEndpointAnnotation>().Single(a => a.Name == bindingName);
16-
var url = $"{allocatedEndpoint.UriString}/pid";
24+
return await builder.HttpGetAsync(client, bindingName, "/health", cancellationToken);
25+
}
26+
catch (HttpRequestException ex)
27+
{
28+
Console.WriteLine(ex);
29+
}
30+
catch
31+
{
32+
await Task.Delay(100, cancellationToken);
33+
}
34+
}
35+
}
1736

18-
var response = await client.GetStringAsync(url, cancellationToken);
19-
return response;
37+
public static async Task<string> HttpGetPidAsync(this IResourceBuilder<ProjectResource> builder, HttpClient client, string bindingName, CancellationToken cancellationToken)
38+
{
39+
while (true)
40+
{
41+
try
42+
{
43+
return await builder.HttpGetAsync(client, bindingName, "/pid", cancellationToken);
2044
}
2145
catch (HttpRequestException ex)
2246
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
var testProgram = TestProgram.Create<Program>(args);
4+
var testProgram = TestProgram.Create<Program>(args, includeIntegrationServices: true, disableDashboard: false);
55
await testProgram.RunAsync();

tests/testproject/TestProject.AppHost/TestProgram.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@
55

66
public class TestProgram
77
{
8-
private TestProgram(string[] args, Assembly assembly)
8+
private TestProgram(string[] args, Assembly assembly, bool includeIntegrationServices = false, bool disableDashboard = true)
99
{
10-
AppBuilder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions { Args = args, DisableDashboard = true, AssemblyName = assembly.FullName });
10+
AppBuilder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions { Args = args, DisableDashboard = disableDashboard, AssemblyName = assembly.FullName });
1111
ServiceABuilder = AppBuilder.AddProject<Projects.ServiceA>("servicea");
1212
ServiceBBuilder = AppBuilder.AddProject<Projects.ServiceB>("serviceb");
1313
ServiceCBuilder = AppBuilder.AddProject<Projects.ServiceC>("servicec");
14+
15+
if (includeIntegrationServices)
16+
{
17+
var sql = AppBuilder.AddSqlServerContainer("sql");
18+
IntegrationServiceA = AppBuilder.AddProject<Projects.IntegrationServiceA>("integrationservicea")
19+
.WithReference(sql);
20+
}
1421
}
1522

16-
public static TestProgram Create<T>(string[]? args = null) => new TestProgram(args ?? [], typeof(T).Assembly);
23+
public static TestProgram Create<T>(string[]? args = null, bool includeIntegrationServices = false, bool disableDashboard = true) => new TestProgram(args ?? [], typeof(T).Assembly, includeIntegrationServices, disableDashboard);
1724

1825
public IDistributedApplicationBuilder AppBuilder { get; private set; }
1926
public IResourceBuilder<ProjectResource> ServiceABuilder { get; private set; }
2027
public IResourceBuilder<ProjectResource> ServiceBBuilder { get; private set; }
2128
public IResourceBuilder<ProjectResource> ServiceCBuilder { get; private set; }
29+
public IResourceBuilder<ProjectResource>? IntegrationServiceA { get; private set; }
2230
public DistributedApplication? App { get; private set; }
2331

2432
public List<IResourceBuilder<ProjectResource>> ServiceProjectBuilders => [ServiceABuilder, ServiceBBuilder, ServiceCBuilder];
@@ -37,6 +45,7 @@ public DistributedApplication Build()
3745
}
3846
return App;
3947
}
48+
4049
public void Run()
4150
{
4251
Build();

tests/testproject/TestProject.AppHost/TestProject.AppHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
<ItemGroup>
1212
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" />
13+
<ProjectReference Include="..\TestProject.IntegrationServiceA\TestProject.IntegrationServiceA.csproj" ServiceNameOverride="IntegrationServiceA" />
1314
<ProjectReference Include="..\TestProject.ServiceA\TestProject.ServiceA.csproj" ServiceNameOverride="ServiceA" />
1415
<ProjectReference Include="..\TestProject.ServiceB\TestProject.ServiceB.csproj" ServiceNameOverride="ServiceB" />
1516
<ProjectReference Include="..\TestProject.ServiceC\TestProject.ServiceC.csproj" ServiceNameOverride="ServiceC" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
builder.AddSqlServerClient("sql");
6+
7+
var app = builder.Build();
8+
9+
app.MapHealthChecks("/health");
10+
app.MapGet("/", () => "Hello World!");
11+
app.MapGet("/pid", () => Environment.ProcessId);
12+
app.Run();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:37455",
8+
"sslPort": 44334
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"applicationUrl": "http://localhost:5281",
17+
"environmentVariables": {
18+
"ASPNETCORE_ENVIRONMENT": "Development"
19+
}
20+
},
21+
"https": {
22+
"commandName": "Project",
23+
"dotnetRunMessages": true,
24+
"launchBrowser": true,
25+
"applicationUrl": "https://localhost:7038;http://localhost:5281",
26+
"environmentVariables": {
27+
"ASPNETCORE_ENVIRONMENT": "Development"
28+
}
29+
},
30+
"IIS Express": {
31+
"commandName": "IISExpress",
32+
"launchBrowser": true,
33+
"environmentVariables": {
34+
"ASPNETCORE_ENVIRONMENT": "Development"
35+
}
36+
}
37+
}
38+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\..\src\Components\Aspire.Microsoft.Data.SqlClient\Aspire.Microsoft.Data.SqlClient.csproj" />
11+
<ProjectReference Include="..\..\..\src\Components\Aspire.Microsoft.EntityFrameworkCore.SqlServer\Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj" />
12+
</ItemGroup>
13+
14+
</Project>

0 commit comments

Comments
 (0)