Skip to content

Commit

Permalink
Fix formatting and test
Browse files Browse the repository at this point in the history
Signed-off-by: Serhii A. Hrytsenko <[email protected]>
  • Loading branch information
gritcsenko committed Jan 23, 2025
1 parent 688888a commit a772d7c
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 98 deletions.
2 changes: 1 addition & 1 deletion src/HomeInventory/HomeInventory.Api/ApplicationModules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
4 changes: 3 additions & 1 deletion src/HomeInventory/HomeInventory.Api/LoggingModule.cs
Original file line number Diff line number Diff line change
@@ -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() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static async ValueTask<TResult> GetOrAddAsync<TKey, TValue, TResult>(this
dictionary.TryGetValue(key, out var value)
? (TResult)value!
: await dictionary.AddAsync(key, createValueFunc);

private static async Task<TResult> AddAsync<TKey, TValue, TResult>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, Task<TResult>> createValueFunc)
where TKey : notnull
where TResult : TValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected override async ValueTask<HealthCheckStatus> CheckHealthAsync(Cancellat
{
IsFailed = true,
Description = "Cannot connect to the database",
Data =
Data =
{
["provider"] = ProviderName,
},
Expand All @@ -53,7 +53,7 @@ private async Task<HealthCheckStatus> CheckRelationalStateAsync(DatabaseFacade d
{
IsFailed = true,
Description = $"Database has {count} pending migrations",
Data =
Data =
{
["provider"] = ProviderName,
["migrations.count"] = count,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,32 @@ public sealed class ServiceCollectionAssertions(IServiceCollection value, Assert
{
public AndWhichConstraint<ObjectAssertions, IConfigureOptions<TOptions>> ContainConfigureOptions<TOptions>(IServiceProvider provider)
where TOptions : class =>
ContainSingleTransient<IConfigureOptions<TOptions>>(provider);
ContainSingle<IConfigureOptions<TOptions>>(provider, ServiceLifetime.Transient);

public AndWhichConstraint<ServiceCollectionAssertions, ServiceDescriptor> ContainConfigureOptions<TOptions>()
where TOptions : class =>
ContainSingleTransient<IConfigureOptions<TOptions>>();

public AndWhichConstraint<ObjectAssertions, T> ContainSingleTransient<T>(IServiceProvider provider)
where T : class =>
ContainSingle<T>(provider, ServiceLifetime.Transient);

public AndWhichConstraint<ServiceCollectionAssertions, ServiceDescriptor> ContainSingleTransient<T>()
where T : class =>
ContainSingle<T>(ServiceLifetime.Transient);

public AndWhichConstraint<ObjectAssertions, T> ContainSingleSingleton<T>(IServiceProvider provider)
where T : class =>
ContainSingle<T>(provider, ServiceLifetime.Singleton);

public AndWhichConstraint<ServiceCollectionAssertions, ServiceDescriptor> ContainSingleSingleton<T>()
where T : class =>
ContainSingle<T>(ServiceLifetime.Singleton);

public AndWhichConstraint<ObjectAssertions, T> ContainSingleScoped<T>(IServiceProvider provider)
where T : class =>
ContainSingle<T>(provider, ServiceLifetime.Scoped);

public AndWhichConstraint<ServiceCollectionAssertions, ServiceDescriptor> ContainSingleScoped<T>()
where T : class =>
ContainSingle<T>(ServiceLifetime.Scoped);

public AndConstraint<ServiceCollectionAssertions> ContainSingleton<T>(IServiceProvider provider)
where T : class =>
Contain<T>(provider, ServiceLifetime.Singleton);

public AndConstraint<ServiceCollectionAssertions> ContainSingleton<T>()
where T : class =>
Contain<T>(ServiceLifetime.Singleton);

public AndConstraint<ServiceCollectionAssertions> ContainTransient<T>(IServiceProvider provider)
where T : class =>
Contain<T>(provider, ServiceLifetime.Transient);

public AndConstraint<ServiceCollectionAssertions> ContainTransient<T>()
where T : class =>
Contain<T>(ServiceLifetime.Transient);

public AndConstraint<ServiceCollectionAssertions> ContainScoped<T>(IServiceProvider provider)
where T : class =>
Contain<T>(provider, ServiceLifetime.Scoped);

public AndConstraint<ServiceCollectionAssertions> ContainScoped<T>()
where T : class =>
Contain<T>(ServiceLifetime.Scoped);
Expand All @@ -73,26 +49,18 @@ private AndWhichConstraint<ServiceCollectionAssertions, ServiceDescriptor> Conta

private AndWhichConstraint<ServiceCollectionAssertions, ServiceDescriptor> 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<ServiceCollectionAssertions, ServiceDescriptor> Contain(Type serviceType, ServiceLifetime lifetime) => Contain(d => d.ServiceType == serviceType && d.Lifetime == lifetime);

private AndConstraint<ServiceCollectionAssertions> Contain<T>(IServiceProvider provider, ServiceLifetime lifetime)
where T : class =>
Contain<T>(lifetime)
.And.AllSatisfy(d =>
{
if (d.ServiceType == typeof(T) && d.Lifetime == lifetime)
{
d.GetInstance(provider).Should().BeAssignableTo<T>();
}
});
private AndConstraint<ServiceCollectionAssertions> 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<ServiceCollectionAssertions, ServiceDescriptor> Contain<T>(ServiceLifetime lifetime)
private AndConstraint<ServiceCollectionAssertions> Contain<T>(ServiceLifetime lifetime)
where T : class =>
Contain(typeof(T), lifetime);
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Api.AppBuilder> _appFactory = new();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http.Json;
using FluentAssertions.Execution;
using Flurl;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<CompilerGeneratedAttribute>() is null
t.GetCustomAttribute<CompilerGeneratedAttribute>() is null
&& t.FullName?.StartsWith("<>", StringComparison.Ordinal) != true
&& !t.Name.EndsWith("Module", StringComparison.Ordinal)
&& !t.Name.EndsWith("Modules", StringComparison.Ordinal)
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,10 +47,19 @@ public WebDependencyInjectionTests()
Services.AddSingleton(env);
Services.AddSingleton<IHostEnvironment>(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);
Expand All @@ -63,7 +75,6 @@ public async Task ShouldRegister()
Services.Should().ContainSingleTransient<IProblemDetailsFactory>();
Services.Should().ContainSingleTransient<IMapper>();
Services.Should().ContainSingleton<IMappingAssemblySource>();
Services.Should().ContainSingleSingleton<IControllerFactory>();
Services.Should().ContainSingleTransient<ISwaggerProvider>();
Services.Should().ContainSingleSingleton<PermissionList>();
Services.Should().ContainTransient<IAuthorizationHandler>();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<UserId>();
fixture.CustomizeEmail();
var instance = fixture.Create(source, new SpecimenContext(fixture));

var sut = CreateSut<ContractsMappings>();
var source = instance.GetType();

var target = sut.Map(instance, source, destination);

target.Should().BeAssignableTo(destination);
}

public static TheoryData<object, Type> Data()
{
var fixture = new Fixture();
fixture.CustomizeId<UserId>();
fixture.CustomizeEmail();
return new()
public static TheoryData<Type, Type> Data() =>
new()
{
{ fixture.Create<LoginRequest>(), typeof(AuthenticateQuery) },
{ fixture.Create<AuthenticateResult>(), typeof(LoginResponse) },
{ typeof(LoginRequest), typeof(AuthenticateQuery) },
{ typeof(AuthenticateResult), typeof(LoginResponse) },
};
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<UserId>();
fixture.CustomizeEmail();
var instance = fixture.Create(source, new SpecimenContext(fixture));
var sut = CreateSut<UserManagementContractsMappings>();
var source = instance.GetType();

var target = sut.Map(instance, source, destination);

target.Should().BeAssignableTo(destination);
}

public static TheoryData<object, Type> Data()
{
var fixture = new Fixture();
fixture.CustomizeId<UserId>();
fixture.CustomizeEmail();
return new()
public static TheoryData<Type, Type> Data() =>
new()
{
{ fixture.Create<UserId>(), typeof(Ulid) },
{ fixture.Create<Email>(), typeof(string) },
{ fixture.Create<RegisterRequest>(), typeof(RegisterCommand) },
{ fixture.Create<RegisterRequest>(), typeof(UserIdQuery) },
{ fixture.Create<UserIdResult>(), typeof(RegisterResponse) },
{ typeof(UserId), typeof(Ulid) },
{ typeof(Email), typeof(string) },
{ typeof(RegisterRequest), typeof(RegisterCommand) },
{ typeof(RegisterRequest), typeof(UserIdQuery) },
{ typeof(UserIdResult), typeof(RegisterResponse) },
};
}
}
Loading

0 comments on commit a772d7c

Please sign in to comment.