Skip to content

Commit b6cbbd8

Browse files
authored
Extract Aspire.Hosting.PostgreSQL.Tests project (#4862)
1 parent 639d9cb commit b6cbbd8

File tree

7 files changed

+271
-7
lines changed

7 files changed

+271
-7
lines changed

Aspire.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{830A
508508
EndProject
509509
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Redis.Tests", "tests\Aspire.Hosting.Redis.Tests\Aspire.Hosting.Redis.Tests.csproj", "{1BC02557-B78B-48CE-9D3C-488A6B7672F4}"
510510
EndProject
511+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.PostgreSQL.Tests", "tests\Aspire.Hosting.PostgreSQL.Tests\Aspire.Hosting.PostgreSQL.Tests.csproj", "{7425E5B2-BC47-4521-AC40-B8CECA329E08}"
512+
EndProject
511513
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Qdrant.Tests", "tests\Aspire.Hosting.Qdrant.Tests\Aspire.Hosting.Qdrant.Tests.csproj", "{8E2AA85E-C351-47B4-AF91-58557FAD5840}"
512514
EndProject
513515
Global
@@ -1332,6 +1334,10 @@ Global
13321334
{8E2AA85E-C351-47B4-AF91-58557FAD5840}.Debug|Any CPU.Build.0 = Debug|Any CPU
13331335
{8E2AA85E-C351-47B4-AF91-58557FAD5840}.Release|Any CPU.ActiveCfg = Release|Any CPU
13341336
{8E2AA85E-C351-47B4-AF91-58557FAD5840}.Release|Any CPU.Build.0 = Release|Any CPU
1337+
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1338+
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Debug|Any CPU.Build.0 = Debug|Any CPU
1339+
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Release|Any CPU.ActiveCfg = Release|Any CPU
1340+
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Release|Any CPU.Build.0 = Release|Any CPU
13351341
EndGlobalSection
13361342
GlobalSection(SolutionProperties) = preSolution
13371343
HideSolutionNode = FALSE
@@ -1576,6 +1582,7 @@ Global
15761582
{830A89EC-4029-4753-B25A-068BAE37DEC7} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
15771583
{1BC02557-B78B-48CE-9D3C-488A6B7672F4} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
15781584
{8E2AA85E-C351-47B4-AF91-58557FAD5840} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
1585+
{7425E5B2-BC47-4521-AC40-B8CECA329E08} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
15791586
EndGlobalSection
15801587
GlobalSection(ExtensibilityGlobals) = postSolution
15811588
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}

src/Aspire.Hosting.PostgreSQL/Aspire.Hosting.PostgreSQL.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
</ItemGroup>
2323

2424
<ItemGroup>
25-
<InternalsVisibleTo Include="Aspire.Hosting.Tests" />
25+
<InternalsVisibleTo Include="Aspire.Hosting.PostgreSQL.Tests" />
2626
</ItemGroup>
2727

2828
</Project>

src/Shared/SecretsStore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal;
1212
/// <summary>
1313
/// Adapted from dotnet user-secrets at https://github.com/dotnet/aspnetcore/blob/482730a4c773ee4b3ae9525186d10999c89b556d/src/Tools/dotnet-user-secrets/src/Internal/SecretsStore.cs
1414
/// </summary>
15-
internal class SecretsStore
15+
internal sealed class SecretsStore
1616
{
1717
private readonly string _secretsFilePath;
1818
private readonly Dictionary<string, string?> _secrets;

tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs renamed to tests/Aspire.Hosting.PostgreSQL.Tests/AddPostgresTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
using System.Net.Sockets;
55
using System.Text.Json;
6+
using Aspire.Hosting.ApplicationModel;
67
using Aspire.Hosting.Postgres;
78
using Aspire.Hosting.Tests.Utils;
89
using Aspire.Hosting.Utils;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Xunit;
1112

12-
namespace Aspire.Hosting.Tests.Postgres;
13+
namespace Aspire.Hosting.PostgreSQL.Tests;
1314

1415
public class AddPostgresTests
1516
{
@@ -20,7 +21,7 @@ public void AddPostgresAddsGeneratedPasswordParameterWithUserSecretsParameterDef
2021

2122
var pg = appBuilder.AddPostgres("pg");
2223

23-
Assert.IsType<UserSecretsParameterDefault>(pg.Resource.PasswordParameter.Default);
24+
Assert.Equal(nameof(UserSecretsParameterDefault), pg.Resource.PasswordParameter.Default?.GetType().Name);
2425
}
2526

2627
[Fact]
@@ -30,7 +31,7 @@ public void AddPostgresDoesNotAddGeneratedPasswordParameterWithUserSecretsParame
3031

3132
var pg = appBuilder.AddPostgres("pg");
3233

33-
Assert.IsNotType<UserSecretsParameterDefault>(pg.Resource.PasswordParameter.Default);
34+
Assert.Equal(nameof(GenerateParameterDefault), pg.Resource.PasswordParameter.Default?.GetType().Name);
3435
}
3536

3637
[Fact]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(NetCurrent)</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\src\Aspire.Hosting.AppHost\Aspire.Hosting.AppHost.csproj" />
9+
<ProjectReference Include="..\..\src\Aspire.Hosting.PostgreSQL\Aspire.Hosting.PostgreSQL.csproj" />
10+
<ProjectReference Include="..\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<Compile Include="$(SharedDir)SecretsStore.cs" Link="Utils\SecretsStore.cs" />
15+
<Compile Include="$(RepoRoot)src\Aspire.Hosting\ApplicationModel\UserSecretsParameterDefault.cs" Link="Utils\UserSecretsParameterDefault.cs" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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+
using Aspire.Components.Common.Tests;
5+
using Aspire.Hosting.Utils;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Hosting;
9+
using Microsoft.Extensions.Logging;
10+
using Npgsql;
11+
using Polly;
12+
using Xunit;
13+
using Xunit.Abstractions;
14+
15+
namespace Aspire.Hosting.PostgreSQL.Tests;
16+
17+
public class PostgresFunctionalTests(ITestOutputHelper testOutputHelper)
18+
{
19+
[Fact]
20+
[RequiresDocker]
21+
public async Task VerifyPostgresResource()
22+
{
23+
var builder = CreateDistributedApplicationBuilder();
24+
25+
var postgresDbName = "db1";
26+
27+
var postgres = builder.AddPostgres("pg").WithEnvironment("POSTGRES_DB", postgresDbName);
28+
var db = postgres.AddDatabase(postgresDbName);
29+
30+
using var app = builder.Build();
31+
32+
await app.StartAsync();
33+
34+
var hb = Host.CreateApplicationBuilder();
35+
36+
hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
37+
{
38+
[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default)
39+
});
40+
41+
hb.AddNpgsqlDataSource(db.Resource.Name);
42+
43+
using var host = hb.Build();
44+
45+
await host.StartAsync();
46+
47+
var pipeline = new ResiliencePipelineBuilder()
48+
.AddRetry(new() { MaxRetryAttempts = 10, Delay = TimeSpan.FromSeconds(1), ShouldHandle = new PredicateBuilder().Handle<NpgsqlException>() })
49+
.AddTimeout(TimeSpan.FromSeconds(5))
50+
.Build();
51+
52+
await pipeline.ExecuteAsync(
53+
async token =>
54+
{
55+
using var connection = host.Services.GetRequiredService<NpgsqlConnection>();
56+
await connection.OpenAsync(token);
57+
58+
var command = connection.CreateCommand();
59+
command.CommandText = $"SELECT 1";
60+
var results = await command.ExecuteReaderAsync(token);
61+
62+
Assert.True(results.HasRows);
63+
});
64+
}
65+
66+
[Theory]
67+
[InlineData(true)]
68+
[InlineData(false)]
69+
[RequiresDocker]
70+
public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume)
71+
{
72+
var postgresDbName = "tempdb";
73+
74+
string? volumeName = null;
75+
string? bindMountPath = null;
76+
77+
var pipeline = new ResiliencePipelineBuilder()
78+
.AddRetry(new() { MaxRetryAttempts = 10, Delay = TimeSpan.FromSeconds(1), ShouldHandle = new PredicateBuilder().Handle<NpgsqlException>() })
79+
.AddTimeout(TimeSpan.FromSeconds(5))
80+
.Build();
81+
82+
try
83+
{
84+
var builder1 = CreateDistributedApplicationBuilder();
85+
86+
var username = "postgres";
87+
var password = "p@ssw0rd1";
88+
89+
var usernameParameter = builder1.AddParameter("user");
90+
var passwordParameter = builder1.AddParameter("pwd");
91+
builder1.Configuration["Parameters:user"] = username;
92+
builder1.Configuration["Parameters:pwd"] = password;
93+
var postgres1 = builder1.AddPostgres("pg", usernameParameter, passwordParameter).WithEnvironment("POSTGRES_DB", postgresDbName);
94+
95+
var db1 = postgres1.AddDatabase(postgresDbName);
96+
97+
if (useVolume)
98+
{
99+
// Use a deterministic volume name to prevent them from exhausting the machines if deletion fails
100+
volumeName = VolumeNameGenerator.CreateVolumeName(postgres1, nameof(WithDataShouldPersistStateBetweenUsages));
101+
102+
// If the volume already exists (because of a crashing previous run), try to delete it
103+
DockerUtils.AttemptDeleteDockerVolume(volumeName);
104+
postgres1.WithDataVolume(volumeName);
105+
}
106+
else
107+
{
108+
bindMountPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
109+
postgres1.WithDataBindMount(bindMountPath);
110+
}
111+
112+
using (var app = builder1.Build())
113+
{
114+
await app.StartAsync();
115+
116+
try
117+
{
118+
var hb = Host.CreateApplicationBuilder();
119+
120+
hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
121+
{
122+
[$"ConnectionStrings:{db1.Resource.Name}"] = await db1.Resource.ConnectionStringExpression.GetValueAsync(default)
123+
});
124+
125+
hb.AddNpgsqlDataSource(db1.Resource.Name);
126+
127+
using (var host = hb.Build())
128+
{
129+
await host.StartAsync();
130+
131+
await pipeline.ExecuteAsync(
132+
async token =>
133+
{
134+
using var connection = host.Services.GetRequiredService<NpgsqlConnection>();
135+
await connection.OpenAsync(token);
136+
137+
var command = connection.CreateCommand();
138+
command.CommandText = $"CREATE TABLE cars (brand VARCHAR(255)); INSERT INTO cars (brand) VALUES ('BatMobile'); SELECT * FROM cars;";
139+
var results = await command.ExecuteReaderAsync(token);
140+
141+
Assert.True(results.HasRows);
142+
});
143+
}
144+
}
145+
finally
146+
{
147+
// Stops the container, or the Volume/mount would still be in use
148+
await app.StopAsync();
149+
}
150+
}
151+
152+
var builder2 = CreateDistributedApplicationBuilder();
153+
usernameParameter = builder2.AddParameter("user");
154+
passwordParameter = builder2.AddParameter("pwd");
155+
builder2.Configuration["Parameters:user"] = username;
156+
builder2.Configuration["Parameters:pwd"] = password;
157+
158+
var postgres2 = builder2.AddPostgres("pg", usernameParameter, passwordParameter);
159+
var db2 = postgres2.AddDatabase(postgresDbName);
160+
161+
if (useVolume)
162+
{
163+
postgres2.WithDataVolume(volumeName);
164+
}
165+
else
166+
{
167+
postgres2.WithDataBindMount(bindMountPath!);
168+
}
169+
170+
using (var app = builder2.Build())
171+
{
172+
await app.StartAsync();
173+
try
174+
{
175+
var hb = Host.CreateApplicationBuilder();
176+
177+
hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
178+
{
179+
[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(default)
180+
});
181+
182+
hb.AddNpgsqlDataSource(db2.Resource.Name);
183+
184+
using (var host = hb.Build())
185+
{
186+
await host.StartAsync();
187+
188+
await pipeline.ExecuteAsync(
189+
async token =>
190+
{
191+
using var connection = host.Services.GetRequiredService<NpgsqlConnection>();
192+
await connection.OpenAsync(token);
193+
194+
var command = connection.CreateCommand();
195+
command.CommandText = $"SELECT * FROM cars;";
196+
var results = await command.ExecuteReaderAsync(token);
197+
198+
Assert.True(results.HasRows);
199+
});
200+
}
201+
202+
}
203+
finally
204+
{
205+
// Stops the container, or the Volume/mount would still be in use
206+
await app.StopAsync();
207+
}
208+
}
209+
210+
}
211+
finally
212+
{
213+
if (volumeName is not null)
214+
{
215+
DockerUtils.AttemptDeleteDockerVolume(volumeName);
216+
}
217+
218+
if (bindMountPath is not null)
219+
{
220+
try
221+
{
222+
File.Delete(bindMountPath);
223+
}
224+
catch
225+
{
226+
// Don't fail test if we can't clean the temporary folder
227+
}
228+
}
229+
}
230+
}
231+
232+
private TestDistributedApplicationBuilder CreateDistributedApplicationBuilder()
233+
{
234+
var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry();
235+
builder.Services.AddXunitLogging(testOutputHelper);
236+
return builder;
237+
}
238+
}

tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@
3030
<ProjectReference Include="..\..\src\Aspire.Hosting.Azure.AppConfiguration\Aspire.Hosting.Azure.AppConfiguration.csproj" IsAspireProjectResource="false" />
3131
<ProjectReference Include="..\..\src\Aspire.Hosting.Dapr\Aspire.Hosting.Dapr.csproj" />
3232
<ProjectReference Include="..\..\src\Aspire.Hosting.Garnet\Aspire.Hosting.Garnet.csproj" IsAspireProjectResource="false" />
33-
<ProjectReference Include="..\..\src\Aspire.Hosting.Milvus\Aspire.Hosting.Milvus.csproj" IsAspireProjectResource="false"/>
33+
<ProjectReference Include="..\..\src\Aspire.Hosting.Milvus\Aspire.Hosting.Milvus.csproj" IsAspireProjectResource="false" />
3434
<ProjectReference Include="..\..\src\Aspire.Hosting.MongoDB\Aspire.Hosting.MongoDB.csproj" IsAspireProjectResource="false" />
3535
<ProjectReference Include="..\..\src\Aspire.Hosting.MySql\Aspire.Hosting.MySql.csproj" IsAspireProjectResource="false" />
3636
<ProjectReference Include="..\..\src\Aspire.Hosting.Nats\Aspire.Hosting.Nats.csproj" IsAspireProjectResource="false" />
37-
<ProjectReference Include="..\..\src\Aspire.Hosting.PostgreSQL\Aspire.Hosting.PostgreSQL.csproj" IsAspireProjectResource="false" />
3837
<ProjectReference Include="..\..\src\Aspire.Hosting.Qdrant\Aspire.Hosting.Qdrant.csproj" IsAspireProjectResource="false" />
3938
<ProjectReference Include="..\..\src\Aspire.Hosting.Testing\Aspire.Hosting.Testing.csproj" />
4039
<ProjectReference Include="..\..\src\Aspire.Hosting.Python\Aspire.Hosting.Python.csproj" IsAspireProjectResource="false" />
@@ -61,6 +60,7 @@
6160
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Milvus\MilvusContainerImageTags.cs" />
6261
<Compile Include="$(RepoRoot)src\Aspire.Hosting.MongoDB\MongoDBContainerImageTags.cs" />
6362
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Oracle\OracleContainerImageTags.cs" />
63+
<Compile Include="$(RepoRoot)src\Aspire.Hosting.PostgreSQL\PostgresContainerImageTags.cs" />
6464
<Compile Include="$(RepoRoot)src\Aspire.Hosting.RabbitMQ\RabbitMQContainerImageTags.cs" />
6565
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Redis\RedisContainerImageTags.cs" />
6666
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Qdrant\QdrantContainerImageTags.cs" />

0 commit comments

Comments
 (0)