diff --git a/.github/actions/cosmosdb-setup/action.yml b/.github/actions/cosmosdb-setup/action.yml index 5389412..d283f95 100644 --- a/.github/actions/cosmosdb-setup/action.yml +++ b/.github/actions/cosmosdb-setup/action.yml @@ -4,11 +4,21 @@ description: "Setup Cosmos DB Emulator environment" runs: using: "composite" steps: + - name: Check for Docker Installation + shell: bash + run: | + if ! command -v docker &> /dev/null; then + echo "Docker could not be found. Install Docker before running this action." + exit 1 + fi + - name: Pull and Run Cosmos DB Emulator shell: bash run: | + set -e docker pull mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest docker run \ + --rm \ --publish 8081:8081 \ --name cosmosdb-linux-emulator \ --detach \ @@ -18,7 +28,7 @@ runs: shell: bash run: | echo "Waiting for Cosmos DB Emulator to start... (it may require a few minutes)" - while ! curl --insecure https://localhost:8081/_explorer/emulator.pem >/dev/null 2>&1; do + until curl --insecure https://localhost:8081/_explorer/emulator.pem >/dev/null 2>&1; do sleep 5 done @@ -33,4 +43,4 @@ runs: run: | sudo cp ~/emulatorcert.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates - echo "Certificate updated. Cosmos DB Emulator configured successfully!" + echo "Certificate updated. Cosmos DB Emulator configured successfully!" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e63f0..cf734f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Run build shell: pwsh diff --git a/.github/workflows/pack&push.yml b/.github/workflows/pack&push.yml index e3bb090..ae91a58 100644 --- a/.github/workflows/pack&push.yml +++ b/.github/workflows/pack&push.yml @@ -26,6 +26,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Pack & Push to NuGet shell: pwsh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4f33757..8cd4f5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - test_type: [ + testType: [ { name: "Abstractions", condition: "abstractions_changed" }, { name: "CosmosDB", condition: "cosmosdb_changed" }, { name: "Dapper", condition: "dapper_changed" }, @@ -87,12 +87,13 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Start CosmosDB Emulator - if: matrix.test_type.name == 'CosmosDB' && needs.checks_tests_need.outputs.cosmosdb_changed == 'true' + if: matrix.testType.name == 'CosmosDB' && needs.checks_tests_need.outputs.cosmosdb_changed == 'true' uses: ./.github/actions/cosmosdb-setup - - name: Run ${{ matrix.test_type.name }} tests - if: needs.checks_tests_need.outputs[matrix.test_type.condition] == 'true' + - name: Run ${{ matrix.testType.name }} tests + if: needs.checks_tests_need.outputs[matrix.testType.condition] == 'true' shell: pwsh - run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.test_type.name }} + run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.testType.name }} diff --git a/FeatureManagement.Database.slnx b/FeatureManagement.Database.slnx index 3cb6a75..11d1c45 100644 --- a/FeatureManagement.Database.slnx +++ b/FeatureManagement.Database.slnx @@ -1,45 +1,42 @@ - - - - + + + + - - + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - diff --git a/README.md b/README.md index 6dc4faf..d82432b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ It includes abstractions and default implementations to facilitate easy integrat The Wiki contains comprehensive information on the following topics: + * **[Packages](https://github.com/teociaps/FeatureManagement.Database/wiki/Packages)**: Overview of the available NuGet packages. * **[Getting Started](https://github.com/teociaps/FeatureManagement.Database/wiki/Getting-Started)**: Instructions on how to begin using the library. * **[Consumption](https://github.com/teociaps/FeatureManagement.Database/wiki/Consumption)**: Details on how to use feature flags in your application. diff --git a/build.ps1 b/build.ps1 index 959520c..69c7d64 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,11 +1,12 @@ # build.ps1 param( - [string]$Configuration = "Release" + [string]$Configuration = "Release", + [string]$SolutionFile = "FeatureManagement.Database.sln" ) # Restore dependencies Write-Host "Restoring dependencies..." -dotnet restore +dotnet restore $SolutionFile if ($LASTEXITCODE -ne 0) { Write-Error "dotnet restore failed" exit $LASTEXITCODE @@ -13,7 +14,7 @@ if ($LASTEXITCODE -ne 0) { # Build the project Write-Host "Building project..." -dotnet build --no-restore --configuration $Configuration +dotnet build $SolutionFile --no-restore --configuration $Configuration if ($LASTEXITCODE -ne 0) { Write-Error "dotnet build failed" exit $LASTEXITCODE diff --git a/build/Common.props b/build/Common.props index c63cb05..d72efa8 100644 --- a/build/Common.props +++ b/build/Common.props @@ -1,7 +1,7 @@ - 12.0 + 13.0 enable diff --git a/samples/ConsoleApp/ConsoleApp.csproj b/samples/ConsoleApp/ConsoleApp.csproj index 65e4577..3d45ff8 100644 --- a/samples/ConsoleApp/ConsoleApp.csproj +++ b/samples/ConsoleApp/ConsoleApp.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable false @@ -10,7 +10,7 @@ - + diff --git a/samples/WebApiApp/Controllers/WeatherForecastController.cs b/samples/WebApiApp/Controllers/WeatherForecastController.cs index b199196..feefa61 100644 --- a/samples/WebApiApp/Controllers/WeatherForecastController.cs +++ b/samples/WebApiApp/Controllers/WeatherForecastController.cs @@ -15,13 +15,6 @@ public class WeatherForecastController : ControllerBase "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" ]; - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - [HttpGet(Name = "GetWeatherForecast")] [FeatureGate(Features.Weather)] public IEnumerable Get() diff --git a/samples/WebApiApp/Program.cs b/samples/WebApiApp/Program.cs index 9ab4480..2cc6779 100644 --- a/samples/WebApiApp/Program.cs +++ b/samples/WebApiApp/Program.cs @@ -21,22 +21,18 @@ // Seed with feature using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); -if (!dbContext.Features.Any()) +if (!await dbContext.Features.AnyAsync()) { dbContext.Features.Add(new Feature { Name = WebApiApp.Features.Weather, Settings = [new FeatureSettings { FilterType = FeatureFilterType.Percentage, Parameters = """{ "Value": 50 }""" }] }); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync(); } -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} +app.UseSwagger(); +app.UseSwaggerUI(); app.UseHttpsRedirection(); @@ -44,4 +40,4 @@ app.MapControllers(); -app.Run(); \ No newline at end of file +await app.RunAsync(); \ No newline at end of file diff --git a/samples/WebApiApp/WebApiApp.csproj b/samples/WebApiApp/WebApiApp.csproj index 507ed43..179d52c 100644 --- a/samples/WebApiApp/WebApiApp.csproj +++ b/samples/WebApiApp/WebApiApp.csproj @@ -1,15 +1,15 @@ - net8.0 + net9.0 enable enable - + - + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index a1d917e..5d80d7e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -3,19 +3,20 @@ true - - - - - - - - + + + + + + + + + - + - - - + + + \ No newline at end of file diff --git a/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj b/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj index 1618926..a9da358 100644 --- a/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net6.0;net8.0 + netstandard2.1;net6.0;net8.0;net9.0 @@ -17,10 +17,20 @@ + + + + - + + + + + + + \ No newline at end of file diff --git a/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/CachedFeatureStore.cs b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/CachedFeatureStore.cs index 93541f2..ebab392 100644 --- a/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/CachedFeatureStore.cs +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/CachedFeatureStore.cs @@ -1,12 +1,19 @@ // Copyright (c) Matteo Ciapparelli. // Licensed under the MIT license. -using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; + +#if NET9_0_OR_GREATER +using Microsoft.Extensions.Caching.Hybrid; +#else + +using Microsoft.Extensions.Caching.Distributed; using System.Text.Json; using System.Text.Json.Serialization; +#endif + namespace FeatureManagement.Database; /// @@ -15,21 +22,31 @@ namespace FeatureManagement.Database; public class CachedFeatureStore : IFeatureStore { private readonly IFeatureStore _featureStore; +#if NET9_0_OR_GREATER + private readonly HybridCache _cache; +#else private readonly IDistributedCache _cache; - private readonly FeatureCacheOptions _cacheOptions; - private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions(JsonSerializerOptions.Default) + private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerOptions.Default) { ReferenceHandler = ReferenceHandler.IgnoreCycles }; +#endif + private readonly FeatureCacheOptions _cacheOptions; + /// /// Initializes a new instance of the class. /// /// The concrete feature store. /// The cache service. /// The cache options. - public CachedFeatureStore(IFeatureStore featureStore, IDistributedCache cache, + public CachedFeatureStore(IFeatureStore featureStore, +#if NET9_0_OR_GREATER + HybridCache cache, +#else + IDistributedCache cache, +#endif IOptions options) { _featureStore = featureStore ?? throw new ArgumentNullException(nameof(featureStore)); @@ -69,23 +86,56 @@ public async Task> GetFeaturesAsync() private async Task GetCacheAsync(string key) where TData : class { - var cachedData = await _cache.GetAsync(FeatureCacheOptions.CachePrefix + key); + var cacheKey = FeatureCacheOptions.CachePrefix + key; + +#if NET9_0_OR_GREATER + return await _cache.GetOrCreateAsync(cacheKey, static (_) => ValueTask.FromResult(default(TData))); +#else + var cachedData = await _cache.GetAsync(cacheKey); if (cachedData is null) return null; return await JsonSerializer.DeserializeAsync(new MemoryStream(cachedData), _jsonOptions); +#endif } - private async Task SetCacheAsync(string key, TData data) where TData : class + private Task SetCacheAsync(string key, TData data) where TData : class { - await _cache.SetAsync(FeatureCacheOptions.CachePrefix + key, - JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions), new DistributedCacheEntryOptions - { - AbsoluteExpiration = _cacheOptions.AbsoluteExpiration, - AbsoluteExpirationRelativeToNow = _cacheOptions.AbsoluteExpirationRelativeToNow, - SlidingExpiration = _cacheOptions.SlidingExpiration - }); + var cacheKey = FeatureCacheOptions.CachePrefix + key; + +#if NET9_0_OR_GREATER + TimeSpan? expiration = null; + if (_cacheOptions.AbsoluteExpirationRelativeToNow.HasValue) + { + expiration = _cacheOptions.AbsoluteExpirationRelativeToNow.Value; + } + else if (_cacheOptions.SlidingExpiration.HasValue) + { + // Note: HybridCache treats 'Expiration' more like an absolute TTL from creation/access. + // Using SlidingExpiration here effectively sets an absolute expiry based on this duration. + expiration = _cacheOptions.SlidingExpiration.Value; + } + else if (_cacheOptions.AbsoluteExpiration.HasValue && _cacheOptions.AbsoluteExpiration > DateTimeOffset.UtcNow) + { + expiration = _cacheOptions.AbsoluteExpiration.Value - DateTimeOffset.UtcNow; + } + + var entryOptions = new HybridCacheEntryOptions + { + Expiration = expiration > TimeSpan.Zero ? expiration : null + }; + + return _cache.SetAsync(cacheKey, data, entryOptions).AsTask(); +#else + var options = new DistributedCacheEntryOptions + { + AbsoluteExpiration = _cacheOptions.AbsoluteExpiration, + AbsoluteExpirationRelativeToNow = _cacheOptions.AbsoluteExpirationRelativeToNow, + SlidingExpiration = _cacheOptions.SlidingExpiration + }; + return _cache.SetAsync(cacheKey, JsonSerializer.SerializeToUtf8Bytes(data, _jsonOptions), options); +#endif } #endregion Private diff --git a/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs new file mode 100644 index 0000000..2fa7563 --- /dev/null +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs @@ -0,0 +1,44 @@ +// Copyright (c) Matteo Ciapparelli. +// Licensed under the MIT license. + +#if NET9_0_OR_GREATER +using Microsoft.Extensions.Caching.Hybrid; +using System.Buffers; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace FeatureManagement.Database; + +/// +/// Custom serializer and deserializer for Feature objects. +/// +public class FeatureHybridCacheSerializer : IHybridCacheSerializer +{ + private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerOptions.Default) + { + ReferenceHandler = ReferenceHandler.IgnoreCycles + }; + + /// + /// Deserializes a ReadOnlySequence of bytes to a Feature object. + /// + /// The ReadOnlySequence of bytes to deserialize. + /// The deserialized Feature object. + public Feature Deserialize(ReadOnlySequence source) + { + var reader = new Utf8JsonReader(source); + return JsonSerializer.Deserialize(ref reader, _jsonOptions); + } + + /// + /// Serializes a Feature object to an IBufferWriter of bytes. + /// + /// The Feature object to serialize. + /// The IBufferWriter to write the serialized bytes to. + public void Serialize(Feature value, IBufferWriter target) + { + using var writer = new Utf8JsonWriter(target); + JsonSerializer.Serialize(writer, value, _jsonOptions); + } +} +#endif \ No newline at end of file diff --git a/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/DecoratorServiceCollectionExtensions.cs b/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/DecoratorServiceCollectionExtensions.cs index 2b71659..4b1fdef 100644 --- a/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/DecoratorServiceCollectionExtensions.cs +++ b/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/DecoratorServiceCollectionExtensions.cs @@ -21,7 +21,7 @@ internal static IServiceCollection Decorate(this IServiceC where TDecorator : class, TService { if (services.TryGetDescriptors(typeof(TService), out var descriptors)) - return services.DecorateDescriptors(descriptors.ToArray(), x => x.Decorate(typeof(TDecorator))); + return services.DecorateDescriptors([.. descriptors], x => x.Decorate(typeof(TDecorator))); return services; } diff --git a/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs index a1911de..25e6834 100644 --- a/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/FeatureManagement.Database.Abstractions/Microsoft/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -125,7 +125,11 @@ private static void AddDatabaseFeatureDefinitionProvider(this IServiceCollection private static IServiceCollection AddDatabaseFeatureManagementCacheServices(this IServiceCollection services) { +#if NET9_0_OR_GREATER + services.AddHybridCache().AddSerializer(); +#else services.AddDistributedMemoryCache(); +#endif return services.Decorate(); } diff --git a/src/FeatureManagement.Database.CosmosDB/FeatureManagement.Database.CosmosDB.csproj b/src/FeatureManagement.Database.CosmosDB/FeatureManagement.Database.CosmosDB.csproj index cf0225a..2401bb4 100644 --- a/src/FeatureManagement.Database.CosmosDB/FeatureManagement.Database.CosmosDB.csproj +++ b/src/FeatureManagement.Database.CosmosDB/FeatureManagement.Database.CosmosDB.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net6.0;net8.0 + netstandard2.1;net6.0;net8.0;net9.0 true diff --git a/src/FeatureManagement.Database.CosmosDB/FeatureManagement/Database/CosmosDB/CosmosDBConnectionFactory.cs b/src/FeatureManagement.Database.CosmosDB/FeatureManagement/Database/CosmosDB/CosmosDBConnectionFactory.cs index cc46617..0863344 100644 --- a/src/FeatureManagement.Database.CosmosDB/FeatureManagement/Database/CosmosDB/CosmosDBConnectionFactory.cs +++ b/src/FeatureManagement.Database.CosmosDB/FeatureManagement/Database/CosmosDB/CosmosDBConnectionFactory.cs @@ -9,9 +9,10 @@ namespace FeatureManagement.Database.CosmosDB; /// /// Default implementation of . /// -public class CosmosDBConnectionFactory : ICosmosDBConnectionFactory +public class CosmosDBConnectionFactory : ICosmosDBConnectionFactory, IDisposable { private readonly CosmosClient _client; + private bool _disposedValue; private readonly Container _featuresContainer; private readonly Container _featureSettingsContainer; @@ -59,4 +60,28 @@ public virtual Container GetFeatureSettingsContainer() { return _useSeparateContainers ? _featureSettingsContainer : _featuresContainer; } + + /// + /// Releases the unmanaged resources used by the CosmosDBConnectionFactory and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _client?.Dispose(); + } + + _disposedValue = true; + } + } + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(disposing: true); + } } \ No newline at end of file diff --git a/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj b/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj index d747328..c625c57 100644 --- a/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj +++ b/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net6.0;net8.0 + netstandard2.1;net6.0;net8.0;net9.0 @@ -16,10 +16,13 @@ - + + + + diff --git a/src/FeatureManagement.Database.EntityFrameworkCore.MySql/FeatureManagement.Database.EntityFrameworkCore.MySql.csproj b/src/FeatureManagement.Database.EntityFrameworkCore.MySql/FeatureManagement.Database.EntityFrameworkCore.MySql.csproj index 8f6f692..4abf3e0 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.MySql/FeatureManagement.Database.EntityFrameworkCore.MySql.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.MySql/FeatureManagement.Database.EntityFrameworkCore.MySql.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net6.0;net8.0;net9.0 @@ -16,10 +16,14 @@ - + + + + + diff --git a/src/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.csproj b/src/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.csproj index 1ced4ed..699ea9e 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net6.0;net8.0;net9.0 @@ -16,10 +16,14 @@ - + + + + + diff --git a/src/FeatureManagement.Database.EntityFrameworkCore.SqlServer/FeatureManagement.Database.EntityFrameworkCore.SqlServer.csproj b/src/FeatureManagement.Database.EntityFrameworkCore.SqlServer/FeatureManagement.Database.EntityFrameworkCore.SqlServer.csproj index 6a2fbe9..f82def1 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.SqlServer/FeatureManagement.Database.EntityFrameworkCore.SqlServer.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.SqlServer/FeatureManagement.Database.EntityFrameworkCore.SqlServer.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net6.0;net8.0;net9.0 @@ -16,10 +16,14 @@ - + + + + + diff --git a/src/FeatureManagement.Database.EntityFrameworkCore.Sqlite/FeatureManagement.Database.EntityFrameworkCore.Sqlite.csproj b/src/FeatureManagement.Database.EntityFrameworkCore.Sqlite/FeatureManagement.Database.EntityFrameworkCore.Sqlite.csproj index dac732b..fb80d92 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.Sqlite/FeatureManagement.Database.EntityFrameworkCore.Sqlite.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.Sqlite/FeatureManagement.Database.EntityFrameworkCore.Sqlite.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net6.0;net8.0;net9.0 @@ -16,10 +16,14 @@ - + + + + + diff --git a/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj b/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj index 0c31d5d..df07330 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net6.0;net8.0;net9.0 @@ -16,10 +16,14 @@ - + + + + + diff --git a/src/FeatureManagement.Database.MongoDB/FeatureManagement.Database.MongoDB.csproj b/src/FeatureManagement.Database.MongoDB/FeatureManagement.Database.MongoDB.csproj index 16601c6..f30cf71 100644 --- a/src/FeatureManagement.Database.MongoDB/FeatureManagement.Database.MongoDB.csproj +++ b/src/FeatureManagement.Database.MongoDB/FeatureManagement.Database.MongoDB.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net6.0;net8.0 + netstandard2.1;net6.0;net8.0;net9.0 diff --git a/src/FeatureManagement.Database.NHibernate/FeatureManagement.Database.NHibernate.csproj b/src/FeatureManagement.Database.NHibernate/FeatureManagement.Database.NHibernate.csproj index 6dbb9b9..045780c 100644 --- a/src/FeatureManagement.Database.NHibernate/FeatureManagement.Database.NHibernate.csproj +++ b/src/FeatureManagement.Database.NHibernate/FeatureManagement.Database.NHibernate.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net6.0;net8.0 + netstandard2.1;net6.0;net8.0;net9.0 diff --git a/test.ps1 b/test.ps1 index beb510a..17e771e 100644 --- a/test.ps1 +++ b/test.ps1 @@ -5,7 +5,7 @@ param( ) # Get all test project files -$testProjects = Get-ChildItem -Path "tests" -Recurse -Filter "*.csproj" +$testProjects = Get-ChildItem -Path "tests" -Recurse -Filter "*.Tests.csproj" # Filter test projects based on the $TestType parameter, except for "All" if ($TestType -ne "All") { diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 232068e..a4518a2 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -1,6 +1,6 @@  - + all runtime; build; native; contentfiles; analyzers diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index dfa4664..4d45470 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -3,24 +3,25 @@ true - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - + \ No newline at end of file diff --git a/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj b/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj index 496d23f..c72cb74 100644 --- a/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj +++ b/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj @@ -3,14 +3,13 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true - @@ -20,6 +19,22 @@ + + + + + + + + + + + + + + + + diff --git a/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement/Database/Abstractions/Tests/CachedFeatureStoreTests.cs b/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement/Database/Abstractions/Tests/CachedFeatureStoreTests.cs index 4b2a846..30e3d19 100644 --- a/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement/Database/Abstractions/Tests/CachedFeatureStoreTests.cs +++ b/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement/Database/Abstractions/Tests/CachedFeatureStoreTests.cs @@ -1,12 +1,19 @@ // Copyright (c) Matteo Ciapparelli. // Licensed under the MIT license. -using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System.Text.Json; using static FeatureManagement.Database.Abstractions.Features; +#if NET9_0_OR_GREATER +using Microsoft.Extensions.Caching.Hybrid; +#else + +using Microsoft.Extensions.Caching.Distributed; +using System.Text.Json; + +#endif + namespace FeatureManagement.Database.Abstractions.Tests; public class CachedFeatureStoreTests @@ -22,11 +29,19 @@ public async Task GetFeatureFromCachedStore() var serviceProvider = serviceCollection.BuildServiceProvider(); var cachedFeatureStore = serviceProvider.GetRequiredService(); +#if NET9_0_OR_GREATER + var cache = serviceProvider.GetRequiredService(); +#else var cache = serviceProvider.GetRequiredService(); +#endif // Act var feature = await cachedFeatureStore.GetFeatureAsync(FirstFeature); +#if NET9_0_OR_GREATER + var cachedFeature = await cache.GetOrCreateAsync(FeatureCacheOptions.CachePrefix + FirstFeature, static (_) => ValueTask.FromResult(default(Feature))); +#else var cachedFeature = JsonSerializer.Deserialize(await cache.GetAsync(FeatureCacheOptions.CachePrefix + FirstFeature)); +#endif // Assert Assert.True(feature is not null); @@ -35,7 +50,7 @@ public async Task GetFeatureFromCachedStore() Assert.Equal(FirstFeature, feature.Name); Assert.Equal(feature.Name, cachedFeature.Name); - Assert.True(feature.Settings.Any()); + Assert.True(feature.Settings.Count > 0); Assert.Equivalent(feature.Settings, cachedFeature.Settings); Assert.Equal(FeatureFilterType.TimeWindow, feature.Settings.First().FilterType); @@ -53,12 +68,20 @@ public async Task GetAllFeaturesFromCachedStore() var serviceProvider = serviceCollection.BuildServiceProvider(); var cachedFeatureStore = serviceProvider.GetRequiredService(); +#if NET9_0_OR_GREATER + var cache = serviceProvider.GetRequiredService(); +#else var cache = serviceProvider.GetRequiredService(); +#endif var cacheOptions = serviceProvider.GetRequiredService>().Value; // Act var features = await cachedFeatureStore.GetFeaturesAsync(); +#if NET9_0_OR_GREATER + var cachedFeatures = await cache.GetOrCreateAsync(FeatureCacheOptions.CachePrefix + cacheOptions.KeyNames.AllFeatures, static (_) => ValueTask.FromResult(default(IReadOnlyCollection))); +#else var cachedFeatures = JsonSerializer.Deserialize>(await cache.GetAsync(FeatureCacheOptions.CachePrefix + cacheOptions.KeyNames.AllFeatures)); +#endif // Assert Assert.True(features is not null); @@ -79,7 +102,7 @@ public async Task GetAllFeaturesFromCachedStore() Assert.Equal(feature.Name, cachedFeature.Name); - Assert.True(feature.Settings.Any()); + Assert.True(feature.Settings.Count > 0); Assert.Equivalent(feature.Settings, cachedFeature.Settings); Assert.Equal(FeatureFilterType.TimeWindow, feature.Settings.First().FilterType); @@ -98,19 +121,26 @@ public async Task GetFeatureFromCacheMustBeNullWhenCacheExpires() var serviceProvider = serviceCollection.BuildServiceProvider(); var cachedFeatureStore = serviceProvider.GetRequiredService(); +#if NET9_0_OR_GREATER + var cache = serviceProvider.GetRequiredService(); +#else var cache = serviceProvider.GetRequiredService(); +#endif // Act var feature = await cachedFeatureStore.GetFeatureAsync(FirstFeature); await Task.Delay(1200); +#if NET9_0_OR_GREATER + var featureCache = await cache.GetOrCreateAsync(FirstFeature, static (_) => ValueTask.FromResult(default(Feature))); +#else var featureCache = await cache.GetAsync(FirstFeature); +#endif // Assert Assert.True(feature is not null); Assert.True(featureCache is null); } - - + [Fact] public async Task GetAllFeaturesFromCachedStoreWhenFeaturesHaveCircularReference() { @@ -122,11 +152,19 @@ public async Task GetAllFeaturesFromCachedStoreWhenFeaturesHaveCircularReference var serviceProvider = serviceCollection.BuildServiceProvider(); var cachedFeatureStore = serviceProvider.GetRequiredService(); +#if NET9_0_OR_GREATER + var cache = serviceProvider.GetRequiredService(); +#else var cache = serviceProvider.GetRequiredService(); +#endif // Act var feature = await cachedFeatureStore.GetFeatureAsync(FirstFeature); +#if NET9_0_OR_GREATER + var cachedFeature = await cache.GetOrCreateAsync(FeatureCacheOptions.CachePrefix + FirstFeature, static (_) => ValueTask.FromResult(default(Feature))); +#else var cachedFeature = JsonSerializer.Deserialize(await cache.GetAsync(FeatureCacheOptions.CachePrefix + FirstFeature)); +#endif // Assert Assert.True(feature is not null); @@ -135,6 +173,6 @@ public async Task GetAllFeaturesFromCachedStoreWhenFeaturesHaveCircularReference Assert.Equal(FirstFeature, feature.Name); Assert.Equal(feature.Name, cachedFeature.Name); - Assert.True(feature.Settings.Any()); + Assert.True(feature.Settings.Count > 0); } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj index 5918c37..ac99324 100644 --- a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj +++ b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj @@ -3,14 +3,13 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true - @@ -19,18 +18,25 @@ - - + + + - + + + + + + + diff --git a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/IntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/IntegrationTestWebAppFactory.cs index 5df6b6e..0210efd 100644 --- a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/IntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/IntegrationTestWebAppFactory.cs @@ -13,16 +13,24 @@ namespace FeatureManagement.Database.CosmosDB.Tests; public sealed class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { - private readonly CosmosDbContainer _cosmosDbContainer = new CosmosDbBuilder() + private readonly CosmosDbContainer _cosmosDbContainer; + + internal string ConnectionString => _cosmosDbContainer.GetConnectionString(); + + public IntegrationTestWebAppFactory() + { + var containerName = GetUniqueContainerName("cosmosdb-test-container"); + + _cosmosDbContainer = new CosmosDbBuilder() + .WithName(containerName) .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest") - .WithExposedPort(8081) - .WithPortBinding(8081, true) + .WithExposedPort(CosmosDbBuilder.CosmosDbPort) + .WithPortBinding(CosmosDbBuilder.CosmosDbPort, assignRandomHostPort: true) .WithEnvironment("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", "2") .WithEnvironment("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false") - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8081)) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(CosmosDbBuilder.CosmosDbPort)) .Build(); - - internal string ConnectionString => _cosmosDbContainer.GetConnectionString(); + } public async Task InitializeAsync() { @@ -69,4 +77,9 @@ private void ConfigureServices(IServiceCollection services) }; }); } + + private static string GetUniqueContainerName(string baseName) + { + return $"{baseName}-{Guid.NewGuid().ToString("N")[..8]}"; + } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/Seed.cs b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/Seed.cs index 4f91343..43bcdf7 100644 --- a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/Seed.cs +++ b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement/Database/CosmosDB/Tests/Seed.cs @@ -21,53 +21,68 @@ internal static async Task SeedDataAsync(CosmosDBOptions cosmosDBOptions, ICosmo var featuresContainer = cosmosDBConnectionFactory.GetFeaturesContainer(); var featureSettingsContainer = cosmosDBConnectionFactory.GetFeatureSettingsContainer(); + // IDs and Partition Keys + const string Feature1Id = "7c81e846-dc77-4aff-bf03-8dd8bb2d3194"; + const string Feature2Id = "d3c82992-2f12-4008-9376-da37695a2747"; + const string FeatureSettingsId = "672dc1bd-9c5b-44ce-8461-234b262a8395"; + var partitionKey1 = new PartitionKey(FirstFeature); + var partitionKey2 = new PartitionKey(SecondFeature); + var featureSettingsPartitionKey = new PartitionKey(Feature1Id); + try { - await featuresContainer.DeleteItemAsync("7c81e846-dc77-4aff-bf03-8dd8bb2d3194", new PartitionKey(FirstFeature)); - await featuresContainer.DeleteItemAsync("d3c82992-2f12-4008-9376-da37695a2747", new PartitionKey(SecondFeature)); + // Delete existing items if they exist + await featuresContainer.DeleteItemAsync(Feature1Id, partitionKey1); + await featuresContainer.DeleteItemAsync(Feature2Id, partitionKey2); if (cosmosDBOptions.UseSeparateContainers) - await featureSettingsContainer.DeleteItemAsync("672dc1bd-9c5b-44ce-8461-234b262a8395", new PartitionKey("7c81e846-dc77-4aff-bf03-8dd8bb2d3194")); + await featureSettingsContainer.DeleteItemAsync(FeatureSettingsId, featureSettingsPartitionKey); } catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { - // Nothing: I can't delete something that doesn't exist + // Item not found, nothing to delete } - List features = - [ - new() - { - Id = Guid.Parse("7C81E846-DC77-4AFF-BF03-8DD8BB2D3194"), + // Define new features and settings + var features = new List + { + new() { + Id = Guid.Parse(Feature1Id), Name = FirstFeature, RequirementType = Microsoft.FeatureManagement.RequirementType.All, }, - new() - { - Id = Guid.Parse("D3C82992-2F12-4008-9376-DA37695A2747"), + new() { + Id = Guid.Parse(Feature2Id), Name = SecondFeature, RequirementType = Microsoft.FeatureManagement.RequirementType.All, } - ]; + }; - List settings = - [ - new() - { - Id = Guid.Parse("672DC1BD-9C5B-44CE-8461-234B262A8395"), + var settings = new List + { + new() { + Id = Guid.Parse(FeatureSettingsId), FeatureId = features[0].Id, FilterType = FeatureFilterType.TimeWindow, Parameters = """{"Start": "Mon, 01 May 2023 13:59:59 GMT", "End": "Sat, 01 July 2023 00:00:00 GMT"}""" } - ]; + }; if (!cosmosDBOptions.UseSeparateContainers) features[0].Settings = settings; - await featuresContainer.CreateItemAsync(new { id = features[0].Id.ToString(), features[0].Name, features[0].RequirementType, features[0].Settings }, new PartitionKey(features[0].Name)); - await featuresContainer.CreateItemAsync(new { id = features[1].Id.ToString(), features[1].Name, features[1].RequirementType }, new PartitionKey(features[1].Name)); + try + { + // Insert new items + await featuresContainer.CreateItemAsync(new { id = features[0].Id.ToString(), features[0].Name, features[0].RequirementType, features[0].Settings }, partitionKey1); + await featuresContainer.CreateItemAsync(new { id = features[1].Id.ToString(), features[1].Name, features[1].RequirementType }, partitionKey2); - if (cosmosDBOptions.UseSeparateContainers) - await featureSettingsContainer.CreateItemAsync(new { id = settings[0].Id.ToString(), settings[0].FeatureId, settings[0].FilterType, settings[0].CustomFilterTypeName, settings[0].Parameters }, new PartitionKey(settings[0].FeatureId.ToString())); + if (cosmosDBOptions.UseSeparateContainers) + await featureSettingsContainer.CreateItemAsync(new { id = settings[0].Id.ToString(), settings[0].FeatureId, settings[0].FilterType, settings[0].Parameters }, featureSettingsPartitionKey); + } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Conflict) + { + // Do nothing when the item already exists + } } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj b/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj index 4562117..2934407 100644 --- a/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj +++ b/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj @@ -3,14 +3,13 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true - @@ -19,8 +18,24 @@ + + + + + + + + + + + + + + + + diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests.csproj b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests.csproj index df3644b..ca600a7 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests.csproj @@ -3,7 +3,7 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlFeatureStoreWithCacheTests.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlFeatureStoreWithCacheTests.cs index 15687bd..eb2c7cb 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlFeatureStoreWithCacheTests.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlFeatureStoreWithCacheTests.cs @@ -3,7 +3,7 @@ using FeatureManagement.Database.EntityFrameworkCore.Tests; -namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; +namespace FeatureManagement.Database.EntityFrameworkCore.MySql.Tests; public sealed class MySqlFeatureStoreWithCacheTests(MySqlWithCacheIntegrationTestWebAppFactory factory) : EFCoreFeatureStoreTests(factory) diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlIntegrationTestWebAppFactory.cs index b1ddb9b..a7685f7 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlIntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlIntegrationTestWebAppFactory.cs @@ -11,28 +11,28 @@ namespace FeatureManagement.Database.EntityFrameworkCore.MySql.Tests; public sealed class MySqlIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private readonly MySqlContainer _mySqlContainer = new MySqlBuilder() - .WithName("mysql-test-container") - .WithImage("mysql:latest") - .WithDatabase("TestDb") - .WithUsername("root") - .WithPassword("mysqlpassword") - .WithPrivileged(true) - .WithPortBinding(MySqlBuilder.MySqlPort) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MySqlBuilder.MySqlPort)) - .Build(); - public MySqlIntegrationTestWebAppFactory() { - _container = _mySqlContainer; + var containerName = GetUniqueContainerName("mysql-test-container"); + + _container = new MySqlBuilder() + .WithName(containerName) + .WithImage("mysql:latest") + .WithDatabase("TestDb") + .WithUsername("root") + .WithPassword("mysqlpassword") + .WithPrivileged(true) + .WithPortBinding(MySqlBuilder.MySqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MySqlBuilder.MySqlPort)) + .Build(); } protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services.AddDatabaseFeatureManagement() - .UseMySql(_mySqlContainer.GetConnectionString(), + .UseMySql(_container.GetConnectionString(), options => options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)); } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlWithCacheIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlWithCacheIntegrationTestWebAppFactory.cs index c4f8ca3..73c329d 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlWithCacheIntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlWithCacheIntegrationTestWebAppFactory.cs @@ -7,32 +7,32 @@ using System.Reflection; using Testcontainers.MySql; -namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; +namespace FeatureManagement.Database.EntityFrameworkCore.MySql.Tests; public sealed class MySqlWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private readonly MySqlContainer _mySqlContainer = new MySqlBuilder() - .WithName("mysql-test-container-cache") - .WithImage("mysql:latest") - .WithDatabase("TestDb") - .WithUsername("root") - .WithPassword("mysqlpassword") - .WithPrivileged(true) - .WithPortBinding(MySqlBuilder.MySqlPort, assignRandomHostPort: true) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MySqlBuilder.MySqlPort)) - .Build(); - public MySqlWithCacheIntegrationTestWebAppFactory() { - _container = _mySqlContainer; + var containerName = GetUniqueContainerName("mysql-test-container-cache"); + + _container = new MySqlBuilder() + .WithName(containerName) + .WithImage("mysql:latest") + .WithDatabase("TestDb") + .WithUsername("root") + .WithPassword("mysqlpassword") + .WithPrivileged(true) + .WithPortBinding(MySqlBuilder.MySqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MySqlBuilder.MySqlPort)) + .Build(); } protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services.AddDatabaseFeatureManagement() - .UseMySql(_mySqlContainer.GetConnectionString(), + .UseMySql(_container.GetConnectionString(), options => options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)) .WithCacheService(); } diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests.csproj b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests.csproj index 7306a7d..bcf7e6f 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests.csproj @@ -3,7 +3,7 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlFeatureStoreWithCacheTests.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlFeatureStoreWithCacheTests.cs index e2b45c3..6911a01 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlFeatureStoreWithCacheTests.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlFeatureStoreWithCacheTests.cs @@ -3,7 +3,7 @@ using FeatureManagement.Database.EntityFrameworkCore.Tests; -namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; +namespace FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests; public sealed class PostgreSqlFeatureStoreWithCacheTests(PostgreSqlWithCacheIntegrationTestWebAppFactory factory) : EFCoreFeatureStoreTests(factory) diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlIntegrationTestWebAppFactory.cs index f98c6f1..11bbb89 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlIntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlIntegrationTestWebAppFactory.cs @@ -11,24 +11,24 @@ namespace FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests; public sealed class PostgreSqlIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private readonly PostgreSqlContainer _postgreSqlContainer = new PostgreSqlBuilder() - .WithName("postgresql-test-container") - .WithImage("postgres:latest") - .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(PostgreSqlBuilder.PostgreSqlPort)) - .Build(); - public PostgreSqlIntegrationTestWebAppFactory() { - _container = _postgreSqlContainer; + var containerName = GetUniqueContainerName("postgresql-test-container"); + + _container = new PostgreSqlBuilder() + .WithName(containerName) + .WithImage("postgres:latest") + .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(PostgreSqlBuilder.PostgreSqlPort)) + .Build(); } protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services.AddDatabaseFeatureManagement() - .UseNpgsql(_postgreSqlContainer.GetConnectionString(), + .UseNpgsql(_container.GetConnectionString(), options => options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)); } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlWithCacheIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlWithCacheIntegrationTestWebAppFactory.cs index 21030bb..4aefe7e 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlWithCacheIntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlWithCacheIntegrationTestWebAppFactory.cs @@ -7,28 +7,28 @@ using System.Reflection; using Testcontainers.PostgreSql; -namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; +namespace FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests; public sealed class PostgreSqlWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private readonly PostgreSqlContainer _postgreSqlContainer = new PostgreSqlBuilder() - .WithName("postgresql-test-container-cache") - .WithImage("postgres:latest") - .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort, assignRandomHostPort: true) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(PostgreSqlBuilder.PostgreSqlPort)) - .Build(); - public PostgreSqlWithCacheIntegrationTestWebAppFactory() { - _container = _postgreSqlContainer; + var containerName = GetUniqueContainerName("postgresql-test-container-cache"); + + _container = new PostgreSqlBuilder() + .WithName(containerName) + .WithImage("postgres:latest") + .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(PostgreSqlBuilder.PostgreSqlPort)) + .Build(); } protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services.AddDatabaseFeatureManagement() - .UseNpgsql(_postgreSqlContainer.GetConnectionString(), + .UseNpgsql(_container.GetConnectionString(), options => options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)) .WithCacheService(); } diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests.csproj b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests.csproj index dfb7b9b..00d4a47 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests.csproj @@ -3,7 +3,7 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerIntegrationTestWebAppFactory.cs index 62dadde..adb681f 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerIntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerIntegrationTestWebAppFactory.cs @@ -11,25 +11,25 @@ namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; public sealed class SqlServerIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private readonly MsSqlContainer _sqlServerContainer = new MsSqlBuilder() - .WithName("sqlserver-test-container") - .WithImage("mcr.microsoft.com/mssql/server:2022-latest") - .WithEnvironment("ACCEPT_EULA", "Y") - .WithPortBinding(MsSqlBuilder.MsSqlPort) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) - .Build(); - public SqlServerIntegrationTestWebAppFactory() { - _container = _sqlServerContainer; + var containerName = GetUniqueContainerName("sqlserver-test-container"); + + _container = new MsSqlBuilder() + .WithName(containerName) + .WithImage("mcr.microsoft.com/mssql/server:2022-latest") + .WithEnvironment("ACCEPT_EULA", "Y") + .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) + .Build(); } protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services.AddDatabaseFeatureManagement() - .UseSqlServer(_sqlServerContainer.GetConnectionString(), + .UseSqlServer(_container.GetConnectionString(), options => options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)); } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerWithCacheIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerWithCacheIntegrationTestWebAppFactory.cs index 13f14d7..ac71142 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerWithCacheIntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerWithCacheIntegrationTestWebAppFactory.cs @@ -11,25 +11,25 @@ namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; public sealed class SqlServerWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private readonly MsSqlContainer _sqlServerContainer = new MsSqlBuilder() - .WithName("sqlserver-test-container-with-cache") - .WithImage("mcr.microsoft.com/mssql/server:2022-latest") - .WithEnvironment("ACCEPT_EULA", "Y") - .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: true) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) - .Build(); - public SqlServerWithCacheIntegrationTestWebAppFactory() { - _container = _sqlServerContainer; + var containerName = GetUniqueContainerName("sqlserver-test-container-with-cache"); + + _container = new MsSqlBuilder() + .WithName(containerName) + .WithImage("mcr.microsoft.com/mssql/server:2022-latest") + .WithEnvironment("ACCEPT_EULA", "Y") + .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) + .Build(); } protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services.AddDatabaseFeatureManagement() - .UseSqlServer(_sqlServerContainer.GetConnectionString(), + .UseSqlServer(_container.GetConnectionString(), options => options.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)) .WithCacheService(); } diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests.csproj b/tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests.csproj index 7a4fd2b..4123652 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests/FeatureManagement.Database.EntityFrameworkCore.Sqlite.Tests.csproj @@ -3,7 +3,7 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj index b9563d3..eb73d59 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj @@ -3,15 +3,13 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true - - @@ -26,13 +24,27 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + @@ -42,6 +54,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement/Database/EntityFrameworkCore/Tests/IntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement/Database/EntityFrameworkCore/Tests/IntegrationTestWebAppFactory.cs index 586d089..0c4844a 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement/Database/EntityFrameworkCore/Tests/IntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement/Database/EntityFrameworkCore/Tests/IntegrationTestWebAppFactory.cs @@ -37,6 +37,11 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) protected virtual void ConfigureServices(IServiceCollection services) { - services.RemoveAll(typeof(DbContextOptions)); + services.RemoveAll>(); + } + + protected static string GetUniqueContainerName(string baseName) + { + return $"{baseName}-{Guid.NewGuid().ToString("N")[..8]}"; } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj index b612dbf..6386ab0 100644 --- a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj +++ b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj @@ -3,14 +3,13 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true - @@ -23,12 +22,19 @@ - + + + + + + + + diff --git a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement/Database/MongoDB/Tests/IntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement/Database/MongoDB/Tests/IntegrationTestWebAppFactory.cs index 551f29f..e5b5412 100644 --- a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement/Database/MongoDB/Tests/IntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement/Database/MongoDB/Tests/IntegrationTestWebAppFactory.cs @@ -12,20 +12,26 @@ namespace FeatureManagement.Database.MongoDB.Tests; public sealed class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { - private readonly MongoDbContainer _mongoDBContainer = new MongoDbBuilder() - .WithName("mongodb-test-container") - .WithImage("mongo:latest") - .WithUsername(null) - .WithPassword(null) - .WithPortBinding(MongoDbBuilder.MongoDbPort) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MongoDbBuilder.MongoDbPort)) - .Build(); - + private readonly MongoDbContainer _mongoDBContainer; private const string _Database = "TestDb"; internal string ConnectionString => _mongoDBContainer.GetConnectionString(); + public IntegrationTestWebAppFactory() + { + var containerName = GetUniqueContainerName("mongodb-test-container"); + + _mongoDBContainer = new MongoDbBuilder() + .WithName(containerName) + .WithImage("mongo:latest") + .WithUsername(null) + .WithPassword(null) + .WithPortBinding(MongoDbBuilder.MongoDbPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MongoDbBuilder.MongoDbPort)) + .Build(); + } + public async Task InitializeAsync() { await _mongoDBContainer.StartAsync(); @@ -50,4 +56,9 @@ private void ConfigureServices(IServiceCollection services) services.AddDatabaseFeatureManagement() .UseMongoDB(ConnectionString, _Database); } + + private static string GetUniqueContainerName(string baseName) + { + return $"{baseName}-{Guid.NewGuid().ToString("N")[..8]}"; + } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj index 8654495..c6e15da 100644 --- a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj +++ b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj @@ -3,14 +3,13 @@ - net6.0;net8.0 + net6.0;net8.0;net9.0 false true - @@ -25,12 +24,19 @@ - + + + + + + + + diff --git a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/IntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/IntegrationTestWebAppFactory.cs index 5ad0b9a..b868892 100644 --- a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/IntegrationTestWebAppFactory.cs +++ b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/IntegrationTestWebAppFactory.cs @@ -12,14 +12,23 @@ namespace FeatureManagement.Database.NHibernate.Tests; public class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { - private readonly MsSqlContainer _sqlServerContainer = new MsSqlBuilder() - .WithName("sqlserver-test-container") - .WithImage("mcr.microsoft.com/mssql/server:2022-latest") - .WithEnvironment("ACCEPT_EULA", "Y") - .WithPortBinding(MsSqlBuilder.MsSqlPort) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) - .Build(); + private readonly MsSqlContainer _sqlServerContainer; + + public IntegrationTestWebAppFactory() + { + var containerName = GetUniqueContainerName("nhibernate-sqlserver-test-container"); + + _sqlServerContainer = new MsSqlBuilder() + .WithName(containerName) + .WithImage("mcr.microsoft.com/mssql/server:2022-latest") + .WithEnvironment("ACCEPT_EULA", "Y") + .WithEnvironment("SA_USERNAME", MsSqlBuilder.DefaultUsername) + .WithEnvironment("SA_PASSWORD", MsSqlBuilder.DefaultPassword) + .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: true) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) + .Build(); + } public async Task InitializeAsync() { @@ -46,4 +55,9 @@ protected virtual void ConfigureServices(IServiceCollection services) services.AddDatabaseFeatureManagement() .UseNHibernate(_sqlServerContainer.GetConnectionString(), new SqlServerConnectionFactory()); } + + private static string GetUniqueContainerName(string baseName) + { + return $"{baseName}-{Guid.NewGuid().ToString("N")[..8]}"; + } } \ No newline at end of file diff --git a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/Seed.cs b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/Seed.cs index 00323e3..c472d62 100644 --- a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/Seed.cs +++ b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/Seed.cs @@ -40,7 +40,7 @@ internal static async Task SeedData(ISessionFactory sessionFactory) }; // Add settings to the feature - features[0].Settings = [ featureSetting ]; + features[0].Settings = [featureSetting]; // Save entities to the database await session.SaveAsync(featureSetting);