Skip to content

Commit

Permalink
Extract Aspire.Hosting.PostgreSQL.Tests project (dotnet#4862)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Jul 15, 2024
1 parent 639d9cb commit b6cbbd8
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 7 deletions.
7 changes: 7 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{830A
EndProject
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}"
EndProject
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}"
EndProject
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}"
EndProject
Global
Expand Down Expand Up @@ -1332,6 +1334,10 @@ Global
{8E2AA85E-C351-47B4-AF91-58557FAD5840}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E2AA85E-C351-47B4-AF91-58557FAD5840}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E2AA85E-C351-47B4-AF91-58557FAD5840}.Release|Any CPU.Build.0 = Release|Any CPU
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7425E5B2-BC47-4521-AC40-B8CECA329E08}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1576,6 +1582,7 @@ Global
{830A89EC-4029-4753-B25A-068BAE37DEC7} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{1BC02557-B78B-48CE-9D3C-488A6B7672F4} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
{8E2AA85E-C351-47B4-AF91-58557FAD5840} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
{7425E5B2-BC47-4521-AC40-B8CECA329E08} = {830A89EC-4029-4753-B25A-068BAE37DEC7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Aspire.Hosting.Tests" />
<InternalsVisibleTo Include="Aspire.Hosting.PostgreSQL.Tests" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Shared/SecretsStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal;
/// <summary>
/// Adapted from dotnet user-secrets at https://github.com/dotnet/aspnetcore/blob/482730a4c773ee4b3ae9525186d10999c89b556d/src/Tools/dotnet-user-secrets/src/Internal/SecretsStore.cs
/// </summary>
internal class SecretsStore
internal sealed class SecretsStore
{
private readonly string _secretsFilePath;
private readonly Dictionary<string, string?> _secrets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

using System.Net.Sockets;
using System.Text.Json;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Postgres;
using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Aspire.Hosting.Tests.Postgres;
namespace Aspire.Hosting.PostgreSQL.Tests;

public class AddPostgresTests
{
Expand All @@ -20,7 +21,7 @@ public void AddPostgresAddsGeneratedPasswordParameterWithUserSecretsParameterDef

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

Assert.IsType<UserSecretsParameterDefault>(pg.Resource.PasswordParameter.Default);
Assert.Equal(nameof(UserSecretsParameterDefault), pg.Resource.PasswordParameter.Default?.GetType().Name);
}

[Fact]
Expand All @@ -30,7 +31,7 @@ public void AddPostgresDoesNotAddGeneratedPasswordParameterWithUserSecretsParame

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

Assert.IsNotType<UserSecretsParameterDefault>(pg.Resource.PasswordParameter.Default);
Assert.Equal(nameof(GenerateParameterDefault), pg.Resource.PasswordParameter.Default?.GetType().Name);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetCurrent)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Aspire.Hosting.AppHost\Aspire.Hosting.AppHost.csproj" />
<ProjectReference Include="..\..\src\Aspire.Hosting.PostgreSQL\Aspire.Hosting.PostgreSQL.csproj" />
<ProjectReference Include="..\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(SharedDir)SecretsStore.cs" Link="Utils\SecretsStore.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting\ApplicationModel\UserSecretsParameterDefault.cs" Link="Utils\UserSecretsParameterDefault.cs" />
</ItemGroup>

</Project>
238 changes: 238 additions & 0 deletions tests/Aspire.Hosting.PostgreSQL.Tests/PostgresFunctionalTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Components.Common.Tests;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Npgsql;
using Polly;
using Xunit;
using Xunit.Abstractions;

namespace Aspire.Hosting.PostgreSQL.Tests;

public class PostgresFunctionalTests(ITestOutputHelper testOutputHelper)
{
[Fact]
[RequiresDocker]
public async Task VerifyPostgresResource()
{
var builder = CreateDistributedApplicationBuilder();

var postgresDbName = "db1";

var postgres = builder.AddPostgres("pg").WithEnvironment("POSTGRES_DB", postgresDbName);
var db = postgres.AddDatabase(postgresDbName);

using var app = builder.Build();

await app.StartAsync();

var hb = Host.CreateApplicationBuilder();

hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default)
});

hb.AddNpgsqlDataSource(db.Resource.Name);

using var host = hb.Build();

await host.StartAsync();

var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new() { MaxRetryAttempts = 10, Delay = TimeSpan.FromSeconds(1), ShouldHandle = new PredicateBuilder().Handle<NpgsqlException>() })
.AddTimeout(TimeSpan.FromSeconds(5))
.Build();

await pipeline.ExecuteAsync(
async token =>
{
using var connection = host.Services.GetRequiredService<NpgsqlConnection>();
await connection.OpenAsync(token);

var command = connection.CreateCommand();
command.CommandText = $"SELECT 1";
var results = await command.ExecuteReaderAsync(token);

Assert.True(results.HasRows);
});
}

[Theory]
[InlineData(true)]
[InlineData(false)]
[RequiresDocker]
public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume)
{
var postgresDbName = "tempdb";

string? volumeName = null;
string? bindMountPath = null;

var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new() { MaxRetryAttempts = 10, Delay = TimeSpan.FromSeconds(1), ShouldHandle = new PredicateBuilder().Handle<NpgsqlException>() })
.AddTimeout(TimeSpan.FromSeconds(5))
.Build();

try
{
var builder1 = CreateDistributedApplicationBuilder();

var username = "postgres";
var password = "p@ssw0rd1";

var usernameParameter = builder1.AddParameter("user");
var passwordParameter = builder1.AddParameter("pwd");
builder1.Configuration["Parameters:user"] = username;
builder1.Configuration["Parameters:pwd"] = password;
var postgres1 = builder1.AddPostgres("pg", usernameParameter, passwordParameter).WithEnvironment("POSTGRES_DB", postgresDbName);

var db1 = postgres1.AddDatabase(postgresDbName);

if (useVolume)
{
// Use a deterministic volume name to prevent them from exhausting the machines if deletion fails
volumeName = VolumeNameGenerator.CreateVolumeName(postgres1, nameof(WithDataShouldPersistStateBetweenUsages));

// If the volume already exists (because of a crashing previous run), try to delete it
DockerUtils.AttemptDeleteDockerVolume(volumeName);
postgres1.WithDataVolume(volumeName);
}
else
{
bindMountPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
postgres1.WithDataBindMount(bindMountPath);
}

using (var app = builder1.Build())
{
await app.StartAsync();

try
{
var hb = Host.CreateApplicationBuilder();

hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{db1.Resource.Name}"] = await db1.Resource.ConnectionStringExpression.GetValueAsync(default)
});

hb.AddNpgsqlDataSource(db1.Resource.Name);

using (var host = hb.Build())
{
await host.StartAsync();

await pipeline.ExecuteAsync(
async token =>
{
using var connection = host.Services.GetRequiredService<NpgsqlConnection>();
await connection.OpenAsync(token);

var command = connection.CreateCommand();
command.CommandText = $"CREATE TABLE cars (brand VARCHAR(255)); INSERT INTO cars (brand) VALUES ('BatMobile'); SELECT * FROM cars;";
var results = await command.ExecuteReaderAsync(token);

Assert.True(results.HasRows);
});
}
}
finally
{
// Stops the container, or the Volume/mount would still be in use
await app.StopAsync();
}
}

var builder2 = CreateDistributedApplicationBuilder();
usernameParameter = builder2.AddParameter("user");
passwordParameter = builder2.AddParameter("pwd");
builder2.Configuration["Parameters:user"] = username;
builder2.Configuration["Parameters:pwd"] = password;

var postgres2 = builder2.AddPostgres("pg", usernameParameter, passwordParameter);
var db2 = postgres2.AddDatabase(postgresDbName);

if (useVolume)
{
postgres2.WithDataVolume(volumeName);
}
else
{
postgres2.WithDataBindMount(bindMountPath!);
}

using (var app = builder2.Build())
{
await app.StartAsync();
try
{
var hb = Host.CreateApplicationBuilder();

hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(default)
});

hb.AddNpgsqlDataSource(db2.Resource.Name);

using (var host = hb.Build())
{
await host.StartAsync();

await pipeline.ExecuteAsync(
async token =>
{
using var connection = host.Services.GetRequiredService<NpgsqlConnection>();
await connection.OpenAsync(token);

var command = connection.CreateCommand();
command.CommandText = $"SELECT * FROM cars;";
var results = await command.ExecuteReaderAsync(token);

Assert.True(results.HasRows);
});
}

}
finally
{
// Stops the container, or the Volume/mount would still be in use
await app.StopAsync();
}
}

}
finally
{
if (volumeName is not null)
{
DockerUtils.AttemptDeleteDockerVolume(volumeName);
}

if (bindMountPath is not null)
{
try
{
File.Delete(bindMountPath);
}
catch
{
// Don't fail test if we can't clean the temporary folder
}
}
}
}

private TestDistributedApplicationBuilder CreateDistributedApplicationBuilder()
{
var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry();
builder.Services.AddXunitLogging(testOutputHelper);
return builder;
}
}
4 changes: 2 additions & 2 deletions tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@
<ProjectReference Include="..\..\src\Aspire.Hosting.Azure.AppConfiguration\Aspire.Hosting.Azure.AppConfiguration.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Dapr\Aspire.Hosting.Dapr.csproj" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Garnet\Aspire.Hosting.Garnet.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Milvus\Aspire.Hosting.Milvus.csproj" IsAspireProjectResource="false"/>
<ProjectReference Include="..\..\src\Aspire.Hosting.Milvus\Aspire.Hosting.Milvus.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.MongoDB\Aspire.Hosting.MongoDB.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.MySql\Aspire.Hosting.MySql.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Nats\Aspire.Hosting.Nats.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.PostgreSQL\Aspire.Hosting.PostgreSQL.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Qdrant\Aspire.Hosting.Qdrant.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Testing\Aspire.Hosting.Testing.csproj" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Python\Aspire.Hosting.Python.csproj" IsAspireProjectResource="false" />
Expand All @@ -61,6 +60,7 @@
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Milvus\MilvusContainerImageTags.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting.MongoDB\MongoDBContainerImageTags.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Oracle\OracleContainerImageTags.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting.PostgreSQL\PostgresContainerImageTags.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting.RabbitMQ\RabbitMQContainerImageTags.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Redis\RedisContainerImageTags.cs" />
<Compile Include="$(RepoRoot)src\Aspire.Hosting.Qdrant\QdrantContainerImageTags.cs" />
Expand Down

0 comments on commit b6cbbd8

Please sign in to comment.