diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index c9a67346d..6a5ca6d3f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,18 +3,18 @@ "isRoot": true, "tools": { "csharpier": { - "version": "1.2.5", + "version": "1.3.0", "commands": [ "csharpier" ], "rollForward": false }, "dotnet-coverage": { - "version": "18.3.1", + "version": "18.11.2", "commands": [ "dotnet-coverage" ], "rollForward": false } } -} \ No newline at end of file +} diff --git a/.github/workflows/core_build_publish.yml b/.github/workflows/core_build_publish.yml index b512bfa11..786572ff4 100644 --- a/.github/workflows/core_build_publish.yml +++ b/.github/workflows/core_build_publish.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: env: - DOTNET_VERSION: 10.0.100 + DOTNET_VERSION: 11.0.100-rc.1.26425.128 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 @@ -121,7 +121,7 @@ jobs: ports: - "1433:1433" postgres: - image: postgres:15 + image: postgres:18 env: POSTGRES_PASSWORD: Passw12# ports: @@ -152,7 +152,7 @@ jobs: CORELIB_TESTS_AZURE_BLOB_STORAGE_CONTAINER_NAME: ${{ secrets.CORELIB_TESTS_AZURE_BLOB_STORAGE_CONTAINER_NAME }} CORELIB_TESTS_AZURE_TABLE_STORAGE_TABLE_NAME: ${{ secrets.CORELIB_TESTS_AZURE_TABLE_STORAGE_TABLE_NAME }} - name: Integration Test - SQL Server - run: dotnet test --no-build --configuration Release -- --explicit on --filter-query '/[(category=integration)&(database!=postgres)]' + run: dotnet test --no-build --configuration Release -- --explicit on --filter-trait 'category=integration' env: SqlServer__ConnectionStringBase: Server=localhost,1433;User Id=sa;Password=Passw12#;Encrypt=false LeanCodeIntegrationTests__Database: sqlserver diff --git a/Directory.Build.props b/Directory.Build.props index d7d30d30d..2fe03827c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ preview library - net10.0 + net11.0 portable true enable diff --git a/Directory.Packages.props b/Directory.Packages.props index a91178548..217437c00 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,17 +2,23 @@ true - 10.0.0 - 10.0.0 - 10.0.0 + 11.0.0-rc.1.26431.118 + 11.0.0-rc.1.26425.128 + 11.0.0-rc.1.26431.118 - 8.5.7 + + 8.5.10 + + 1.18.0 + 10.2.3 + 4.0.1 - 5.0.0 + + 5.9.0 - 10.0.0 + 11.0.0-rc.1.26425.128 @@ -21,41 +27,41 @@ - + - - + + - - - - - + + + + + - + - + - + - + - - + + - + - + - - - - + + + + @@ -72,7 +78,7 @@ - + @@ -80,7 +86,7 @@ - + @@ -97,36 +103,36 @@ - + - + - - + + - - - + + + - - + + - - - - - - + + + + + + diff --git a/README.md b/README.md index 073e56421..d86645bf7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Corelibrary Build & Publish](https://github.com/leancodepl/corelibrary/workflows/Corelibrary%20Build%20&%20Publish/badge.svg) [![Nuget](https://img.shields.io/nuget/vpre/LeanCode.Components)](https://www.nuget.org/packages/LeanCode.Components) [![feedz.io](https://img.shields.io/badge/endpoint.svg?url=https%3A%2F%2Ff.feedz.io%2Fleancode%2Fpublic%2Fshield%2FLeanCode.Components%2Flatest)](https://feedz.io/org/leancode/repository/public/packages/LeanCode.Components) -[![codecov](https://codecov.io/gh/leancodepl/corelibrary/branch/v10.0-preview/graph/badge.svg?token=ROFNA3WTTD)](https://codecov.io/gh/leancodepl/corelibrary) +[![codecov](https://codecov.io/gh/leancodepl/corelibrary/branch/v11.0-preview/graph/badge.svg?token=ROFNA3WTTD)](https://codecov.io/gh/leancodepl/corelibrary) [![License](https://img.shields.io/badge/License-Apache_2.0-green.svg)](https://www.apache.org/licenses/LICENSE-2.0) The LeanCode Core Library is a set of helper libraries developed at [our company](https://leancode.co/) that aids our day-to-day development. Not only does it serve as a facilitator in our day-to-day coding activities, but it also encapsulates comprehensive guidelines, gathers our collective knowledge on application architecture and development best practices. @@ -50,7 +50,7 @@ All of the libraries that are part of the CoreLib are versioned together and req ## Supported versions -Here is the list of available major versions of the library (as of 2024-08-16): +Here is the list of available major versions of the library (as of 2026-09-14): | CoreLib | .NET Core | Under development | Stable | Notes | |---------|-----------|-------------------|------------|-------------------| @@ -65,7 +65,8 @@ Here is the list of available major versions of the library (as of 2024-08-16): | v8.0 | 8.0 | | | Unmaintained | | v8.1 | 8.0 | | ✔ | | | v9.0 | 9.0 | | ✔ | | -| v10.0 | 10.0 | ✔ | | | +| v10.0 | 10.0 | | ✔ | | +| v11.0 | 11.0 | ✔ | | Release candidate | ## Building & Testing @@ -95,9 +96,9 @@ It has a `docker` folder that contains necessary configuration. You can run the ```sh # For running tests with SQL Server -$ DB=sqlserver docker-compose run test +$ DB=sqlserver docker compose run test # For running tests with PostgreSQL -$ DB=postgres docker-compose run test +$ DB=postgres docker compose run test ``` ### Publishing diff --git a/cloud-tests-infrastructure/postgres.tf b/cloud-tests-infrastructure/postgres.tf index 6db5a19f4..b9a2694a4 100644 --- a/cloud-tests-infrastructure/postgres.tf +++ b/cloud-tests-infrastructure/postgres.tf @@ -3,7 +3,7 @@ resource "azurerm_postgresql_flexible_server" "server" { location = var.location name = var.postgres_server_name - version = "14" + version = "18" sku_name = "B_Standard_B1ms" storage_mb = "32768" diff --git a/global.json b/global.json index 6f5a3e61c..4dc8ca551 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "10.0.100", - "allowPrerelease": false, + "version": "11.0.100-rc.1.26425.128", + "allowPrerelease": true, "rollForward": "latestMinor" }, "test": { diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..345b4b2ff --- /dev/null +++ b/nuget.config @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CQRS/LeanCode.CQRS.Annotations/LeanCode.CQRS.Annotations.csproj b/src/CQRS/LeanCode.CQRS.Annotations/LeanCode.CQRS.Annotations.csproj index 96871dbad..60ea24f82 100644 --- a/src/CQRS/LeanCode.CQRS.Annotations/LeanCode.CQRS.Annotations.csproj +++ b/src/CQRS/LeanCode.CQRS.Annotations/LeanCode.CQRS.Annotations.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0;net11.0 diff --git a/src/Domain/LeanCode.DomainModels.EF/DbContextOptionsBuilderExtensions.cs b/src/Domain/LeanCode.DomainModels.EF/DbContextOptionsBuilderExtensions.cs index 753b8c97d..071b71c77 100644 --- a/src/Domain/LeanCode.DomainModels.EF/DbContextOptionsBuilderExtensions.cs +++ b/src/Domain/LeanCode.DomainModels.EF/DbContextOptionsBuilderExtensions.cs @@ -35,7 +35,7 @@ public static DbContextOptionsBuilder AddSqlServerTypedIdMappingPlugins(this DbC } } -internal class TypedIdDbContextOptionsExtension : IDbContextOptionsExtension +internal sealed class TypedIdDbContextOptionsExtension : IDbContextOptionsExtension { private readonly ITypedIdStoreTypeProvider storeTypeProvider; diff --git a/src/Domain/LeanCode.DomainModels.EF/TimestampTzExpressionInterceptor.cs b/src/Domain/LeanCode.DomainModels.EF/TimestampTzExpressionInterceptor.cs index e555010cf..e1f33892a 100644 --- a/src/Domain/LeanCode.DomainModels.EF/TimestampTzExpressionInterceptor.cs +++ b/src/Domain/LeanCode.DomainModels.EF/TimestampTzExpressionInterceptor.cs @@ -9,6 +9,7 @@ public sealed class TimestampTzExpressionRewriter : ExpressionVisitor { private static readonly Type TimestampTzType = typeof(TimestampTz); private static readonly Type TimeZoneInfoType = typeof(TimeZoneInfo); + private static readonly Type DateTimeType = typeof(DateTime); private static readonly Type DateTimeOffsetType = typeof(DateTimeOffset); public static readonly PropertyInfo LocalTimestampWithoutOffsetProperty = @@ -27,6 +28,10 @@ public sealed class TimestampTzExpressionRewriter : ExpressionVisitor DateTimeOffsetType.GetProperty(nameof(DateTimeOffset.UtcDateTime)) ?? throw new MissingMemberException(DateTimeOffsetType.FullName, nameof(DateTimeOffset.UtcDateTime)); + public static readonly MethodInfo ToUniversalTimeMethod = + DateTimeType.GetMethod(nameof(DateTime.ToUniversalTime), Type.EmptyTypes) + ?? throw new MissingMemberException(DateTimeType.FullName, nameof(DateTime.ToUniversalTime)); + public static readonly MethodInfo ConvertDateTimeBySystemTimeZoneIdMethod = TimeZoneInfoType.GetMethod( nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId), @@ -44,7 +49,15 @@ protected override Expression VisitMember(MemberExpression node) return Expression.Call( null, ConvertDateTimeBySystemTimeZoneIdMethod, - Expression.Property(Expression.Property(node.Expression, UtcTimestampProperty), UtcDateTimeProperty), + // Npgsql 11 translates UtcDateTime as a no-op which retains DateTimeOffset as the SQL + // expression's CLR type. ToUniversalTime retags it as DateTime, which the outer translator requires. + Expression.Call( + Expression.Property( + Expression.Property(node.Expression, UtcTimestampProperty), + UtcDateTimeProperty + ), + ToUniversalTimeMethod + ), Expression.Property(node.Expression, TimeZoneIdProperty) ); } diff --git a/src/Infrastructure/LeanCode.AzureIdentity/DefaultLeanCodeCredential.cs b/src/Infrastructure/LeanCode.AzureIdentity/DefaultLeanCodeCredential.cs index a019cc934..db9bb9d73 100644 --- a/src/Infrastructure/LeanCode.AzureIdentity/DefaultLeanCodeCredential.cs +++ b/src/Infrastructure/LeanCode.AzureIdentity/DefaultLeanCodeCredential.cs @@ -49,7 +49,11 @@ public static TokenCredential Create(AzureCredentialConfiguration config) if (config.UseManagedIdentity) { - return new ManagedIdentityCredential(config.ClientId); + var managedIdentityId = string.IsNullOrEmpty(config.ClientId) + ? ManagedIdentityId.SystemAssigned + : ManagedIdentityId.FromUserAssignedClientId(config.ClientId); + + return new ManagedIdentityCredential(managedIdentityId); } else if (config.UseAzureCLI) { diff --git a/src/Infrastructure/LeanCode.ClientCredentialsHandler/ClientCredentialsHandler.cs b/src/Infrastructure/LeanCode.ClientCredentialsHandler/ClientCredentialsHandler.cs index ea5d5f031..5a2670ae8 100644 --- a/src/Infrastructure/LeanCode.ClientCredentialsHandler/ClientCredentialsHandler.cs +++ b/src/Infrastructure/LeanCode.ClientCredentialsHandler/ClientCredentialsHandler.cs @@ -1,6 +1,6 @@ using System.Net; using System.Net.Http.Headers; -using IdentityModel.Client; +using Duende.IdentityModel.Client; using LeanCode.Logging; namespace LeanCode.ClientCredentialsHandler; diff --git a/src/Infrastructure/LeanCode.ClientCredentialsHandler/LeanCode.ClientCredentialsHandler.csproj b/src/Infrastructure/LeanCode.ClientCredentialsHandler/LeanCode.ClientCredentialsHandler.csproj index 433f62140..e7f6092e9 100644 --- a/src/Infrastructure/LeanCode.ClientCredentialsHandler/LeanCode.ClientCredentialsHandler.csproj +++ b/src/Infrastructure/LeanCode.ClientCredentialsHandler/LeanCode.ClientCredentialsHandler.csproj @@ -1,6 +1,6 @@ - + diff --git a/src/Infrastructure/LeanCode.Dapper/RawQueryValidator.cs b/src/Infrastructure/LeanCode.Dapper/RawQueryValidator.cs index e8b646e6c..cdf526a4e 100644 --- a/src/Infrastructure/LeanCode.Dapper/RawQueryValidator.cs +++ b/src/Infrastructure/LeanCode.Dapper/RawQueryValidator.cs @@ -6,7 +6,7 @@ namespace LeanCode.Dapper; // Inspired by QueryValidator.Fody // https://github.com/kamil-mrzyglod/QueryValidator.Fody/blob/master/QueryValidator.Fody/QueryValidator.Fody/ModuleWeaver.cs -public static class RawQueryValidator +public static partial class RawQueryValidator { [System.Diagnostics.CodeAnalysis.SuppressMessage("?", "CA2100", Justification = "It does not. :)")] [System.Diagnostics.CodeAnalysis.SuppressMessage("?", "CA2201", Justification = "Doesn't need to be specific.")] @@ -29,8 +29,8 @@ public static async Task ValidateQueriesAsync(string connectionString, params As var sqlText = (string?)sql.GetValue(null) ?? ""; // https://xkcd.com/208/ - sqlText = Regex.Replace(sqlText, "IN @[a-zA-Z]{0,}", "IN (1)", RegexOptions.Compiled); - sqlText = Regex.Replace(sqlText, "(?i)(? t.Value).ToList(); + message.Fids = tokens.SelectMany(t => t.Value).ToList(); - if (message.Tokens.Count == 0) + if (message.Fids.Count == 0) { logger.Information("Cannot send push to users {UserIds} - no tokens", userIds); } @@ -99,10 +99,10 @@ public virtual async Task SendToUsersAsync( logger.Debug( "Sending push notification to user {Count} users, targeting {Count} devices", userIds.Count, - message.Tokens.Count + message.Fids.Count ); var response = await messaging.SendEachForMulticastAsync(message, dryRun, cancellationToken); - await HandleBatchResponseAsync(response, message.Tokens, cancellationToken); + await HandleBatchResponseAsync(response, message.Fids, cancellationToken); } } @@ -116,7 +116,7 @@ public virtual async Task SendAllAsync( logger.Debug("Sending {Count} push messages", messages.Count()); var response = await messaging.SendEachAsync(messages, dryRun, cancellationToken); - await HandleBatchResponseAsync(response, messages.Select(m => m.Token), cancellationToken); + await HandleBatchResponseAsync(response, messages.Select(m => m.Fid), cancellationToken); } public virtual async Task SendMulticastAsync( @@ -125,10 +125,10 @@ public virtual async Task SendMulticastAsync( CancellationToken cancellationToken = default ) { - logger.Debug("Sending multicast push message to {Count} targets", message.Tokens.Count); + logger.Debug("Sending multicast push message to {Count} targets", message.Fids.Count); var response = await messaging.SendEachForMulticastAsync(message, dryRun, cancellationToken); - await HandleBatchResponseAsync(response, message.Tokens, cancellationToken); + await HandleBatchResponseAsync(response, message.Fids, cancellationToken); } private async Task HandleBatchResponseAsync( diff --git a/src/Infrastructure/LeanCode.Firebase.FCM/PushNotificationTokenStore.cs b/src/Infrastructure/LeanCode.Firebase.FCM/PushNotificationTokenStore.cs index cd68196ff..90a60af5a 100644 --- a/src/Infrastructure/LeanCode.Firebase.FCM/PushNotificationTokenStore.cs +++ b/src/Infrastructure/LeanCode.Firebase.FCM/PushNotificationTokenStore.cs @@ -72,7 +72,7 @@ public async Task AddUserTokenAsync(TUserId userId, string newToken, Cancellatio var dateCreatedColumn = GetTokensColumnName(nameof(PushNotificationTokenEntity.DateCreated)); // perform an upsert using SQL merge, ensuring that given token is assigned to our user and no-one else - await dbContext.Database.ExecuteSqlInterpolatedAsync( + await dbContext.Database.ExecuteSqlAsync( FormattableStringFactory.Create( $$""" MERGE INTO {{tokensTable}} "pt" diff --git a/src/Infrastructure/LeanCode.Firebase/FirebaseConfiguration.cs b/src/Infrastructure/LeanCode.Firebase/FirebaseConfiguration.cs index 4d31f3f09..634573e5b 100644 --- a/src/Infrastructure/LeanCode.Firebase/FirebaseConfiguration.cs +++ b/src/Infrastructure/LeanCode.Firebase/FirebaseConfiguration.cs @@ -28,7 +28,7 @@ public static FirebaseApp Prepare(string? cfg, string name) return FirebaseApp.Create( new AppOptions { - Credential = GoogleCredential.FromJson(cfg), + Credential = CredentialFactory.FromJson(cfg).ToGoogleCredential(), ProjectId = JsonNode.Parse(cfg)?["project_id"]?.ToString(), }, name diff --git a/src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.ForceUpdate.Contracts.csproj b/src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.ForceUpdate.Contracts.csproj index 3023b2002..0cbcf095d 100644 --- a/src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.ForceUpdate.Contracts.csproj +++ b/src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.ForceUpdate.Contracts.csproj @@ -2,7 +2,7 @@ annotations - net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0;net11.0 diff --git a/src/Infrastructure/LeanCode.ServiceToService/Incoming/ServiceToServiceAuthenticationHandler.cs b/src/Infrastructure/LeanCode.ServiceToService/Incoming/ServiceToServiceAuthenticationHandler.cs index 2af34eeb8..d198cd12d 100644 --- a/src/Infrastructure/LeanCode.ServiceToService/Incoming/ServiceToServiceAuthenticationHandler.cs +++ b/src/Infrastructure/LeanCode.ServiceToService/Incoming/ServiceToServiceAuthenticationHandler.cs @@ -79,7 +79,11 @@ out var multipleCallerIds var identity = new ClaimsIdentity(claims, Scheme.Name, Options.NameClaimType, Options.RoleClaimType); var ticket = new AuthenticationTicket(new(identity), Scheme.Name); - LogAuthenticatedServiceCaller(Logger, callerId, string.Join(", ", roles)); + if (Logger.IsEnabled(LogLevel.Debug)) + { + var formattedRoles = string.Join(", ", roles); + LogAuthenticatedServiceCaller(Logger, callerId, formattedRoles); + } return Task.FromResult(AuthenticateResult.Success(ticket)); } diff --git a/src/Testing/LeanCode.IntegrationTestHelpers/LeanCode.IntegrationTestHelpers.csproj b/src/Testing/LeanCode.IntegrationTestHelpers/LeanCode.IntegrationTestHelpers.csproj index 83be8e630..579fa6b2f 100644 --- a/src/Testing/LeanCode.IntegrationTestHelpers/LeanCode.IntegrationTestHelpers.csproj +++ b/src/Testing/LeanCode.IntegrationTestHelpers/LeanCode.IntegrationTestHelpers.csproj @@ -19,8 +19,6 @@ - - diff --git a/test-bed/LeanCode.TestBed.csproj b/test-bed/LeanCode.TestBed.csproj index 33d2f5efe..720fcb253 100644 --- a/test-bed/LeanCode.TestBed.csproj +++ b/test-bed/LeanCode.TestBed.csproj @@ -1,6 +1,6 @@ - net10.0 + net11.0 enable enable false diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 07f23288a..5ebd9a026 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -13,9 +13,13 @@ true true + $(ArtifactsPath)/test-results + $(MSBuildThisFileDirectory)/../TestResults - $(TestingPlatformCommandLineArguments) --report-ctrf --coverage --ignore-exit-code 8 --results-directory $(MSBuildThisFileDirectory)/../TestResults --coverage-output-format cobertura --coverage-settings $(MSBuildThisFileDirectory)/coverage.xml --report-ctrf-filename $(MSBuildProjectName)-$([System.DateTime]::Now.ToString("HH-mm-ss")).json --coverage-output $(MSBuildProjectName)-$([System.DateTime]::Now.ToString("HH-mm-ss")).xml + $(TestingPlatformCommandLineArguments) --report-xunit-ctrf --coverage --ignore-exit-code 8 --results-directory $(LeanCodeTestResultsDirectory) --coverage-output-format cobertura --coverage-settings $(MSBuildThisFileDirectory)/coverage.xml --report-xunit-ctrf-filename $(MSBuildProjectName)-$([System.DateTime]::Now.ToString("HH-mm-ss")).json --coverage-output $(MSBuildProjectName)-$([System.DateTime]::Now.ToString("HH-mm-ss")).xml diff --git a/test/Domain/LeanCode.DomainModels.Tests/Ids/GuidTests.cs b/test/Domain/LeanCode.DomainModels.Tests/Ids/GuidTests.cs index 1f290de62..3274b5026 100644 --- a/test/Domain/LeanCode.DomainModels.Tests/Ids/GuidTests.cs +++ b/test/Domain/LeanCode.DomainModels.Tests/Ids/GuidTests.cs @@ -20,7 +20,7 @@ public class GuidIdTests [SuppressMessage("?", "xUnit2007", Justification = "Cannot use `IRawTypedId` as generic parameter.")] public void Generated_class_implements_ITypedId() { - Assert.IsAssignableFrom(typeof(IRawTypedId), new TestGuidId()); + Assert.IsType(typeof(IRawTypedId), new TestGuidId(), exactMatch: false); } [Fact] diff --git a/test/Domain/LeanCode.DomainModels.Tests/Ids/IntTests.cs b/test/Domain/LeanCode.DomainModels.Tests/Ids/IntTests.cs index f3f7e9ca6..a4504b43e 100644 --- a/test/Domain/LeanCode.DomainModels.Tests/Ids/IntTests.cs +++ b/test/Domain/LeanCode.DomainModels.Tests/Ids/IntTests.cs @@ -15,7 +15,7 @@ public class IntIdTests [SuppressMessage("?", "xUnit2007", Justification = "Cannot use `IRawTypedId` as generic parameter.")] public void Generated_class_implements_ITypedId() { - Assert.IsAssignableFrom(typeof(IRawTypedId), new TestIntId()); + Assert.IsType(typeof(IRawTypedId), new TestIntId(), exactMatch: false); } [Fact] diff --git a/test/Domain/LeanCode.DomainModels.Tests/Ids/LongTests.cs b/test/Domain/LeanCode.DomainModels.Tests/Ids/LongTests.cs index 531c8a74a..f55e4ab5f 100644 --- a/test/Domain/LeanCode.DomainModels.Tests/Ids/LongTests.cs +++ b/test/Domain/LeanCode.DomainModels.Tests/Ids/LongTests.cs @@ -15,7 +15,7 @@ public class LongIdTests [SuppressMessage("?", "xUnit2007", Justification = "Cannot use `IRawTypedId` as generic parameter.")] public void Generated_class_implements_ITypedId() { - Assert.IsAssignableFrom(typeof(IRawTypedId), new TestLongId()); + Assert.IsType(typeof(IRawTypedId), new TestLongId(), exactMatch: false); } [Fact] diff --git a/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedGuidTests.cs b/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedGuidTests.cs index 656e90dc0..6038d830b 100644 --- a/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedGuidTests.cs +++ b/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedGuidTests.cs @@ -22,7 +22,7 @@ public class PrefixedGuidIdTests [SuppressMessage("?", "xUnit2007", Justification = "Cannot use `IPrefixedTypedId` as generic parameter.")] public void Generated_class_implements_ITypedId() { - Assert.IsAssignableFrom(typeof(IPrefixedTypedId), new TestPrefixedGuidId()); + Assert.IsType(typeof(IPrefixedTypedId), new TestPrefixedGuidId(), exactMatch: false); } [Fact] diff --git a/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedStringTests.cs b/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedStringTests.cs index d2d135c93..cf495d872 100644 --- a/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedStringTests.cs +++ b/test/Domain/LeanCode.DomainModels.Tests/Ids/PrefixedStringTests.cs @@ -21,7 +21,7 @@ public class PrefixedStringIdTests [SuppressMessage("?", "xUnit2007", Justification = "Cannot use `IPrefixedTypedId` as generic parameter.")] public void Generated_class_implements_ITypedId() { - Assert.IsAssignableFrom(typeof(IPrefixedTypedId), new TestPrefixedStringId()); + Assert.IsType(typeof(IPrefixedTypedId), new TestPrefixedStringId(), exactMatch: false); } [Fact] diff --git a/test/Domain/LeanCode.DomainModels.Tests/Ids/RawStringTests.cs b/test/Domain/LeanCode.DomainModels.Tests/Ids/RawStringTests.cs index 60e5f7908..ec07464fb 100644 --- a/test/Domain/LeanCode.DomainModels.Tests/Ids/RawStringTests.cs +++ b/test/Domain/LeanCode.DomainModels.Tests/Ids/RawStringTests.cs @@ -20,7 +20,7 @@ public class RawStringIdTests [SuppressMessage("?", "xUnit2007", Justification = "Cannot use `IRawStringTypedId` as generic parameter.")] public void Generated_class_implements_ITypedId() { - Assert.IsAssignableFrom(typeof(IRawStringTypedId), new TestRawStringId()); + Assert.IsType(typeof(IRawStringTypedId), new TestRawStringId(), exactMatch: false); } [Fact] diff --git a/test/Infrastructure/LeanCode.Azure.Tests/AzureStorageAuditLogIntegrationTests.cs b/test/Infrastructure/LeanCode.Azure.Tests/AzureStorageAuditLogIntegrationTests.cs index 4a40906d0..829ddc6b9 100644 --- a/test/Infrastructure/LeanCode.Azure.Tests/AzureStorageAuditLogIntegrationTests.cs +++ b/test/Infrastructure/LeanCode.Azure.Tests/AzureStorageAuditLogIntegrationTests.cs @@ -200,7 +200,14 @@ await storage.StoreEventAsync( var blobsFound = 0; var linesFound = 0; - await foreach (var b in containerClient.GetBlobsAsync(BlobTraits.All, BlobStates.All, $"{type}/{id}")) + await foreach ( + var b in containerClient.GetBlobsAsync( + BlobTraits.All, + BlobStates.All, + $"{type}/{id}", + CancellationToken.None + ) + ) { var blockBlob = containerClient.GetBlobClient(b.Name); using var stream = new MemoryStream(); diff --git a/test/Infrastructure/LeanCode.AzureIdentity.Tests/Properties.cs b/test/Infrastructure/LeanCode.AzureIdentity.Tests/Properties.cs index e03b09461..1176b0c4a 100644 --- a/test/Infrastructure/LeanCode.AzureIdentity.Tests/Properties.cs +++ b/test/Infrastructure/LeanCode.AzureIdentity.Tests/Properties.cs @@ -1,4 +1,2 @@ -using Xunit; - // This is necessary, because some tests rely on setting environmental variables, which might be overwritten by tests. -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] +[assembly: Xunit.v3.Parallelization(Mode = Xunit.Sdk.ParallelMode.None)] diff --git a/test/Infrastructure/LeanCode.Firebase.FCM.Tests/FCMClientTests.cs b/test/Infrastructure/LeanCode.Firebase.FCM.Tests/FCMClientTests.cs index 1a955a0c8..765747f84 100644 --- a/test/Infrastructure/LeanCode.Firebase.FCM.Tests/FCMClientTests.cs +++ b/test/Infrastructure/LeanCode.Firebase.FCM.Tests/FCMClientTests.cs @@ -73,7 +73,7 @@ public async Task Gets_user_tokens_when_sending_the_message() await client.SendToUserAsync(UserId, message); - var token = Assert.Single(message.Tokens); + var token = Assert.Single(message.Fids); Assert.Equal(Token, token); } } diff --git a/test/LeanCode.IntegrationTests/PostgresOnlyFactAttribute.cs b/test/LeanCode.IntegrationTests/PostgresOnlyFactAttribute.cs index 4d3da9f28..727aa56e2 100644 --- a/test/LeanCode.IntegrationTests/PostgresOnlyFactAttribute.cs +++ b/test/LeanCode.IntegrationTests/PostgresOnlyFactAttribute.cs @@ -3,11 +3,21 @@ namespace LeanCode.IntegrationTests; -public sealed class PostgresOnlyFactAttribute( - [CallerFilePath] string? sourceFilePath = null, - [CallerLineNumber] int sourceLineNumber = 0 -) : IntegrationFactAttribute(sourceFilePath, sourceLineNumber) +public sealed class PostgresOnlyFactAttribute : IntegrationFactAttribute { + public static bool IsPostgres => Environment.GetEnvironmentVariable(TestDatabaseConfig.ConfigEnvName) == "postgres"; + + public PostgresOnlyFactAttribute( + [CallerFilePath] string? sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = 0 + ) + : base(sourceFilePath, sourceLineNumber) + { + Skip = "Test requires PostgreSQL."; + SkipType = typeof(PostgresOnlyFactAttribute); + SkipUnless = nameof(IsPostgres); + } + public override IReadOnlyCollection> GetTraits() => base.GetTraits().Append(new("database", "postgres")).ToList(); } diff --git a/test/LeanCode.IntegrationTests/TestApp.cs b/test/LeanCode.IntegrationTests/TestApp.cs index 87e649e9c..1a91b3853 100644 --- a/test/LeanCode.IntegrationTests/TestApp.cs +++ b/test/LeanCode.IntegrationTests/TestApp.cs @@ -37,7 +37,7 @@ protected override IEnumerable GetTestAssemblies() protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); - builder.UseSolutionRelativeContentRoot("test/LeanCode.IntegrationTests"); + builder.UseContentRoot(AppContext.BaseDirectory); } protected override IHostBuilder CreateHostBuilder() diff --git a/test/LeanCode.IntegrationTests/docker/Dockerfile b/test/LeanCode.IntegrationTests/docker/Dockerfile index 6b7a54a42..7d9370194 100644 --- a/test/LeanCode.IntegrationTests/docker/Dockerfile +++ b/test/LeanCode.IntegrationTests/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 +FROM mcr.microsoft.com/dotnet/sdk:11.0 WORKDIR /app/code/test/LeanCode.IntegrationTests -ENTRYPOINT dotnet run -- --explicit on +ENTRYPOINT ["dotnet", "run", "--", "--explicit", "on"] diff --git a/test/LeanCode.IntegrationTests/docker/Dockerfile.watch b/test/LeanCode.IntegrationTests/docker/Dockerfile.watch index c8c3dfedb..b3892bea9 100644 --- a/test/LeanCode.IntegrationTests/docker/Dockerfile.watch +++ b/test/LeanCode.IntegrationTests/docker/Dockerfile.watch @@ -1,5 +1,5 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 +FROM mcr.microsoft.com/dotnet/sdk:11.0 -# Wait for SQL Server first WORKDIR /app/code/test/LeanCode.IntegrationTests -ENTRYPOINT dotnet restore && dotnet watch test -- --explicit on +ENTRYPOINT dotnet restore && \ + dotnet watch test -- --explicit on diff --git a/test/LeanCode.IntegrationTests/docker/docker-compose.yml b/test/LeanCode.IntegrationTests/docker/docker-compose.yml index 9ad141e03..04811d403 100755 --- a/test/LeanCode.IntegrationTests/docker/docker-compose.yml +++ b/test/LeanCode.IntegrationTests/docker/docker-compose.yml @@ -9,6 +9,9 @@ services: - ../../..:/app/code - ~/.nuget:/home/dotnet/.nuget environment: + UseArtifactsOutput: "true" + ArtifactsPath: /tmp/corelibrary-artifacts + ASPNETCORE_TEST_CONTENTROOT_LEANCODE_INTEGRATIONTESTS: /app/code/test/LeanCode.IntegrationTests LeanCodeIntegrationTests__Database: ${DB:?} SqlServer__ConnectionStringBase: Server=sqlserver;User Id=sa;Password=Passw12#;Encrypt=false Postgres__ConnectionStringBase: Host=postgres;Username=postgres;Password=Passw12#;SSL Mode=Disable @@ -24,6 +27,9 @@ services: - ../../..:/app/code - ~/.nuget:/home/dotnet/.nuget environment: + UseArtifactsOutput: "true" + ArtifactsPath: /tmp/corelibrary-artifacts + ASPNETCORE_TEST_CONTENTROOT_LEANCODE_INTEGRATIONTESTS: /app/code/test/LeanCode.IntegrationTests LeanCodeIntegrationTests__Database: ${DB:?} SqlServer__ConnectionStringBase: Server=sqlserver;User Id=sa;Password=Passw12#;Encrypt=false Postgres__ConnectionStringBase: Host=postgres;Username=postgres;Password=Passw12#;SSL Mode=Disable @@ -41,7 +47,7 @@ services: - "1433:1433" postgres: - image: postgres:17 + image: postgres:18 environment: POSTGRES_PASSWORD: Passw12# ports: