From ac8ac1b71cfca094a7ac353132adc97feda45f0f Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 30 Mar 2025 01:31:09 +0100 Subject: [PATCH 01/16] Upgrade to .NET 9.0 and update package references --- build/Common.props | 2 +- samples/ConsoleApp/ConsoleApp.csproj | 4 +-- samples/WebApiApp/WebApiApp.csproj | 6 ++-- src/Directory.Packages.props | 25 ++++++++------- ...ureManagement.Database.Abstractions.csproj | 14 +++++++-- ...FeatureManagement.Database.CosmosDB.csproj | 2 +- .../FeatureManagement.Database.Dapper.csproj | 7 +++-- ....Database.EntityFrameworkCore.MySql.csproj | 9 +++++- ...base.EntityFrameworkCore.PostgreSQL.csproj | 4 +-- ...abase.EntityFrameworkCore.SqlServer.csproj | 4 +-- ...Database.EntityFrameworkCore.Sqlite.csproj | 4 +-- ...gement.Database.EntityFrameworkCore.csproj | 4 +-- .../FeatureManagement.Database.MongoDB.csproj | 2 +- ...atureManagement.Database.NHibernate.csproj | 2 +- tests/Directory.Packages.props | 31 ++++++++++--------- ...agement.Database.Abstractions.Tests.csproj | 18 +++++++++-- ...eManagement.Database.CosmosDB.Tests.csproj | 16 +++++++--- ...ureManagement.Database.Dapper.Tests.csproj | 18 +++++++++-- ...ase.EntityFrameworkCore.MySql.Tests.csproj | 14 ++++++++- ...ntityFrameworkCore.PostgreSQL.Tests.csproj | 2 +- ...EntityFrameworkCore.SqlServer.Tests.csproj | 2 +- ...se.EntityFrameworkCore.Sqlite.Tests.csproj | 2 +- ....Database.EntityFrameworkCore.Tests.csproj | 22 ++++++++++--- ...reManagement.Database.MongoDB.Tests.csproj | 12 +++++-- ...anagement.Database.NHibernate.Tests.csproj | 12 +++++-- 25 files changed, 166 insertions(+), 72 deletions(-) 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/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..ea62f69 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..cb8ddf2 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.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.Dapper/FeatureManagement.Database.Dapper.csproj b/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj index d747328..12e0a0b 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..18f8e2a 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,12 +16,19 @@ + + + + + + + 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..fa09e71 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,7 +16,7 @@ - + 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..794c462 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,7 +16,7 @@ - + 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..e041271 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,7 +16,7 @@ - + diff --git a/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj b/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj index 0c31d5d..189606d 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,7 +16,7 @@ - + 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/tests/Directory.Packages.props b/tests/Directory.Packages.props index dfa4664..a7dc0b1 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..9d92018 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,21 @@ + + + + + + + + + + + + + + + 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..68446cb 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.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj b/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj index 4562117..364bfc0 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,6 +18,21 @@ + + + + + + + + + + + + + + + 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..f82c6e9 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 @@ -13,6 +13,18 @@ + + + + + + + + + + + + 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.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.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..145f0b4 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.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj index b612dbf..8274941 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.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj index 8654495..836c953 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 @@ - + + + + + + + + From 5d377747cf6201045f56f8acda3c85c2c5f8529d Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Mon, 31 Mar 2025 00:21:09 +0200 Subject: [PATCH 02/16] Add HybridCache support for .NET 9 caching --- README.md | 5 +- src/Directory.Packages.props | 1 + ...ureManagement.Database.Abstractions.csproj | 2 +- .../Database/CachedFeatureStore.cs | 76 +++++++++++++++---- .../Database/FeatureHybridCacheSerializer.cs | 41 ++++++++++ .../DecoratorServiceCollectionExtensions.cs | 2 +- .../ServiceCollectionExtensions.cs | 4 + tests/Directory.Packages.props | 1 + ...agement.Database.Abstractions.Tests.csproj | 1 + .../Tests/CachedFeatureStoreTests.cs | 52 +++++++++++-- 10 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs diff --git a/README.md b/README.md index bd3c710..4eecfa9 100644 --- a/README.md +++ b/README.md @@ -181,13 +181,16 @@ The cache keys have a prefix (**FMDb_**) defined in the options (`FeatureCacheOp | Key | FMDb_MyFeature | FMDb_features | -Note that _"features"_ can be overridden when configuring cache. So you can have `"FMDb_your-custom-cache-key"`. +Note that _"features"_ can be overridden when configuring cache. So you can have `"FMDb_"`. See the `FeatureCacheOptions` class for more cache-related settings. > [!WARNING] > Cache does not auto-refresh when feature values update directly in the database. Handle cache invalidation appropriately. +> [!IMPORTANT] +> When using .NET 9, cache entries will automatically leverage the unified caching approach of `HybridCache`. This provides multi-level caching with in-memory and distributed cache support out of the box. The expiration settings from `FeatureCacheOptions` (like `SlidingExpiration` and `AbsoluteExpirationRelativeToNow`) are automatically applied to the cache entries. This optimization happens transparently in the `CachedFeatureStore` implementation, requiring no additional configuration from you. + ## Consumption diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index ea62f69..672201e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -10,6 +10,7 @@ + diff --git a/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj b/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj index cb8ddf2..5fcab6c 100644 --- a/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj @@ -24,6 +24,7 @@ + @@ -32,5 +33,4 @@ - \ 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..2e0dec0 --- /dev/null +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs @@ -0,0 +1,41 @@ +#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/tests/Directory.Packages.props b/tests/Directory.Packages.props index a7dc0b1..d490e38 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -9,6 +9,7 @@ + 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 9d92018..a8fbe32 100644 --- a/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj +++ b/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj @@ -22,6 +22,7 @@ + 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 From c92fed769c0bf54e0985534c6fe200f49161a819 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Mon, 31 Mar 2025 22:04:43 +0200 Subject: [PATCH 03/16] Update dotnet-version to include 9.0.x in gh workflows --- .github/workflows/build.yml | 1 + .github/workflows/pack&push.yml | 1 + .github/workflows/test.yml | 1 + 3 files changed, 3 insertions(+) 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..0e87d55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -87,6 +87,7 @@ 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' From d58378c4e80a01c8ab7263c9bdf9aa21a4ea8e93 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Mon, 31 Mar 2025 22:12:49 +0200 Subject: [PATCH 04/16] Refactor WeatherForecastController and improve async setup in sample app --- .../Controllers/WeatherForecastController.cs | 7 ------- samples/WebApiApp/Program.cs | 14 +++++--------- 2 files changed, 5 insertions(+), 16 deletions(-) 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 From d4da4de7db5cf67921c7a60ecca423fc7cd0b6a6 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Thu, 3 Apr 2025 00:19:49 +0200 Subject: [PATCH 05/16] Enhanced compatibility with package versions and fix some tests Note: `Pomelo.EntityFrameworkCore.MySql` is still in preview for .net9: `9.0.0-preview.3.efcore.9.0.0` --- src/Directory.Packages.props | 3 +-- .../FeatureManagement.Database.Abstractions.csproj | 4 ++-- .../FeatureManagement.Database.Dapper.csproj | 4 ++-- ...agement.Database.EntityFrameworkCore.MySql.csproj | 5 +---- ...nt.Database.EntityFrameworkCore.PostgreSQL.csproj | 6 +++++- ...ent.Database.EntityFrameworkCore.SqlServer.csproj | 6 +++++- ...gement.Database.EntityFrameworkCore.Sqlite.csproj | 6 +++++- ...ureManagement.Database.EntityFrameworkCore.csproj | 6 +++++- tests/Directory.Packages.props | 1 - ...tureManagement.Database.Abstractions.Tests.csproj | 2 +- .../FeatureManagement.Database.CosmosDB.Tests.csproj | 2 +- .../FeatureManagement.Database.Dapper.Tests.csproj | 2 +- ...t.Database.EntityFrameworkCore.MySql.Tests.csproj | 12 ------------ .../MySql/Tests/MySqlFeatureStoreWithCacheTests.cs | 2 +- .../MySqlWithCacheIntegrationTestWebAppFactory.cs | 2 +- .../Tests/PostgreSqlFeatureStoreWithCacheTests.cs | 2 +- ...ostgreSqlWithCacheIntegrationTestWebAppFactory.cs | 2 +- ...agement.Database.EntityFrameworkCore.Tests.csproj | 4 ++-- .../FeatureManagement.Database.MongoDB.Tests.csproj | 2 +- ...eatureManagement.Database.NHibernate.Tests.csproj | 2 +- .../NHibernate/Tests/IntegrationTestWebAppFactory.cs | 4 +++- 21 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 672201e..5d80d7e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -7,7 +7,6 @@ - @@ -17,7 +16,7 @@ - + \ 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 5fcab6c..a9da358 100644 --- a/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement.Database.Abstractions.csproj @@ -20,14 +20,14 @@ - + - + diff --git a/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj b/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj index 12e0a0b..c625c57 100644 --- a/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj +++ b/src/FeatureManagement.Database.Dapper/FeatureManagement.Database.Dapper.csproj @@ -16,11 +16,11 @@ - + - + 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 18f8e2a..4abf3e0 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.MySql/FeatureManagement.Database.EntityFrameworkCore.MySql.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.MySql/FeatureManagement.Database.EntityFrameworkCore.MySql.csproj @@ -18,17 +18,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 fa09e71..699ea9e 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.csproj @@ -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 794c462..f82def1 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.SqlServer/FeatureManagement.Database.EntityFrameworkCore.SqlServer.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.SqlServer/FeatureManagement.Database.EntityFrameworkCore.SqlServer.csproj @@ -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 e041271..fb80d92 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore.Sqlite/FeatureManagement.Database.EntityFrameworkCore.Sqlite.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore.Sqlite/FeatureManagement.Database.EntityFrameworkCore.Sqlite.csproj @@ -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 189606d..df07330 100644 --- a/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj +++ b/src/FeatureManagement.Database.EntityFrameworkCore/FeatureManagement.Database.EntityFrameworkCore.csproj @@ -16,10 +16,14 @@ - + + + + + diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index d490e38..4d45470 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -7,7 +7,6 @@ - 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 a8fbe32..c72cb74 100644 --- a/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj +++ b/tests/FeatureManagement.Database.Abstractions.Tests/FeatureManagement.Database.Abstractions.Tests.csproj @@ -27,7 +27,7 @@ - + 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 68446cb..ac99324 100644 --- a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj +++ b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj @@ -25,7 +25,7 @@ - + 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 364bfc0..04ba2b4 100644 --- a/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj +++ b/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj @@ -25,7 +25,7 @@ - + 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 f82c6e9..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 @@ -13,18 +13,6 @@ - - - - - - - - - - - - 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/MySqlWithCacheIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.MySql.Tests/FeatureManagement/Database/EntityFrameworkCore/MySql/Tests/MySqlWithCacheIntegrationTestWebAppFactory.cs index c4f8ca3..a4f188f 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,7 +7,7 @@ using System.Reflection; using Testcontainers.MySql; -namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; +namespace FeatureManagement.Database.EntityFrameworkCore.MySql.Tests; public sealed class MySqlWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { 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/PostgreSqlWithCacheIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlWithCacheIntegrationTestWebAppFactory.cs index 21030bb..abd876d 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,7 +7,7 @@ using System.Reflection; using Testcontainers.PostgreSql; -namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; +namespace FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests; public sealed class PostgreSqlWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { 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 145f0b4..eb73d59 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj @@ -43,8 +43,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + 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 8274941..6386ab0 100644 --- a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj +++ b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj @@ -29,7 +29,7 @@ - + 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 836c953..c6e15da 100644 --- a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj +++ b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj @@ -31,7 +31,7 @@ - + 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..a78190a 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 @@ -13,9 +13,11 @@ namespace FeatureManagement.Database.NHibernate.Tests; public class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { private readonly MsSqlContainer _sqlServerContainer = new MsSqlBuilder() - .WithName("sqlserver-test-container") + .WithName("nhibernate-sqlserver-test-container") .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) .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) From e096a0815d684af5698ccdd31056f5b835d847c7 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Fri, 4 Apr 2025 00:26:20 +0200 Subject: [PATCH 06/16] Add SolutionFile parameter to build.ps1 --- build.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 From 8075e9ad1ddb4ec2862d95ed910f4e892ad104cb Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 6 Apr 2025 18:17:11 +0200 Subject: [PATCH 07/16] Updated solution to include a new project for common utilities in the tests directory and improved Docker container management in IntegrationTestWebAppFactory --- FeatureManagement.Database.sln | 7 +++ FeatureManagement.Database.slnx | 62 +++++++++---------- .../Database/FeatureHybridCacheSerializer.cs | 3 + test.ps1 | 2 +- tests/Directory.Build.props | 2 +- tests/Directory.Packages.props | 1 + .../DockerContainerHelper.cs | 25 ++++++++ ...anagement.Database.Common.Utilities.csproj | 16 +++++ ...eManagement.Database.CosmosDB.Tests.csproj | 1 + .../Tests/IntegrationTestWebAppFactory.cs | 5 ++ ...ureManagement.Database.Dapper.Tests.csproj | 1 + .../MySqlIntegrationTestWebAppFactory.cs | 28 ++++----- ...qlWithCacheIntegrationTestWebAppFactory.cs | 26 ++++---- .../PostgreSqlIntegrationTestWebAppFactory.cs | 20 +++--- ...qlWithCacheIntegrationTestWebAppFactory.cs | 18 +++--- .../SqlServerIntegrationTestWebAppFactory.cs | 22 +++---- ...erWithCacheIntegrationTestWebAppFactory.cs | 20 +++--- ....Database.EntityFrameworkCore.Tests.csproj | 1 + .../Tests/IntegrationTestWebAppFactory.cs | 6 +- ...reManagement.Database.MongoDB.Tests.csproj | 1 + .../Tests/IntegrationTestWebAppFactory.cs | 21 ++++--- ...anagement.Database.NHibernate.Tests.csproj | 1 + .../Tests/IntegrationTestWebAppFactory.cs | 24 ++++--- .../Database/NHibernate/Tests/Seed.cs | 2 +- 24 files changed, 192 insertions(+), 123 deletions(-) create mode 100644 tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs create mode 100644 tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj diff --git a/FeatureManagement.Database.sln b/FeatureManagement.Database.sln index 22e48eb..7ecf9b5 100644 --- a/FeatureManagement.Database.sln +++ b/FeatureManagement.Database.sln @@ -72,6 +72,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{72D5C4 .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FeatureManagement.Database.Common.Utilities", "tests\FeatureManagement.Database.Common.Utilities\FeatureManagement.Database.Common.Utilities.csproj", "{7906BDB1-E7F5-43E0-B5EB-6774F674781E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -166,6 +168,10 @@ Global {BB720806-46DF-4F8E-BD91-0DEE06BA69F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB720806-46DF-4F8E-BD91-0DEE06BA69F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB720806-46DF-4F8E-BD91-0DEE06BA69F0}.Release|Any CPU.Build.0 = Release|Any CPU + {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -193,6 +199,7 @@ Global {DBAB888A-FE09-4020-BE88-743958B54F53} = {43BBEC90-C0D3-4FBD-BD47-ACCDE5FA7EA8} {84718A54-72FF-4AE9-9FFB-558088C344BD} = {80D694E3-6FD9-4249-A5E9-B6623BD59E28} {BB720806-46DF-4F8E-BD91-0DEE06BA69F0} = {43BBEC90-C0D3-4FBD-BD47-ACCDE5FA7EA8} + {7906BDB1-E7F5-43E0-B5EB-6774F674781E} = {43BBEC90-C0D3-4FBD-BD47-ACCDE5FA7EA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6910875A-378A-40F4-B89C-68DFDF955953} diff --git a/FeatureManagement.Database.slnx b/FeatureManagement.Database.slnx index 3cb6a75..87c551b 100644 --- a/FeatureManagement.Database.slnx +++ b/FeatureManagement.Database.slnx @@ -1,45 +1,43 @@ - - - - + + + + - - + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + - - - diff --git a/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs index 2e0dec0..2fa7563 100644 --- a/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs +++ b/src/FeatureManagement.Database.Abstractions/FeatureManagement/Database/FeatureHybridCacheSerializer.cs @@ -1,3 +1,6 @@ +// Copyright (c) Matteo Ciapparelli. +// Licensed under the MIT license. + #if NET9_0_OR_GREATER using Microsoft.Extensions.Caching.Hybrid; using System.Buffers; 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 4d45470..f1c1d51 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -3,6 +3,7 @@ true + diff --git a/tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs b/tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs new file mode 100644 index 0000000..640b536 --- /dev/null +++ b/tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs @@ -0,0 +1,25 @@ +// Copyright (c) Matteo Ciapparelli. +// Licensed under the MIT license. + +using Docker.DotNet; +using Docker.DotNet.Models; + +namespace FeatureManagement.Database.Common.Utilities; + +public static class DockerContainerHelper +{ + // Used just to avoid conflicts with other containers in GitHub Actions environment + public static async Task RemoveExistingContainerAsync(string containerName) + { +#pragma warning disable S1075 // URIs should not be hardcoded: tests purposes + using var client = new DockerClientConfiguration(new Uri("unix:///var/run/docker.sock")).CreateClient(); +#pragma warning restore S1075 // URIs should not be hardcoded + var containers = await client.Containers.ListContainersAsync(new ContainersListParameters { All = true }); + + var existingContainer = containers.FirstOrDefault(c => c.Names.Contains($"/{containerName}")); + if (existingContainer is not null) + { + await client.Containers.RemoveContainerAsync(existingContainer.ID, new ContainerRemoveParameters { Force = true }); + } + } +} \ No newline at end of file diff --git a/tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj b/tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj new file mode 100644 index 0000000..1c8aadd --- /dev/null +++ b/tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj @@ -0,0 +1,16 @@ + + + + + + net6.0;net8.0;net9.0 + + false + false + + + + + + + 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 ac99324..a7270e4 100644 --- a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj +++ b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj @@ -35,6 +35,7 @@ + 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..14ba7c1 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 @@ -2,6 +2,7 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Builders; +using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Azure.Cosmos; @@ -14,6 +15,7 @@ namespace FeatureManagement.Database.CosmosDB.Tests; public sealed class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { private readonly CosmosDbContainer _cosmosDbContainer = new CosmosDbBuilder() + .WithName(_ContainerName) .WithImage("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest") .WithExposedPort(8081) .WithPortBinding(8081, true) @@ -22,6 +24,8 @@ public sealed class IntegrationTestWebAppFactory : WebApplicationFactory _cosmosDbContainer.GetConnectionString(); public async Task InitializeAsync() @@ -38,6 +42,7 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _cosmosDbContainer.DisposeAsync(); + await DockerContainerHelper.RemoveExistingContainerAsync(_ContainerName); } protected override void ConfigureWebHost(IWebHostBuilder builder) 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 04ba2b4..2934407 100644 --- a/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj +++ b/tests/FeatureManagement.Database.Dapper.Tests/FeatureManagement.Database.Dapper.Tests.csproj @@ -35,6 +35,7 @@ + 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..af04d5b 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(); + private const string _ContainerName = "mysql-test-container"; public MySqlIntegrationTestWebAppFactory() { - _container = _mySqlContainer; + _container = new MySqlBuilder() + .WithName(_ContainerName) + .WithImage("mysql:latest") + .WithDatabase("TestDb") + .WithUsername("root") + .WithPassword("mysqlpassword") + .WithPrivileged(true) + .WithPortBinding(MySqlBuilder.MySqlPort) + .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 a4f188f..28f63cf 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 @@ -11,28 +11,28 @@ 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(); + private const string _ContainerName = "mysql-test-container-cache"; public MySqlWithCacheIntegrationTestWebAppFactory() { - _container = _mySqlContainer; + _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/PostgreSqlIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests/FeatureManagement/Database/EntityFrameworkCore/PostgreSQL/Tests/PostgreSqlIntegrationTestWebAppFactory.cs index f98c6f1..a951a52 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(); + private const string _ContainerName = "postgresql-test-container"; public PostgreSqlIntegrationTestWebAppFactory() { - _container = _postgreSqlContainer; + _container = new PostgreSqlBuilder() + .WithName(_ContainerName) + .WithImage("postgres:latest") + .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort) + .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 abd876d..f641b0a 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 @@ -11,24 +11,24 @@ 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(); + private const string _ContainerName = "postgresql-test-container-cache"; public PostgreSqlWithCacheIntegrationTestWebAppFactory() { - _container = _postgreSqlContainer; + _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/SqlServerIntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests/FeatureManagement/Database/EntityFrameworkCore/SqlServer/Tests/SqlServerIntegrationTestWebAppFactory.cs index 62dadde..899a672 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(); + private const string _ContainerName = "sqlserver-test-container"; public SqlServerIntegrationTestWebAppFactory() { - _container = _sqlServerContainer; + _container = new MsSqlBuilder() + .WithName(_ContainerName) + .WithImage("mcr.microsoft.com/mssql/server:2022-latest") + .WithEnvironment("ACCEPT_EULA", "Y") + .WithPortBinding(MsSqlBuilder.MsSqlPort) + .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..3aa3765 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(); + private const string _ContainerName = "sqlserver-test-container-with-cache"; public SqlServerWithCacheIntegrationTestWebAppFactory() { - _container = _sqlServerContainer; + _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.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj index eb73d59..0fb5811 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj @@ -22,6 +22,7 @@ + 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..6694d5a 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 @@ -2,6 +2,7 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Containers; +using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; @@ -28,6 +29,7 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _container.DisposeAsync(); + await DockerContainerHelper.RemoveExistingContainerAsync(_container.Name); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -37,6 +39,6 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) protected virtual void ConfigureServices(IServiceCollection services) { - services.RemoveAll(typeof(DbContextOptions)); + services.RemoveAll>(); } -} \ 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 6386ab0..2d5dbf0 100644 --- a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj +++ b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj @@ -20,6 +20,7 @@ + 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..2294837 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 @@ -2,6 +2,7 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Builders; +using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; @@ -13,16 +14,17 @@ 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(); + .WithName(_ContainerName) + .WithImage("mongo:latest") + .WithUsername(null) + .WithPassword(null) + .WithPortBinding(MongoDbBuilder.MongoDbPort) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MongoDbBuilder.MongoDbPort)) + .Build(); private const string _Database = "TestDb"; + private const string _ContainerName = "mongodb-test-container"; internal string ConnectionString => _mongoDBContainer.GetConnectionString(); @@ -38,6 +40,7 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _mongoDBContainer.DisposeAsync(); + await DockerContainerHelper.RemoveExistingContainerAsync(_ContainerName); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -50,4 +53,4 @@ private void ConfigureServices(IServiceCollection services) services.AddDatabaseFeatureManagement() .UseMongoDB(ConnectionString, _Database); } -} \ 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 c6e15da..b982d04 100644 --- a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj +++ b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj @@ -22,6 +22,7 @@ + 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 a78190a..5f15823 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 @@ -2,6 +2,7 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Builders; +using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; @@ -13,15 +14,17 @@ namespace FeatureManagement.Database.NHibernate.Tests; public class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { private readonly MsSqlContainer _sqlServerContainer = new MsSqlBuilder() - .WithName("nhibernate-sqlserver-test-container") - .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) - .WithCleanUp(true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) - .Build(); + .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) + .WithCleanUp(true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) + .Build(); + + private const string _ContainerName = "nhibernate-sqlserver-test-container"; public async Task InitializeAsync() { @@ -36,6 +39,7 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _sqlServerContainer.DisposeAsync(); + await DockerContainerHelper.RemoveExistingContainerAsync(_ContainerName); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -48,4 +52,4 @@ protected virtual void ConfigureServices(IServiceCollection services) services.AddDatabaseFeatureManagement() .UseNHibernate(_sqlServerContainer.GetConnectionString(), new SqlServerConnectionFactory()); } -} \ 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); From d485f18c1a8b87b678fd9c0b7efc4579fd15460e Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 6 Apr 2025 22:39:55 +0200 Subject: [PATCH 08/16] Refactor testing setup and remove utility dependencies --- .github/workflows/test.yml | 8 ++--- FeatureManagement.Database.sln | 7 ----- FeatureManagement.Database.slnx | 1 - tests/Directory.Packages.props | 1 - .../DockerContainerHelper.cs | 25 ---------------- ...anagement.Database.Common.Utilities.csproj | 16 ---------- ...eManagement.Database.CosmosDB.Tests.csproj | 1 - .../Tests/IntegrationTestWebAppFactory.cs | 25 +++++++++++----- .../MySqlIntegrationTestWebAppFactory.cs | 8 ++--- ...qlWithCacheIntegrationTestWebAppFactory.cs | 6 ++-- .../PostgreSqlIntegrationTestWebAppFactory.cs | 8 ++--- ...qlWithCacheIntegrationTestWebAppFactory.cs | 6 ++-- .../SqlServerIntegrationTestWebAppFactory.cs | 8 ++--- ...erWithCacheIntegrationTestWebAppFactory.cs | 6 ++-- ....Database.EntityFrameworkCore.Tests.csproj | 1 - .../Tests/IntegrationTestWebAppFactory.cs | 10 +++++-- ...reManagement.Database.MongoDB.Tests.csproj | 1 - .../Tests/IntegrationTestWebAppFactory.cs | 29 ++++++++++++------- ...anagement.Database.NHibernate.Tests.csproj | 1 - .../Tests/IntegrationTestWebAppFactory.cs | 23 ++++++++++----- 20 files changed, 84 insertions(+), 107 deletions(-) delete mode 100644 tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs delete mode 100644 tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e87d55..59c08f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,6 +70,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + framework: [net6, net8, net9] test_type: [ { name: "Abstractions", condition: "abstractions_changed" }, { name: "CosmosDB", condition: "cosmosdb_changed" }, @@ -84,10 +85,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 6.0.x - 8.0.x - 9.0.x + dotnet-version: ${{ matrix.framework }} - name: Start CosmosDB Emulator if: matrix.test_type.name == 'CosmosDB' && needs.checks_tests_need.outputs.cosmosdb_changed == 'true' @@ -96,4 +94,6 @@ jobs: - name: Run ${{ matrix.test_type.name }} tests if: needs.checks_tests_need.outputs[matrix.test_type.condition] == 'true' shell: pwsh + env: + DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework }} run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.test_type.name }} diff --git a/FeatureManagement.Database.sln b/FeatureManagement.Database.sln index 7ecf9b5..22e48eb 100644 --- a/FeatureManagement.Database.sln +++ b/FeatureManagement.Database.sln @@ -72,8 +72,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{72D5C4 .editorconfig = .editorconfig EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FeatureManagement.Database.Common.Utilities", "tests\FeatureManagement.Database.Common.Utilities\FeatureManagement.Database.Common.Utilities.csproj", "{7906BDB1-E7F5-43E0-B5EB-6774F674781E}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -168,10 +166,6 @@ Global {BB720806-46DF-4F8E-BD91-0DEE06BA69F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB720806-46DF-4F8E-BD91-0DEE06BA69F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB720806-46DF-4F8E-BD91-0DEE06BA69F0}.Release|Any CPU.Build.0 = Release|Any CPU - {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7906BDB1-E7F5-43E0-B5EB-6774F674781E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -199,7 +193,6 @@ Global {DBAB888A-FE09-4020-BE88-743958B54F53} = {43BBEC90-C0D3-4FBD-BD47-ACCDE5FA7EA8} {84718A54-72FF-4AE9-9FFB-558088C344BD} = {80D694E3-6FD9-4249-A5E9-B6623BD59E28} {BB720806-46DF-4F8E-BD91-0DEE06BA69F0} = {43BBEC90-C0D3-4FBD-BD47-ACCDE5FA7EA8} - {7906BDB1-E7F5-43E0-B5EB-6774F674781E} = {43BBEC90-C0D3-4FBD-BD47-ACCDE5FA7EA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6910875A-378A-40F4-B89C-68DFDF955953} diff --git a/FeatureManagement.Database.slnx b/FeatureManagement.Database.slnx index 87c551b..11d1c45 100644 --- a/FeatureManagement.Database.slnx +++ b/FeatureManagement.Database.slnx @@ -29,7 +29,6 @@ - diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index f1c1d51..4d45470 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -3,7 +3,6 @@ true - diff --git a/tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs b/tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs deleted file mode 100644 index 640b536..0000000 --- a/tests/FeatureManagement.Database.Common.Utilities/DockerContainerHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Matteo Ciapparelli. -// Licensed under the MIT license. - -using Docker.DotNet; -using Docker.DotNet.Models; - -namespace FeatureManagement.Database.Common.Utilities; - -public static class DockerContainerHelper -{ - // Used just to avoid conflicts with other containers in GitHub Actions environment - public static async Task RemoveExistingContainerAsync(string containerName) - { -#pragma warning disable S1075 // URIs should not be hardcoded: tests purposes - using var client = new DockerClientConfiguration(new Uri("unix:///var/run/docker.sock")).CreateClient(); -#pragma warning restore S1075 // URIs should not be hardcoded - var containers = await client.Containers.ListContainersAsync(new ContainersListParameters { All = true }); - - var existingContainer = containers.FirstOrDefault(c => c.Names.Contains($"/{containerName}")); - if (existingContainer is not null) - { - await client.Containers.RemoveContainerAsync(existingContainer.ID, new ContainerRemoveParameters { Force = true }); - } - } -} \ No newline at end of file diff --git a/tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj b/tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj deleted file mode 100644 index 1c8aadd..0000000 --- a/tests/FeatureManagement.Database.Common.Utilities/FeatureManagement.Database.Common.Utilities.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - net6.0;net8.0;net9.0 - - false - false - - - - - - - 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 a7270e4..ac99324 100644 --- a/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj +++ b/tests/FeatureManagement.Database.CosmosDB.Tests/FeatureManagement.Database.CosmosDB.Tests.csproj @@ -35,7 +35,6 @@ - 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 14ba7c1..a0e7f20 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 @@ -2,7 +2,6 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Builders; -using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Azure.Cosmos; @@ -14,8 +13,16 @@ namespace FeatureManagement.Database.CosmosDB.Tests; public sealed class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { - private readonly CosmosDbContainer _cosmosDbContainer = new CosmosDbBuilder() - .WithName(_ContainerName) + 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) @@ -23,10 +30,7 @@ public sealed class IntegrationTestWebAppFactory : WebApplicationFactory _cosmosDbContainer.GetConnectionString(); + } public async Task InitializeAsync() { @@ -42,7 +46,6 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _cosmosDbContainer.DisposeAsync(); - await DockerContainerHelper.RemoveExistingContainerAsync(_ContainerName); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -74,4 +77,10 @@ private void ConfigureServices(IServiceCollection services) }; }); } + + private static string GetUniqueContainerName(string baseName) + { + var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; + return $"{baseName}-{framework}-{Guid.NewGuid().ToString("N")[..8]}"; + } } \ No newline at end of file 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 af04d5b..20d98d0 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,12 +11,12 @@ namespace FeatureManagement.Database.EntityFrameworkCore.MySql.Tests; public sealed class MySqlIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private const string _ContainerName = "mysql-test-container"; - public MySqlIntegrationTestWebAppFactory() { + var containerName = GetUniqueContainerName("mysql-test-container"); + _container = new MySqlBuilder() - .WithName(_ContainerName) + .WithName(containerName) .WithImage("mysql:latest") .WithDatabase("TestDb") .WithUsername("root") @@ -35,4 +35,4 @@ protected override void ConfigureServices(IServiceCollection services) .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 28f63cf..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 @@ -11,12 +11,12 @@ namespace FeatureManagement.Database.EntityFrameworkCore.MySql.Tests; public sealed class MySqlWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private const string _ContainerName = "mysql-test-container-cache"; - public MySqlWithCacheIntegrationTestWebAppFactory() { + var containerName = GetUniqueContainerName("mysql-test-container-cache"); + _container = new MySqlBuilder() - .WithName(_ContainerName) + .WithName(containerName) .WithImage("mysql:latest") .WithDatabase("TestDb") .WithUsername("root") 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 a951a52..58d95ca 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,12 +11,12 @@ namespace FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests; public sealed class PostgreSqlIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private const string _ContainerName = "postgresql-test-container"; - public PostgreSqlIntegrationTestWebAppFactory() { + var containerName = GetUniqueContainerName("postgresql-test-container"); + _container = new PostgreSqlBuilder() - .WithName(_ContainerName) + .WithName(containerName) .WithImage("postgres:latest") .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort) .WithCleanUp(true) @@ -31,4 +31,4 @@ protected override void ConfigureServices(IServiceCollection services) .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 f641b0a..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 @@ -11,12 +11,12 @@ namespace FeatureManagement.Database.EntityFrameworkCore.PostgreSQL.Tests; public sealed class PostgreSqlWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private const string _ContainerName = "postgresql-test-container-cache"; - public PostgreSqlWithCacheIntegrationTestWebAppFactory() { + var containerName = GetUniqueContainerName("postgresql-test-container-cache"); + _container = new PostgreSqlBuilder() - .WithName(_ContainerName) + .WithName(containerName) .WithImage("postgres:latest") .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort, assignRandomHostPort: true) .WithCleanUp(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 899a672..ea2bf26 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,12 +11,12 @@ namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; public sealed class SqlServerIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private const string _ContainerName = "sqlserver-test-container"; - public SqlServerIntegrationTestWebAppFactory() { + var containerName = GetUniqueContainerName("sqlserver-test-container"); + _container = new MsSqlBuilder() - .WithName(_ContainerName) + .WithName(containerName) .WithImage("mcr.microsoft.com/mssql/server:2022-latest") .WithEnvironment("ACCEPT_EULA", "Y") .WithPortBinding(MsSqlBuilder.MsSqlPort) @@ -32,4 +32,4 @@ protected override void ConfigureServices(IServiceCollection services) .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 3aa3765..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,12 +11,12 @@ namespace FeatureManagement.Database.EntityFrameworkCore.SqlServer.Tests; public sealed class SqlServerWithCacheIntegrationTestWebAppFactory : IntegrationTestWebAppFactory { - private const string _ContainerName = "sqlserver-test-container-with-cache"; - public SqlServerWithCacheIntegrationTestWebAppFactory() { + var containerName = GetUniqueContainerName("sqlserver-test-container-with-cache"); + _container = new MsSqlBuilder() - .WithName(_ContainerName) + .WithName(containerName) .WithImage("mcr.microsoft.com/mssql/server:2022-latest") .WithEnvironment("ACCEPT_EULA", "Y") .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: 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 0fb5811..eb73d59 100644 --- a/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj +++ b/tests/FeatureManagement.Database.EntityFrameworkCore.Tests/FeatureManagement.Database.EntityFrameworkCore.Tests.csproj @@ -22,7 +22,6 @@ - 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 6694d5a..0b1a067 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 @@ -2,7 +2,6 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Containers; -using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; @@ -29,7 +28,6 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _container.DisposeAsync(); - await DockerContainerHelper.RemoveExistingContainerAsync(_container.Name); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -41,4 +39,10 @@ protected virtual void ConfigureServices(IServiceCollection services) { services.RemoveAll>(); } -} + + protected static string GetUniqueContainerName(string baseName) + { + var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; + return $"{baseName}-{framework}-{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 2d5dbf0..6386ab0 100644 --- a/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj +++ b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement.Database.MongoDB.Tests.csproj @@ -20,7 +20,6 @@ - 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 2294837..f9e78be 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 @@ -2,7 +2,6 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Builders; -using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; @@ -13,8 +12,17 @@ namespace FeatureManagement.Database.MongoDB.Tests; public sealed class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { - private readonly MongoDbContainer _mongoDBContainer = new MongoDbBuilder() - .WithName(_ContainerName) + 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) @@ -22,11 +30,7 @@ public sealed class IntegrationTestWebAppFactory : WebApplicationFactory _mongoDBContainer.GetConnectionString(); + } public async Task InitializeAsync() { @@ -40,7 +44,6 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _mongoDBContainer.DisposeAsync(); - await DockerContainerHelper.RemoveExistingContainerAsync(_ContainerName); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -53,4 +56,10 @@ private void ConfigureServices(IServiceCollection services) services.AddDatabaseFeatureManagement() .UseMongoDB(ConnectionString, _Database); } -} + + private static string GetUniqueContainerName(string baseName) + { + var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; + return $"{baseName}-{framework}-{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 b982d04..c6e15da 100644 --- a/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj +++ b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement.Database.NHibernate.Tests.csproj @@ -22,7 +22,6 @@ - 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 5f15823..b4973c7 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 @@ -2,7 +2,6 @@ // Licensed under the MIT license. using DotNet.Testcontainers.Builders; -using FeatureManagement.Database.Common.Utilities; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; @@ -13,8 +12,14 @@ namespace FeatureManagement.Database.NHibernate.Tests; public class IntegrationTestWebAppFactory : WebApplicationFactory, IAsyncLifetime { - private readonly MsSqlContainer _sqlServerContainer = new MsSqlBuilder() - .WithName(_ContainerName) + 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) @@ -23,8 +28,7 @@ public class IntegrationTestWebAppFactory : WebApplicationFactory, IAsy .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) .Build(); - - private const string _ContainerName = "nhibernate-sqlserver-test-container"; + } public async Task InitializeAsync() { @@ -39,7 +43,6 @@ public async Task InitializeAsync() public new async Task DisposeAsync() { await _sqlServerContainer.DisposeAsync(); - await DockerContainerHelper.RemoveExistingContainerAsync(_ContainerName); } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -52,4 +55,10 @@ protected virtual void ConfigureServices(IServiceCollection services) services.AddDatabaseFeatureManagement() .UseNHibernate(_sqlServerContainer.GetConnectionString(), new SqlServerConnectionFactory()); } -} + + private static string GetUniqueContainerName(string baseName) + { + var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; + return $"{baseName}-{framework}-{Guid.NewGuid().ToString("N")[..8]}"; + } +} \ No newline at end of file From 49dcff2d097aa21dab493aff11873ada1274d711 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 6 Apr 2025 22:44:13 +0200 Subject: [PATCH 09/16] Fix dotnet version in test workflow --- .github/workflows/test.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59c08f6..21a5bfb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,8 +70,9 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - framework: [net6, net8, net9] - test_type: [ + targetFramework: [net6, net8, net9] + dotnetVersion: [6.0.x, 8.0.x, 9.0.x] + testType: [ { name: "Abstractions", condition: "abstractions_changed" }, { name: "CosmosDB", condition: "cosmosdb_changed" }, { name: "Dapper", condition: "dapper_changed" }, @@ -85,15 +86,15 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ matrix.framework }} + dotnet-version: ${{ matrix.dotnetVersion }} - 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 env: DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework }} - run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.test_type.name }} + run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.testType.name }} From c7d38cbad04787c7f86b6f59f0048b4dcd83a1a8 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 6 Apr 2025 22:50:36 +0200 Subject: [PATCH 10/16] Refactor .NET matrix configuration in test.yml --- .github/workflows/test.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21a5bfb..b9aff0f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,8 +70,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - targetFramework: [net6, net8, net9] - dotnetVersion: [6.0.x, 8.0.x, 9.0.x] + framework: + - target: net6 + sdk: 6.0.x + - target: net8 + sdk: 8.0.x + - target: net9 + sdk: 9.0.x testType: [ { name: "Abstractions", condition: "abstractions_changed" }, { name: "CosmosDB", condition: "cosmosdb_changed" }, @@ -86,7 +91,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ matrix.dotnetVersion }} + dotnet-version: ${{ matrix.framework.sdk }} - name: Start CosmosDB Emulator if: matrix.testType.name == 'CosmosDB' && needs.checks_tests_need.outputs.cosmosdb_changed == 'true' @@ -96,5 +101,5 @@ jobs: if: needs.checks_tests_need.outputs[matrix.testType.condition] == 'true' shell: pwsh env: - DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework }} + DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework.target }} run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.testType.name }} From 630d69ec453e595b779c34ca62d883fd7726a946 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 6 Apr 2025 22:52:26 +0200 Subject: [PATCH 11/16] Update test.yml for matrix strategy in tests --- .github/workflows/test.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9aff0f..2fbebbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,13 +70,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - framework: - - target: net6 - sdk: 6.0.x - - target: net8 - sdk: 8.0.x - - target: net9 - sdk: 9.0.x + targetFramework: [net6, net8, net9] testType: [ { name: "Abstractions", condition: "abstractions_changed" }, { name: "CosmosDB", condition: "cosmosdb_changed" }, @@ -91,7 +85,10 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: ${{ matrix.framework.sdk }} + dotnet-version: | + 6.0.x + 8.0.x + 9.0.x - name: Start CosmosDB Emulator if: matrix.testType.name == 'CosmosDB' && needs.checks_tests_need.outputs.cosmosdb_changed == 'true' @@ -101,5 +98,5 @@ jobs: if: needs.checks_tests_need.outputs[matrix.testType.condition] == 'true' shell: pwsh env: - DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework.target }} + DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework }} run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.testType.name }} From 670e78b2798f2d9959dc72cddbb6f8eb0b03e780 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Sun, 6 Apr 2025 23:09:57 +0200 Subject: [PATCH 12/16] Update test configuration and container setups --- .github/workflows/test.yml | 3 --- .../CosmosDB/Tests/IntegrationTestWebAppFactory.cs | 9 ++++----- .../MySql/Tests/MySqlIntegrationTestWebAppFactory.cs | 2 +- .../Tests/PostgreSqlIntegrationTestWebAppFactory.cs | 2 +- .../Tests/SqlServerIntegrationTestWebAppFactory.cs | 2 +- .../Tests/IntegrationTestWebAppFactory.cs | 3 +-- .../MongoDB/Tests/IntegrationTestWebAppFactory.cs | 5 ++--- .../NHibernate/Tests/IntegrationTestWebAppFactory.cs | 5 ++--- 8 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2fbebbc..8cd4f5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,6 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - targetFramework: [net6, net8, net9] testType: [ { name: "Abstractions", condition: "abstractions_changed" }, { name: "CosmosDB", condition: "cosmosdb_changed" }, @@ -97,6 +96,4 @@ jobs: - name: Run ${{ matrix.testType.name }} tests if: needs.checks_tests_need.outputs[matrix.testType.condition] == 'true' shell: pwsh - env: - DOTNET_TARGET_FRAMEWORK: ${{ matrix.framework }} run: ./test.ps1 -Configuration ${{ env.CONFIGURATION }} -TestType ${{ matrix.testType.name }} 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 a0e7f20..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 @@ -24,11 +24,11 @@ public IntegrationTestWebAppFactory() _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(); } @@ -80,7 +80,6 @@ private void ConfigureServices(IServiceCollection services) private static string GetUniqueContainerName(string baseName) { - var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; - return $"{baseName}-{framework}-{Guid.NewGuid().ToString("N")[..8]}"; + return $"{baseName}-{Guid.NewGuid().ToString("N")[..8]}"; } } \ No newline at end of file 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 20d98d0..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 @@ -22,7 +22,7 @@ public MySqlIntegrationTestWebAppFactory() .WithUsername("root") .WithPassword("mysqlpassword") .WithPrivileged(true) - .WithPortBinding(MySqlBuilder.MySqlPort) + .WithPortBinding(MySqlBuilder.MySqlPort, assignRandomHostPort: true) .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MySqlBuilder.MySqlPort)) .Build(); 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 58d95ca..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 @@ -18,7 +18,7 @@ public PostgreSqlIntegrationTestWebAppFactory() _container = new PostgreSqlBuilder() .WithName(containerName) .WithImage("postgres:latest") - .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort) + .WithPortBinding(PostgreSqlBuilder.PostgreSqlPort, assignRandomHostPort: true) .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(PostgreSqlBuilder.PostgreSqlPort)) .Build(); 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 ea2bf26..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 @@ -19,7 +19,7 @@ public SqlServerIntegrationTestWebAppFactory() .WithName(containerName) .WithImage("mcr.microsoft.com/mssql/server:2022-latest") .WithEnvironment("ACCEPT_EULA", "Y") - .WithPortBinding(MsSqlBuilder.MsSqlPort) + .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: true) .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) .Build(); 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 0b1a067..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 @@ -42,7 +42,6 @@ protected virtual void ConfigureServices(IServiceCollection services) protected static string GetUniqueContainerName(string baseName) { - var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; - return $"{baseName}-{framework}-{Guid.NewGuid().ToString("N")[..8]}"; + 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/IntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.MongoDB.Tests/FeatureManagement/Database/MongoDB/Tests/IntegrationTestWebAppFactory.cs index f9e78be..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 @@ -26,7 +26,7 @@ public IntegrationTestWebAppFactory() .WithImage("mongo:latest") .WithUsername(null) .WithPassword(null) - .WithPortBinding(MongoDbBuilder.MongoDbPort) + .WithPortBinding(MongoDbBuilder.MongoDbPort, assignRandomHostPort: true) .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MongoDbBuilder.MongoDbPort)) .Build(); @@ -59,7 +59,6 @@ private void ConfigureServices(IServiceCollection services) private static string GetUniqueContainerName(string baseName) { - var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; - return $"{baseName}-{framework}-{Guid.NewGuid().ToString("N")[..8]}"; + 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/IntegrationTestWebAppFactory.cs b/tests/FeatureManagement.Database.NHibernate.Tests/FeatureManagement/Database/NHibernate/Tests/IntegrationTestWebAppFactory.cs index b4973c7..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 @@ -24,7 +24,7 @@ public IntegrationTestWebAppFactory() .WithEnvironment("ACCEPT_EULA", "Y") .WithEnvironment("SA_USERNAME", MsSqlBuilder.DefaultUsername) .WithEnvironment("SA_PASSWORD", MsSqlBuilder.DefaultPassword) - .WithPortBinding(MsSqlBuilder.MsSqlPort) + .WithPortBinding(MsSqlBuilder.MsSqlPort, assignRandomHostPort: true) .WithCleanUp(true) .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlBuilder.MsSqlPort)) .Build(); @@ -58,7 +58,6 @@ protected virtual void ConfigureServices(IServiceCollection services) private static string GetUniqueContainerName(string baseName) { - var framework = Environment.GetEnvironmentVariable("DOTNET_TARGET_FRAMEWORK") ?? "default"; - return $"{baseName}-{framework}-{Guid.NewGuid().ToString("N")[..8]}"; + return $"{baseName}-{Guid.NewGuid().ToString("N")[..8]}"; } } \ No newline at end of file From d030277325143a328ca90b42aebe22637c145e17 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Mon, 7 Apr 2025 00:10:57 +0200 Subject: [PATCH 13/16] Implement IDisposable in CosmosDBConnectionFactory --- .../CosmosDB/CosmosDBConnectionFactory.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) 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 From c6a2ad67a0e03c300115d95abd07d74de0973e31 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Mon, 7 Apr 2025 00:14:40 +0200 Subject: [PATCH 14/16] Check Docker installation and improve CosmosDB emulator setup --- .github/actions/cosmosdb-setup/action.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 From 8e1b1d869e208f9eb7ee92abee5f49fce291fc8b Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Mon, 7 Apr 2025 23:42:52 +0200 Subject: [PATCH 15/16] Refactor cosmosDb tests seed for clarity --- .../Database/CosmosDB/Tests/Seed.cs | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) 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..3667577 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,61 @@ 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)); + // 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())); + await featureSettingsContainer.CreateItemAsync(new { id = settings[0].Id.ToString(), settings[0].FeatureId, settings[0].FilterType, settings[0].Parameters }, featureSettingsPartitionKey); } } \ No newline at end of file From a634376f09e438a6823fc3de4e9a94b68eccb3f4 Mon Sep 17 00:00:00 2001 From: Matteo Ciapparelli Date: Tue, 8 Apr 2025 23:17:28 +0200 Subject: [PATCH 16/16] Add error handling for item insertion in ComsosDB tests --- .../Database/CosmosDB/Tests/Seed.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) 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 3667577..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 @@ -71,11 +71,18 @@ internal static async Task SeedDataAsync(CosmosDBOptions cosmosDBOptions, ICosmo if (!cosmosDBOptions.UseSeparateContainers) features[0].Settings = settings; - // 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); + 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].Parameters }, featureSettingsPartitionKey); + 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