From a772d7c615aa0c73b8df149ddb454ccfbe33fcdf Mon Sep 17 00:00:00 2001 From: "Serhii A. Hrytsenko" Date: Wed, 22 Jan 2025 23:26:29 -0500 Subject: [PATCH] Fix formatting and test Signed-off-by: Serhii A. Hrytsenko --- .../HomeInventory.Api/ApplicationModules.cs | 2 +- .../HomeInventory.Api/LoggingModule.cs | 4 +- .../DictionaryExtensions.cs | 2 +- .../Services/PersistenceHealthCheck.cs | 4 +- .../Assertions/ServiceCollectionAssertions.cs | 48 ++++--------------- .../BaseIntegrationTest.cs | 6 ++- .../UserManagementApiTests.cs | 6 ++- .../Architecture/ArchitectureTests.cs | 34 ++++++++++++- .../WebDependencyInjectionTests.cs | 19 ++++++-- .../Systems/Mapping/ContractsMappingsTests.cs | 24 +++++----- .../UserManagementContractsMappingsTests.cs | 29 ++++++----- .../UserManagementModelMappingsTests.cs | 33 ++++++------- ...rHandling.cs => WebErrorHandlingModule.cs} | 2 +- .../HomeInventory.sln.DotSettings | 1 + 14 files changed, 116 insertions(+), 98 deletions(-) rename src/HomeInventory/HomeInventory.Web/ErrorHandling/{WebErrorHandling.cs => WebErrorHandlingModule.cs} (97%) diff --git a/src/HomeInventory/HomeInventory.Api/ApplicationModules.cs b/src/HomeInventory/HomeInventory.Api/ApplicationModules.cs index d6772c31..6419a5a5 100644 --- a/src/HomeInventory/HomeInventory.Api/ApplicationModules.cs +++ b/src/HomeInventory/HomeInventory.Api/ApplicationModules.cs @@ -26,7 +26,7 @@ public ApplicationModules() Add(new LoggingModule()); Add(new ContractsValidationsModule()); Add(new ContractsUserManagementValidatorsModule()); - Add(new WebErrorHandling()); + Add(new WebErrorHandlingModule()); Add(new WebAuthenticationModule()); Add(new DynamicWebAuthorizationModule()); Add(new WebSwaggerModule()); diff --git a/src/HomeInventory/HomeInventory.Api/LoggingModule.cs b/src/HomeInventory/HomeInventory.Api/LoggingModule.cs index caf72c75..9445910e 100644 --- a/src/HomeInventory/HomeInventory.Api/LoggingModule.cs +++ b/src/HomeInventory/HomeInventory.Api/LoggingModule.cs @@ -1,10 +1,12 @@ -using System.Globalization; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using HomeInventory.Modules.Interfaces; using Serilog; using Serilog.Core; namespace HomeInventory.Api; +[SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Module")] public sealed class LoggingModule : BaseModule { public static Logger CreateBootstrapLogger() => diff --git a/src/HomeInventory/HomeInventory.Core/DictionaryExtensions.cs b/src/HomeInventory/HomeInventory.Core/DictionaryExtensions.cs index a995693a..1a6ed1ee 100644 --- a/src/HomeInventory/HomeInventory.Core/DictionaryExtensions.cs +++ b/src/HomeInventory/HomeInventory.Core/DictionaryExtensions.cs @@ -37,7 +37,7 @@ public static async ValueTask GetOrAddAsync(this dictionary.TryGetValue(key, out var value) ? (TResult)value! : await dictionary.AddAsync(key, createValueFunc); - + private static async Task AddAsync(this IDictionary dictionary, TKey key, Func> createValueFunc) where TKey : notnull where TResult : TValue diff --git a/src/HomeInventory/HomeInventory.Infrastructure/Services/PersistenceHealthCheck.cs b/src/HomeInventory/HomeInventory.Infrastructure/Services/PersistenceHealthCheck.cs index a1526883..40bb6f3f 100644 --- a/src/HomeInventory/HomeInventory.Infrastructure/Services/PersistenceHealthCheck.cs +++ b/src/HomeInventory/HomeInventory.Infrastructure/Services/PersistenceHealthCheck.cs @@ -27,7 +27,7 @@ protected override async ValueTask CheckHealthAsync(Cancellat { IsFailed = true, Description = "Cannot connect to the database", - Data = + Data = { ["provider"] = ProviderName, }, @@ -53,7 +53,7 @@ private async Task CheckRelationalStateAsync(DatabaseFacade d { IsFailed = true, Description = $"Database has {count} pending migrations", - Data = + Data = { ["provider"] = ProviderName, ["migrations.count"] = count, diff --git a/src/HomeInventory/HomeInventory.Tests.Framework/Assertions/ServiceCollectionAssertions.cs b/src/HomeInventory/HomeInventory.Tests.Framework/Assertions/ServiceCollectionAssertions.cs index c1b60dad..276813d0 100644 --- a/src/HomeInventory/HomeInventory.Tests.Framework/Assertions/ServiceCollectionAssertions.cs +++ b/src/HomeInventory/HomeInventory.Tests.Framework/Assertions/ServiceCollectionAssertions.cs @@ -8,56 +8,32 @@ public sealed class ServiceCollectionAssertions(IServiceCollection value, Assert { public AndWhichConstraint> ContainConfigureOptions(IServiceProvider provider) where TOptions : class => - ContainSingleTransient>(provider); + ContainSingle>(provider, ServiceLifetime.Transient); public AndWhichConstraint ContainConfigureOptions() where TOptions : class => ContainSingleTransient>(); - public AndWhichConstraint ContainSingleTransient(IServiceProvider provider) - where T : class => - ContainSingle(provider, ServiceLifetime.Transient); - public AndWhichConstraint ContainSingleTransient() where T : class => ContainSingle(ServiceLifetime.Transient); - public AndWhichConstraint ContainSingleSingleton(IServiceProvider provider) - where T : class => - ContainSingle(provider, ServiceLifetime.Singleton); - public AndWhichConstraint ContainSingleSingleton() where T : class => ContainSingle(ServiceLifetime.Singleton); - public AndWhichConstraint ContainSingleScoped(IServiceProvider provider) - where T : class => - ContainSingle(provider, ServiceLifetime.Scoped); - public AndWhichConstraint ContainSingleScoped() where T : class => ContainSingle(ServiceLifetime.Scoped); - public AndConstraint ContainSingleton(IServiceProvider provider) - where T : class => - Contain(provider, ServiceLifetime.Singleton); - public AndConstraint ContainSingleton() where T : class => Contain(ServiceLifetime.Singleton); - public AndConstraint ContainTransient(IServiceProvider provider) - where T : class => - Contain(provider, ServiceLifetime.Transient); - public AndConstraint ContainTransient() where T : class => Contain(ServiceLifetime.Transient); - public AndConstraint ContainScoped(IServiceProvider provider) - where T : class => - Contain(provider, ServiceLifetime.Scoped); - public AndConstraint ContainScoped() where T : class => Contain(ServiceLifetime.Scoped); @@ -73,26 +49,18 @@ private AndWhichConstraint Conta private AndWhichConstraint ContainSingle(Type serviceType, ServiceLifetime lifetime) { - var matched = Subject.Where(d => d.ServiceType == serviceType && !d.IsKeyedService) - .Should().ContainSingle(d => d.Lifetime == lifetime, $"Expected {nameof(ServiceDescriptor.Lifetime)} of the {serviceType.FullName} to be {lifetime}") + var matched = Subject.Should().ContainSingle(d => d.ServiceType == serviceType && !d.IsKeyedService && d.Lifetime == lifetime, $"expected single non-keyed {serviceType.FullName} with {nameof(ServiceDescriptor.Lifetime)} = {lifetime}.") .Subject; return new(this, matched); } - private AndWhichConstraint Contain(Type serviceType, ServiceLifetime lifetime) => Contain(d => d.ServiceType == serviceType && d.Lifetime == lifetime); - - private AndConstraint Contain(IServiceProvider provider, ServiceLifetime lifetime) - where T : class => - Contain(lifetime) - .And.AllSatisfy(d => - { - if (d.ServiceType == typeof(T) && d.Lifetime == lifetime) - { - d.GetInstance(provider).Should().BeAssignableTo(); - } - }); + private AndConstraint Contain(Type serviceType, ServiceLifetime lifetime) + { + Subject.Should().Contain(d => d.ServiceType == serviceType && !d.IsKeyedService && d.Lifetime == lifetime, $"expected non-keyed {serviceType.FullName} with {nameof(ServiceDescriptor.Lifetime)} = {lifetime}."); + return new(this); + } - private AndWhichConstraint Contain(ServiceLifetime lifetime) + private AndConstraint Contain(ServiceLifetime lifetime) where T : class => Contain(typeof(T), lifetime); } diff --git a/src/HomeInventory/HomeInventory.Tests.Integration/BaseIntegrationTest.cs b/src/HomeInventory/HomeInventory.Tests.Integration/BaseIntegrationTest.cs index fc74b4a8..5683a21b 100644 --- a/src/HomeInventory/HomeInventory.Tests.Integration/BaseIntegrationTest.cs +++ b/src/HomeInventory/HomeInventory.Tests.Integration/BaseIntegrationTest.cs @@ -1,4 +1,5 @@ -using System.Net.Http.Json; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http.Json; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -7,7 +8,8 @@ namespace HomeInventory.Tests.Integration; [IntegrationTest] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "See InitializeDisposables")] +[SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "See InitializeDisposables")] +[SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Test")] public abstract class BaseIntegrationTest : BaseTest { private readonly WebApplicationFactory _appFactory = new(); diff --git a/src/HomeInventory/HomeInventory.Tests.Integration/UserManagementApiTests.cs b/src/HomeInventory/HomeInventory.Tests.Integration/UserManagementApiTests.cs index c9b7d997..72645d06 100644 --- a/src/HomeInventory/HomeInventory.Tests.Integration/UserManagementApiTests.cs +++ b/src/HomeInventory/HomeInventory.Tests.Integration/UserManagementApiTests.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Net.Http.Json; using FluentAssertions.Execution; using Flurl; @@ -11,7 +12,8 @@ namespace HomeInventory.Tests.Integration; -public class UserManagementApiTests : BaseIntegrationTest +[SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Test")] +public sealed class UserManagementApiTests : BaseIntegrationTest { private static readonly string _registerRoute = "/".AppendPathSegments("api", "users", "manage", "register"); private readonly JsonContent _content; diff --git a/src/HomeInventory/HomeInventory.Tests/Architecture/ArchitectureTests.cs b/src/HomeInventory/HomeInventory.Tests/Architecture/ArchitectureTests.cs index 2a695c6e..232fef75 100644 --- a/src/HomeInventory/HomeInventory.Tests/Architecture/ArchitectureTests.cs +++ b/src/HomeInventory/HomeInventory.Tests/Architecture/ArchitectureTests.cs @@ -1,6 +1,9 @@ using NetArchTest.Rules; using System.Reflection; using System.Runtime.CompilerServices; +using HomeInventory.Api; +using HomeInventory.Modules.Interfaces; +using Mono.Cecil; namespace HomeInventory.Tests.Architecture; @@ -19,7 +22,7 @@ public void Should_NotHaveBadDependencies(string name, string[] allowed) var result = conditionList.GetResult(); var failing = result.FailingTypes?.Where(static t => - t.GetCustomAttribute() is null + t.GetCustomAttribute() is null && t.FullName?.StartsWith("<>", StringComparison.Ordinal) != true && !t.Name.EndsWith("Module", StringComparison.Ordinal) && !t.Name.EndsWith("Modules", StringComparison.Ordinal) @@ -81,4 +84,33 @@ public void Controllers_Should_HaveDependencyOn_Automapper() result.FailingTypeNames.Should().BeNullOrEmpty(); } + + [Fact] + public void Modules_Should_EndWithModule() + { + _ = new ApplicationModules(); + var result = Types.InCurrentDomain() + .That().MeetCustomRule(new RuntimeClassesRule()) + .And().AreClasses() + .And().AreNotAbstract() + .And().Inherit(typeof(BaseModule)) + .Should().BeSealed() + .And().HaveNameEndingWith("Module", StringComparison.Ordinal) + .GetResult(); + result.FailingTypeNames.Should().BeNullOrEmpty(); + } +} + +file sealed class RuntimeClassesRule : ICustomRule +{ + public bool MeetsRule(TypeDefinition type) + { + if (type.Namespace is not { } ns) + { + return false; + } + + return ns.StartsWith("HomeInventory", StringComparison.Ordinal) + && !ns.Contains("Test", StringComparison.Ordinal); + } } diff --git a/src/HomeInventory/HomeInventory.Tests/DependencyInjection/WebDependencyInjectionTests.cs b/src/HomeInventory/HomeInventory.Tests/DependencyInjection/WebDependencyInjectionTests.cs index 2d8ec3ff..f8f04cb1 100644 --- a/src/HomeInventory/HomeInventory.Tests/DependencyInjection/WebDependencyInjectionTests.cs +++ b/src/HomeInventory/HomeInventory.Tests/DependencyInjection/WebDependencyInjectionTests.cs @@ -3,16 +3,19 @@ using HomeInventory.Application.Framework; using HomeInventory.Application.Interfaces.Authentication; using HomeInventory.Modules; +using HomeInventory.Web; using HomeInventory.Web.Authentication; using HomeInventory.Web.Authorization.Dynamic; using HomeInventory.Web.Configuration; using HomeInventory.Web.ErrorHandling; +using HomeInventory.Web.Framework; using HomeInventory.Web.Framework.Infrastructure; +using HomeInventory.Web.Mapping; +using HomeInventory.Web.OpenApi; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -44,10 +47,19 @@ public WebDependencyInjectionTests() Services.AddSingleton(env); Services.AddSingleton(env); - _host = new([]); + _host = new([ + new WebCarterSupportModule(), + new WebAuthenticationModule(), + new WebHealthCheckModule(), + new WebErrorHandlingModule(), + new ApplicationMappingModule(), + new WebMappingModule(), + new WebSwaggerModule(), + new DynamicWebAuthorizationModule(), + ]); } - [Fact(Skip = "Will be removed in a future version")] + [Fact] public async Task ShouldRegister() { await _host.AddModulesAsync(Services, _configuration); @@ -63,7 +75,6 @@ public async Task ShouldRegister() Services.Should().ContainSingleTransient(); Services.Should().ContainSingleTransient(); Services.Should().ContainSingleton(); - Services.Should().ContainSingleSingleton(); Services.Should().ContainSingleTransient(); Services.Should().ContainSingleSingleton(); Services.Should().ContainTransient(); diff --git a/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/ContractsMappingsTests.cs b/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/ContractsMappingsTests.cs index 46f750f2..715aa42f 100644 --- a/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/ContractsMappingsTests.cs +++ b/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/ContractsMappingsTests.cs @@ -1,4 +1,5 @@ -using HomeInventory.Application.Cqrs.Queries.Authenticate; +using AutoFixture.Kernel; +using HomeInventory.Application.Cqrs.Queries.Authenticate; using HomeInventory.Contracts; using HomeInventory.Domain.UserManagement.ValueObjects; using HomeInventory.Web.Mapping; @@ -10,25 +11,24 @@ public class ContractsMappingsTests : BaseMappingsTests { [Theory] [MemberData(nameof(Data))] - public void ShouldMap(object instance, Type destination) + public void ShouldMap(Type source, Type destination) { + var fixture = new Fixture(); + fixture.CustomizeId(); + fixture.CustomizeEmail(); + var instance = fixture.Create(source, new SpecimenContext(fixture)); + var sut = CreateSut(); - var source = instance.GetType(); var target = sut.Map(instance, source, destination); target.Should().BeAssignableTo(destination); } - public static TheoryData Data() - { - var fixture = new Fixture(); - fixture.CustomizeId(); - fixture.CustomizeEmail(); - return new() + public static TheoryData Data() => + new() { - { fixture.Create(), typeof(AuthenticateQuery) }, - { fixture.Create(), typeof(LoginResponse) }, + { typeof(LoginRequest), typeof(AuthenticateQuery) }, + { typeof(AuthenticateResult), typeof(LoginResponse) }, }; - } } diff --git a/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementContractsMappingsTests.cs b/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementContractsMappingsTests.cs index ab201883..d9255fe2 100644 --- a/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementContractsMappingsTests.cs +++ b/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementContractsMappingsTests.cs @@ -1,4 +1,5 @@ -using HomeInventory.Application.UserManagement.Interfaces.Commands; +using AutoFixture.Kernel; +using HomeInventory.Application.UserManagement.Interfaces.Commands; using HomeInventory.Application.UserManagement.Interfaces.Queries; using HomeInventory.Contracts.UserManagement; using HomeInventory.Domain.UserManagement.ValueObjects; @@ -11,28 +12,26 @@ public class UserManagementContractsMappingsTests : BaseMappingsTests { [Theory] [MemberData(nameof(Data))] - public void ShouldMap(object instance, Type destination) + public void ShouldMap(Type source, Type destination) { + var fixture = new Fixture(); + fixture.CustomizeId(); + fixture.CustomizeEmail(); + var instance = fixture.Create(source, new SpecimenContext(fixture)); var sut = CreateSut(); - var source = instance.GetType(); var target = sut.Map(instance, source, destination); target.Should().BeAssignableTo(destination); } - public static TheoryData Data() - { - var fixture = new Fixture(); - fixture.CustomizeId(); - fixture.CustomizeEmail(); - return new() + public static TheoryData Data() => + new() { - { fixture.Create(), typeof(Ulid) }, - { fixture.Create(), typeof(string) }, - { fixture.Create(), typeof(RegisterCommand) }, - { fixture.Create(), typeof(UserIdQuery) }, - { fixture.Create(), typeof(RegisterResponse) }, + { typeof(UserId), typeof(Ulid) }, + { typeof(Email), typeof(string) }, + { typeof(RegisterRequest), typeof(RegisterCommand) }, + { typeof(RegisterRequest), typeof(UserIdQuery) }, + { typeof(UserIdResult), typeof(RegisterResponse) }, }; - } } diff --git a/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementModelMappingsTests.cs b/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementModelMappingsTests.cs index dda44966..112e9bba 100644 --- a/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementModelMappingsTests.cs +++ b/src/HomeInventory/HomeInventory.Tests/Systems/Mapping/UserManagementModelMappingsTests.cs @@ -1,4 +1,5 @@ -using HomeInventory.Api; +using AutoFixture.Kernel; +using HomeInventory.Api; using HomeInventory.Domain; using HomeInventory.Domain.UserManagement.Aggregates; using HomeInventory.Domain.UserManagement.ValueObjects; @@ -32,10 +33,15 @@ public override async Task InitializeAsync() [Theory] [MemberData(nameof(MapData))] - public void ShouldMap(object instance, Type destination) + public void ShouldMap(Type source, Type destination) { + var timestamp = new DateTimeOffset(new(2024, 01, 01), TimeOnly.MinValue, TimeSpan.Zero); + var fixture = new Fixture(); + fixture.CustomizeId(timestamp); + fixture.CustomizeEmail(timestamp); + var instance = fixture.Create(source, new SpecimenContext(fixture)); + var sut = CreateSut(); - var source = instance.GetType(); var target = sut.Map(instance, source, destination); @@ -68,29 +74,24 @@ public void ShouldProjectUserModelToUser() target.Should().ContainSingle(); } - public static TheoryData MapData() + public static TheoryData MapData() { - var timestamp = new DateTimeOffset(new(2024, 01, 01), TimeOnly.MinValue, TimeSpan.Zero); - var fixture = new Fixture(); - fixture.CustomizeId(timestamp); - fixture.CustomizeEmail(timestamp); - - var data = new TheoryData(); + var data = new TheoryData(); - Add(fixture, data); + Add(data); - Add(fixture, data); + Add(data); - Add(fixture, data); + Add(data); return data; - static void Add(IFixture fixture, TheoryData data) + static void Add(TheoryData data) where T1 : notnull where T2 : notnull { - data.Add(fixture.Create(), typeof(T2)); - data.Add(fixture.Create(), typeof(T1)); + data.Add(typeof(T1), typeof(T2)); + data.Add(typeof(T2), typeof(T1)); } } } diff --git a/src/HomeInventory/HomeInventory.Web/ErrorHandling/WebErrorHandling.cs b/src/HomeInventory/HomeInventory.Web/ErrorHandling/WebErrorHandlingModule.cs similarity index 97% rename from src/HomeInventory/HomeInventory.Web/ErrorHandling/WebErrorHandling.cs rename to src/HomeInventory/HomeInventory.Web/ErrorHandling/WebErrorHandlingModule.cs index 36e5e9ae..22b2c236 100644 --- a/src/HomeInventory/HomeInventory.Web/ErrorHandling/WebErrorHandling.cs +++ b/src/HomeInventory/HomeInventory.Web/ErrorHandling/WebErrorHandlingModule.cs @@ -12,7 +12,7 @@ namespace HomeInventory.Web.ErrorHandling; -public sealed class WebErrorHandling : BaseModule +public sealed class WebErrorHandlingModule : BaseModule { public override async Task AddServicesAsync(IModuleServicesContext context) { diff --git a/src/HomeInventory/HomeInventory.sln.DotSettings b/src/HomeInventory/HomeInventory.sln.DotSettings index 0a4e0476..fc644c5f 100644 --- a/src/HomeInventory/HomeInventory.sln.DotSettings +++ b/src/HomeInventory/HomeInventory.sln.DotSettings @@ -1,2 +1,3 @@  + UI True \ No newline at end of file