0
0
mirror of https://github.com/alex289/CleanArchitecture.git synced 2025-06-30 02:31:08 +00:00

Full Cleanup

This commit is contained in:
alex289 2023-03-22 19:06:01 +01:00
parent 492ea93b0d
commit df5530c726
No known key found for this signature in database
GPG Key ID: 573F77CD2D87F863
91 changed files with 593 additions and 579 deletions

View File

@ -7,22 +7,22 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.4"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.4"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.4"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" /> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" /> <ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" /> <ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" /> <ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -62,7 +62,7 @@ public class ApiController : ControllerBase
{ {
return HttpStatusCode.NotFound; return HttpStatusCode.NotFound;
} }
if (_notifications.GetNotifications().Any(n => n.Code == ErrorCodes.InsufficientPermissions)) if (_notifications.GetNotifications().Any(n => n.Code == ErrorCodes.InsufficientPermissions))
{ {
return HttpStatusCode.Forbidden; return HttpStatusCode.Forbidden;

View File

@ -46,7 +46,7 @@ public class UserController : ApiController
var user = await _userService.GetUserByUserIdAsync(id, isDeleted); var user = await _userService.GetUserByUserIdAsync(id, isDeleted);
return Response(user); return Response(user);
} }
[Authorize] [Authorize]
[HttpGet("me")] [HttpGet("me")]
[SwaggerOperation("Get the current active user")] [SwaggerOperation("Get the current active user")]
@ -56,7 +56,7 @@ public class UserController : ApiController
var user = await _userService.GetCurrentUserAsync(); var user = await _userService.GetCurrentUserAsync();
return Response(user); return Response(user);
} }
[HttpPost] [HttpPost]
[SwaggerOperation("Create a new user")] [SwaggerOperation("Create a new user")]
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))] [SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]

View File

@ -28,37 +28,36 @@ builder.Services.AddSwaggerGen(c =>
{ {
Title = "CleanArchitecture", Title = "CleanArchitecture",
Version = "v1", Version = "v1",
Description = "A clean architecture API", Description = "A clean architecture API"
}); });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{ {
Description = "JWT Authorization header using the Bearer scheme. " + Description = "JWT Authorization header using the Bearer scheme. " +
"Use the /auth/azureLogin endpoint to generate a token (use the id_token here), " + "Use the /api/v1/user/login endpoint to generate a token",
"or create a personal access token in centralhub.",
Name = "Authorization", Name = "Authorization",
In = ParameterLocation.Header, In = ParameterLocation.Header,
Type = SecuritySchemeType.Http, Type = SecuritySchemeType.Http,
Scheme = "bearer" Scheme = "bearer"
}); });
c.AddSecurityRequirement(new OpenApiSecurityRequirement() c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{ {
Reference = new OpenApiReference
{ {
new OpenApiSecurityScheme Type = ReferenceType.SecurityScheme,
{ Id = "Bearer"
Reference = new OpenApiReference },
{ Scheme = "oauth2",
Type = ReferenceType.SecurityScheme, Name = "Bearer",
Id = "Bearer" In = ParameterLocation.Header
}, },
Scheme = "oauth2", new List<string>()
Name = "Bearer", }
In = ParameterLocation.Header, });
},
new List<string>()
}
});
}); });
builder.Services.AddHealthChecks(); builder.Services.AddHealthChecks();
@ -72,15 +71,9 @@ builder.Services.AddDbContext<ApplicationDbContext>(options =>
}); });
builder.Services.AddAuthentication( builder.Services.AddAuthentication(
options => options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; })
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer( .AddJwtBearer(
jwtOptions => jwtOptions => { jwtOptions.TokenValidationParameters = CreateTokenValidationParameters(); });
{
jwtOptions.TokenValidationParameters = CreateTokenValidationParameters();
});
builder.Services.AddInfrastructure(); builder.Services.AddInfrastructure();
builder.Services.AddQueryHandlers(); builder.Services.AddQueryHandlers();
@ -94,10 +87,7 @@ builder.Services
.Bind(builder.Configuration.GetSection("Auth")) .Bind(builder.Configuration.GetSection("Auth"))
.ValidateOnStart(); .ValidateOnStart();
builder.Services.AddMediatR(cfg => builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly); });
{
cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly);
});
var app = builder.Build(); var app = builder.Build();
@ -116,10 +106,10 @@ app.MapControllers();
app.MapHealthChecks("/health"); app.MapHealthChecks("/health");
app.MapGrpcService<UsersApiImplementation>(); app.MapGrpcService<UsersApiImplementation>();
using (IServiceScope scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
var services = scope.ServiceProvider; var services = scope.ServiceProvider;
ApplicationDbContext appDbContext = services.GetRequiredService<ApplicationDbContext>(); var appDbContext = services.GetRequiredService<ApplicationDbContext>();
appDbContext.EnsureMigrationsApplied(); appDbContext.EnsureMigrationsApplied();
} }
@ -137,8 +127,8 @@ TokenValidationParameters CreateTokenValidationParameters()
ValidIssuer = builder.Configuration["Auth:Issuer"], ValidIssuer = builder.Configuration["Auth:Issuer"],
ValidAudience = builder.Configuration["Auth:Audience"], ValidAudience = builder.Configuration["Auth:Audience"],
IssuerSigningKey = new SymmetricSecurityKey( IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes( Encoding.UTF8.GetBytes(
builder.Configuration["Auth:Secret"]!)), builder.Configuration["Auth:Secret"]!)),
RequireSignedTokens = false RequireSignedTokens = false
}; };
@ -146,4 +136,6 @@ TokenValidationParameters CreateTokenValidationParameters()
} }
// Needed for integration tests webapplication factory // Needed for integration tests webapplication factory
public partial class Program { } public partial class Program
{
}

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.10.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="MockQueryable.Moq" Version="7.0.0" /> <PackageReference Include="MockQueryable.Moq" Version="7.0.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -24,8 +24,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" /> <ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,7 +7,7 @@ namespace CleanArchitecture.Application.Tests.Fixtures.Queries;
public class QueryHandlerBaseFixture public class QueryHandlerBaseFixture
{ {
public Mock<IMediatorHandler> Bus { get; } = new(); public Mock<IMediatorHandler> Bus { get; } = new();
public QueryHandlerBaseFixture VerifyExistingNotification(string key, string errorCode, string message) public QueryHandlerBaseFixture VerifyExistingNotification(string key, string errorCode, string message)
{ {
Bus.Verify( Bus.Verify(

View File

@ -11,30 +11,30 @@ namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Users;
public sealed class GetAllUsersTestFixture : QueryHandlerBaseFixture public sealed class GetAllUsersTestFixture : QueryHandlerBaseFixture
{ {
public GetAllUsersTestFixture()
{
UserRepository = new Mock<IUserRepository>();
Handler = new GetAllUsersQueryHandler(UserRepository.Object);
}
private Mock<IUserRepository> UserRepository { get; } private Mock<IUserRepository> UserRepository { get; }
public GetAllUsersQueryHandler Handler { get; } public GetAllUsersQueryHandler Handler { get; }
public Guid ExistingUserId { get; } = Guid.NewGuid(); public Guid ExistingUserId { get; } = Guid.NewGuid();
public GetAllUsersTestFixture()
{
UserRepository = new();
Handler = new(UserRepository.Object);
}
public void SetupUserAsync() public void SetupUserAsync()
{ {
var user = new Mock<User>(() => var user = new Mock<User>(() =>
new User( new User(
ExistingUserId, ExistingUserId,
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User)); UserRole.User));
var query = new[] { user.Object }.AsQueryable().BuildMock(); var query = new[] { user.Object }.AsQueryable().BuildMock();
UserRepository UserRepository
.Setup(x => x.GetAllNoTracking()) .Setup(x => x.GetAllNoTracking())
.Returns(query); .Returns(query);
@ -43,13 +43,13 @@ public sealed class GetAllUsersTestFixture : QueryHandlerBaseFixture
public void SetupDeletedUserAsync() public void SetupDeletedUserAsync()
{ {
var user = new Mock<User>(() => var user = new Mock<User>(() =>
new User( new User(
ExistingUserId, ExistingUserId,
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User)); UserRole.User));
user.Object.Delete(); user.Object.Delete();

View File

@ -11,30 +11,30 @@ namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Users;
public sealed class GetUserByIdTestFixture : QueryHandlerBaseFixture public sealed class GetUserByIdTestFixture : QueryHandlerBaseFixture
{ {
public GetUserByIdTestFixture()
{
UserRepository = new Mock<IUserRepository>();
Handler = new GetUserByIdQueryHandler(UserRepository.Object, Bus.Object);
}
private Mock<IUserRepository> UserRepository { get; } private Mock<IUserRepository> UserRepository { get; }
public GetUserByIdQueryHandler Handler { get; } public GetUserByIdQueryHandler Handler { get; }
public Guid ExistingUserId { get; } = Guid.NewGuid(); public Guid ExistingUserId { get; } = Guid.NewGuid();
public GetUserByIdTestFixture()
{
UserRepository = new();
Handler = new(UserRepository.Object, Bus.Object);
}
public void SetupUserAsync() public void SetupUserAsync()
{ {
var user = new Mock<User>(() => var user = new Mock<User>(() =>
new User( new User(
ExistingUserId, ExistingUserId,
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User)); UserRole.User));
var query = new[] { user.Object }.AsQueryable().BuildMock(); var query = new[] { user.Object }.AsQueryable().BuildMock();
UserRepository UserRepository
.Setup(x => x.GetAllNoTracking()) .Setup(x => x.GetAllNoTracking())
.Returns(query); .Returns(query);

View File

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CleanArchitecture.Application.Queries.Users.GetAll;
using CleanArchitecture.Application.Tests.Fixtures.Queries.Users; using CleanArchitecture.Application.Tests.Fixtures.Queries.Users;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
@ -16,11 +17,11 @@ public sealed class GetAllUsersQueryHandlerTests
_fixture.SetupUserAsync(); _fixture.SetupUserAsync();
var result = await _fixture.Handler.Handle( var result = await _fixture.Handler.Handle(
new(), new GetAllUsersQuery(),
default); default);
_fixture.VerifyNoDomainNotification(); _fixture.VerifyNoDomainNotification();
result.Should().NotBeNull(); result.Should().NotBeNull();
result.Should().ContainSingle(); result.Should().ContainSingle();
result.FirstOrDefault()!.Id.Should().Be(_fixture.ExistingUserId); result.FirstOrDefault()!.Id.Should().Be(_fixture.ExistingUserId);
@ -32,7 +33,7 @@ public sealed class GetAllUsersQueryHandlerTests
_fixture.SetupDeletedUserAsync(); _fixture.SetupDeletedUserAsync();
var result = await _fixture.Handler.Handle( var result = await _fixture.Handler.Handle(
new(), new GetAllUsersQuery(),
default); default);
_fixture.VerifyNoDomainNotification(); _fixture.VerifyNoDomainNotification();

View File

@ -18,15 +18,15 @@ public sealed class GetUserByIdQueryHandlerTests
_fixture.SetupUserAsync(); _fixture.SetupUserAsync();
var result = await _fixture.Handler.Handle( var result = await _fixture.Handler.Handle(
new(_fixture.ExistingUserId, false), new GetUserByIdQuery(_fixture.ExistingUserId, false),
default); default);
_fixture.VerifyNoDomainNotification(); _fixture.VerifyNoDomainNotification();
result.Should().NotBeNull(); result.Should().NotBeNull();
result!.Id.Should().Be(_fixture.ExistingUserId); result!.Id.Should().Be(_fixture.ExistingUserId);
} }
[Fact] [Fact]
public async Task Should_Raise_Notification_For_No_User() public async Task Should_Raise_Notification_For_No_User()
{ {
@ -36,7 +36,7 @@ public sealed class GetUserByIdQueryHandlerTests
var result = await _fixture.Handler.Handle( var result = await _fixture.Handler.Handle(
request, request,
default); default);
_fixture.VerifyExistingNotification( _fixture.VerifyExistingNotification(
nameof(GetUserByIdQuery), nameof(GetUserByIdQuery),
ErrorCodes.ObjectNotFound, ErrorCodes.ObjectNotFound,
@ -51,7 +51,7 @@ public sealed class GetUserByIdQueryHandlerTests
_fixture.SetupDeletedUserAsync(); _fixture.SetupDeletedUserAsync();
var result = await _fixture.Handler.Handle( var result = await _fixture.Handler.Handle(
new(_fixture.ExistingUserId, false), new GetUserByIdQuery(_fixture.ExistingUserId, false),
default); default);
_fixture.VerifyExistingNotification( _fixture.VerifyExistingNotification(

View File

@ -6,11 +6,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -14,15 +14,15 @@ public static class ServiceCollectionExtension
public static IServiceCollection AddServices(this IServiceCollection services) public static IServiceCollection AddServices(this IServiceCollection services)
{ {
services.AddScoped<IUserService, UserService>(); services.AddScoped<IUserService, UserService>();
return services; return services;
} }
public static IServiceCollection AddQueryHandlers(this IServiceCollection services) public static IServiceCollection AddQueryHandlers(this IServiceCollection services)
{ {
services.AddScoped<IRequestHandler<GetUserByIdQuery, UserViewModel?>, GetUserByIdQueryHandler>(); services.AddScoped<IRequestHandler<GetUserByIdQuery, UserViewModel?>, GetUserByIdQueryHandler>();
services.AddScoped<IRequestHandler<GetAllUsersQuery, IEnumerable<UserViewModel>>, GetAllUsersQueryHandler>(); services.AddScoped<IRequestHandler<GetAllUsersQuery, IEnumerable<UserViewModel>>, GetAllUsersQueryHandler>();
return services; return services;
} }
} }

View File

@ -1,6 +1,6 @@
using System.Threading.Tasks;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using CleanArchitecture.Application.ViewModels.Users; using CleanArchitecture.Application.ViewModels.Users;
namespace CleanArchitecture.Application.Interfaces; namespace CleanArchitecture.Application.Interfaces;

View File

@ -4,4 +4,4 @@ using MediatR;
namespace CleanArchitecture.Application.Queries.Users.GetAll; namespace CleanArchitecture.Application.Queries.Users.GetAll;
public sealed record GetAllUsersQuery : IRequest<IEnumerable<UserViewModel>>; public sealed record GetAllUsersQuery : IRequest<IEnumerable<UserViewModel>>;

View File

@ -4,4 +4,4 @@ using MediatR;
namespace CleanArchitecture.Application.Queries.Users.GetUserById; namespace CleanArchitecture.Application.Queries.Users.GetUserById;
public sealed record GetUserByIdQuery(Guid UserId, bool IsDeleted) : IRequest<UserViewModel?>; public sealed record GetUserByIdQuery(Guid UserId, bool IsDeleted) : IRequest<UserViewModel?>;

View File

@ -13,8 +13,8 @@ namespace CleanArchitecture.Application.Queries.Users.GetUserById;
public sealed class GetUserByIdQueryHandler : public sealed class GetUserByIdQueryHandler :
IRequestHandler<GetUserByIdQuery, UserViewModel?> IRequestHandler<GetUserByIdQuery, UserViewModel?>
{ {
private readonly IUserRepository _userRepository;
private readonly IMediatorHandler _bus; private readonly IMediatorHandler _bus;
private readonly IUserRepository _userRepository;
public GetUserByIdQueryHandler(IUserRepository userRepository, IMediatorHandler bus) public GetUserByIdQueryHandler(IUserRepository userRepository, IMediatorHandler bus)
{ {
@ -26,7 +26,7 @@ public sealed class GetUserByIdQueryHandler :
{ {
var user = _userRepository var user = _userRepository
.GetAllNoTracking() .GetAllNoTracking()
.FirstOrDefault(x => .FirstOrDefault(x =>
x.Id == request.UserId && x.Id == request.UserId &&
x.Deleted == request.IsDeleted); x.Deleted == request.IsDeleted);
@ -42,4 +42,4 @@ public sealed class GetUserByIdQueryHandler :
return UserViewModel.FromUser(user); return UserViewModel.FromUser(user);
} }
} }

View File

@ -29,7 +29,7 @@ public sealed class UserService : IUserService
{ {
return await _bus.QueryAsync(new GetUserByIdQuery(userId, isDeleted)); return await _bus.QueryAsync(new GetUserByIdQuery(userId, isDeleted));
} }
public async Task<UserViewModel?> GetCurrentUserAsync() public async Task<UserViewModel?> GetCurrentUserAsync()
{ {
return await _bus.QueryAsync(new GetUserByIdQuery(_user.GetUserId(), false)); return await _bus.QueryAsync(new GetUserByIdQuery(_user.GetUserId(), false));
@ -39,7 +39,7 @@ public sealed class UserService : IUserService
{ {
return await _bus.QueryAsync(new GetAllUsersQuery()); return await _bus.QueryAsync(new GetAllUsersQuery());
} }
public async Task<Guid> CreateUserAsync(CreateUserViewModel user) public async Task<Guid> CreateUserAsync(CreateUserViewModel user)
{ {
var userId = Guid.NewGuid(); var userId = Guid.NewGuid();
@ -53,7 +53,7 @@ public sealed class UserService : IUserService
return userId; return userId;
} }
public async Task UpdateUserAsync(UpdateUserViewModel user) public async Task UpdateUserAsync(UpdateUserViewModel user)
{ {
await _bus.SendCommandAsync(new UpdateUserCommand( await _bus.SendCommandAsync(new UpdateUserCommand(
@ -63,7 +63,7 @@ public sealed class UserService : IUserService
user.GivenName, user.GivenName,
user.Role)); user.Role));
} }
public async Task DeleteUserAsync(Guid userId) public async Task DeleteUserAsync(Guid userId)
{ {
await _bus.SendCommandAsync(new DeleteUserCommand(userId)); await _bus.SendCommandAsync(new DeleteUserCommand(userId));

View File

@ -1,3 +1,3 @@
namespace CleanArchitecture.Application.ViewModels.Users; namespace CleanArchitecture.Application.ViewModels.Users;
public sealed record ChangePasswordViewModel(string Password, string NewPassword); public sealed record ChangePasswordViewModel(string Password, string NewPassword);

View File

@ -1,3 +1,3 @@
namespace CleanArchitecture.Application.ViewModels.Users; namespace CleanArchitecture.Application.ViewModels.Users;
public sealed record LoginUserViewModel(string Email, string Password); public sealed record LoginUserViewModel(string Email, string Password);

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.10.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -24,7 +24,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -60,4 +60,4 @@ public sealed class ChangePasswordCommandHandlerTests
DomainErrorCodes.UserPasswordIncorrect, DomainErrorCodes.UserPasswordIncorrect,
"The password is incorrect"); "The password is incorrect");
} }
} }

View File

@ -9,14 +9,11 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.ChangePassword;
public sealed class ChangePasswordCommandTestFixture : CommandHandlerFixtureBase public sealed class ChangePasswordCommandTestFixture : CommandHandlerFixtureBase
{ {
public ChangePasswordCommandHandler CommandHandler { get; set; }
public Mock<IUserRepository> UserRepository { get; set; }
public ChangePasswordCommandTestFixture() public ChangePasswordCommandTestFixture()
{ {
UserRepository = new Mock<IUserRepository>(); UserRepository = new Mock<IUserRepository>();
CommandHandler = new( CommandHandler = new ChangePasswordCommandHandler(
Bus.Object, Bus.Object,
UnitOfWork.Object, UnitOfWork.Object,
NotificationHandler.Object, NotificationHandler.Object,
@ -24,6 +21,9 @@ public sealed class ChangePasswordCommandTestFixture : CommandHandlerFixtureBase
User.Object); User.Object);
} }
public ChangePasswordCommandHandler CommandHandler { get; set; }
public Mock<IUserRepository> UserRepository { get; set; }
public Entities.User SetupUser() public Entities.User SetupUser()
{ {
var user = new Entities.User( var user = new Entities.User(
@ -49,4 +49,4 @@ public sealed class ChangePasswordCommandTestFixture : CommandHandlerFixtureBase
User.Setup(x => x.GetUserId()).Returns(id); User.Setup(x => x.GetUserId()).Returns(id);
return id; return id;
} }
} }

View File

@ -12,15 +12,15 @@ public sealed class ChangePasswordCommandValidationTests :
public ChangePasswordCommandValidationTests() : base(new ChangePasswordCommandValidation()) public ChangePasswordCommandValidationTests() : base(new ChangePasswordCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
var command = CreateTestCommand(); var command = CreateTestCommand();
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Password() public void Should_Be_Invalid_For_Empty_Password()
{ {
@ -35,60 +35,63 @@ public sealed class ChangePasswordCommandValidationTests :
DomainErrorCodes.UserUppercaseLetterPassword, DomainErrorCodes.UserUppercaseLetterPassword,
DomainErrorCodes.UserShortPassword DomainErrorCodes.UserShortPassword
}; };
ShouldHaveExpectedErrors(command, errors.ToArray()); ShouldHaveExpectedErrors(command, errors.ToArray());
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Special_Character() public void Should_Be_Invalid_For_Missing_Special_Character()
{ {
var command = CreateTestCommand("z8tnayvd5FNLU9AQm"); var command = CreateTestCommand("z8tnayvd5FNLU9AQm");
ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Number() public void Should_Be_Invalid_For_Missing_Number()
{ {
var command = CreateTestCommand("z]tnayvdFNLU:]AQm"); var command = CreateTestCommand("z]tnayvdFNLU:]AQm");
ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Lowercase_Character() public void Should_Be_Invalid_For_Missing_Lowercase_Character()
{ {
var command = CreateTestCommand("Z8]TNAYVDFNLU:]AQM"); var command = CreateTestCommand("Z8]TNAYVDFNLU:]AQM");
ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Uppercase_Character() public void Should_Be_Invalid_For_Missing_Uppercase_Character()
{ {
var command = CreateTestCommand("z8]tnayvd5fnlu9:]aqm"); var command = CreateTestCommand("z8]tnayvd5fnlu9:]aqm");
ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Password_Too_Short() public void Should_Be_Invalid_For_Password_Too_Short()
{ {
var command = CreateTestCommand("zA6{"); var command = CreateTestCommand("zA6{");
ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Password_Too_Long() public void Should_Be_Invalid_For_Password_Too_Long()
{ {
var command = CreateTestCommand(string.Concat(Enumerable.Repeat("zA6{", 12), 12)); var command = CreateTestCommand(string.Concat(Enumerable.Repeat("zA6{", 12), 12));
ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword);
} }
private ChangePasswordCommand CreateTestCommand( private ChangePasswordCommand CreateTestCommand(
string? password = null, string? newPassword = null) => new( string? password = null, string? newPassword = null)
password ?? "z8]tnayvd5FNLU9:]AQm", {
return new(
password ?? "z8]tnayvd5FNLU9:]AQm",
newPassword ?? "z8]tnayvd5FNLU9:]AQw"); newPassword ?? "z8]tnayvd5FNLU9:]AQw");
} }
}

View File

@ -9,19 +9,19 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.CreateUser;
public sealed class CreateUserCommandHandlerTests public sealed class CreateUserCommandHandlerTests
{ {
private readonly CreateUserCommandTestFixture _fixture = new(); private readonly CreateUserCommandTestFixture _fixture = new();
[Fact] [Fact]
public void Should_Create_User() public void Should_Create_User()
{ {
_fixture.SetupUser(); _fixture.SetupUser();
var command = new CreateUserCommand( var command = new CreateUserCommand(
Guid.NewGuid(), Guid.NewGuid(),
"test@email.com", "test@email.com",
"Test", "Test",
"Email", "Email",
"Po=PF]PC6t.?8?ks)A6W"); "Po=PF]PC6t.?8?ks)A6W");
_fixture.CommandHandler.Handle(command, default).Wait(); _fixture.CommandHandler.Handle(command, default).Wait();
_fixture _fixture
@ -29,19 +29,19 @@ public sealed class CreateUserCommandHandlerTests
.VerifyCommit() .VerifyCommit()
.VerifyRaisedEvent<UserCreatedEvent>(x => x.UserId == command.UserId); .VerifyRaisedEvent<UserCreatedEvent>(x => x.UserId == command.UserId);
} }
[Fact] [Fact]
public void Should_Not_Create_Already_Existing_User() public void Should_Not_Create_Already_Existing_User()
{ {
var user = _fixture.SetupUser(); var user = _fixture.SetupUser();
var command = new CreateUserCommand( var command = new CreateUserCommand(
user.Id, user.Id,
"test@email.com", "test@email.com",
"Test", "Test",
"Email", "Email",
"Po=PF]PC6t.?8?ks)A6W"); "Po=PF]PC6t.?8?ks)A6W");
_fixture.CommandHandler.Handle(command, default).Wait(); _fixture.CommandHandler.Handle(command, default).Wait();
_fixture _fixture

View File

@ -8,20 +8,20 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.CreateUser;
public sealed class CreateUserCommandTestFixture : CommandHandlerFixtureBase public sealed class CreateUserCommandTestFixture : CommandHandlerFixtureBase
{ {
public CreateUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public CreateUserCommandTestFixture() public CreateUserCommandTestFixture()
{ {
UserRepository = new Mock<IUserRepository>(); UserRepository = new Mock<IUserRepository>();
CommandHandler = new( CommandHandler = new CreateUserCommandHandler(
Bus.Object, Bus.Object,
UnitOfWork.Object, UnitOfWork.Object,
NotificationHandler.Object, NotificationHandler.Object,
UserRepository.Object); UserRepository.Object);
} }
public CreateUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public Entities.User SetupUser() public Entities.User SetupUser()
{ {
var user = new Entities.User( var user = new Entities.User(

View File

@ -13,103 +13,103 @@ public sealed class CreateUserCommandValidationTests :
public CreateUserCommandValidationTests() : base(new CreateUserCommandValidation()) public CreateUserCommandValidationTests() : base(new CreateUserCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
var command = CreateTestCommand(); var command = CreateTestCommand();
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_User_Id() public void Should_Be_Invalid_For_Empty_User_Id()
{ {
var command = CreateTestCommand(userId: Guid.Empty); var command = CreateTestCommand(Guid.Empty);
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptyId, DomainErrorCodes.UserEmptyId,
"User id may not be empty"); "User id may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Email() public void Should_Be_Invalid_For_Empty_Email()
{ {
var command = CreateTestCommand(email: string.Empty); var command = CreateTestCommand(email: string.Empty);
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserInvalidEmail, DomainErrorCodes.UserInvalidEmail,
"Email is not a valid email address"); "Email is not a valid email address");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Invalid_Email() public void Should_Be_Invalid_For_Invalid_Email()
{ {
var command = CreateTestCommand(email: "not a email"); var command = CreateTestCommand(email: "not a email");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserInvalidEmail, DomainErrorCodes.UserInvalidEmail,
"Email is not a valid email address"); "Email is not a valid email address");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Email_Exceeds_Max_Length() public void Should_Be_Invalid_For_Email_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(email: new string('a', 320) + "@test.com"); var command = CreateTestCommand(email: new string('a', 320) + "@test.com");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmailExceedsMaxLength, DomainErrorCodes.UserEmailExceedsMaxLength,
"Email may not be longer than 320 characters"); "Email may not be longer than 320 characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Surname() public void Should_Be_Invalid_For_Empty_Surname()
{ {
var command = CreateTestCommand(surName: ""); var command = CreateTestCommand(surName: "");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptySurname, DomainErrorCodes.UserEmptySurname,
"Surname may not be empty"); "Surname may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Surname_Exceeds_Max_Length() public void Should_Be_Invalid_For_Surname_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(surName: new string('a', 101)); var command = CreateTestCommand(surName: new string('a', 101));
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserSurnameExceedsMaxLength, DomainErrorCodes.UserSurnameExceedsMaxLength,
"Surname may not be longer than 100 characters"); "Surname may not be longer than 100 characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Given_Name() public void Should_Be_Invalid_For_Empty_Given_Name()
{ {
var command = CreateTestCommand(givenName: ""); var command = CreateTestCommand(givenName: "");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptyGivenName, DomainErrorCodes.UserEmptyGivenName,
"Given name may not be empty"); "Given name may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Given_Name_Exceeds_Max_Length() public void Should_Be_Invalid_For_Given_Name_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(givenName: new string('a', 101)); var command = CreateTestCommand(givenName: new string('a', 101));
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserGivenNameExceedsMaxLength, DomainErrorCodes.UserGivenNameExceedsMaxLength,
"Given name may not be longer than 100 characters"); "Given name may not be longer than 100 characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Password() public void Should_Be_Invalid_For_Empty_Password()
{ {
@ -124,68 +124,70 @@ public sealed class CreateUserCommandValidationTests :
DomainErrorCodes.UserUppercaseLetterPassword, DomainErrorCodes.UserUppercaseLetterPassword,
DomainErrorCodes.UserShortPassword DomainErrorCodes.UserShortPassword
}; };
ShouldHaveExpectedErrors(command, errors.ToArray()); ShouldHaveExpectedErrors(command, errors.ToArray());
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Special_Character() public void Should_Be_Invalid_For_Missing_Special_Character()
{ {
var command = CreateTestCommand(password: "z8tnayvd5FNLU9AQm"); var command = CreateTestCommand(password: "z8tnayvd5FNLU9AQm");
ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Number() public void Should_Be_Invalid_For_Missing_Number()
{ {
var command = CreateTestCommand(password: "z]tnayvdFNLU:]AQm"); var command = CreateTestCommand(password: "z]tnayvdFNLU:]AQm");
ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Lowercase_Character() public void Should_Be_Invalid_For_Missing_Lowercase_Character()
{ {
var command = CreateTestCommand(password: "Z8]TNAYVDFNLU:]AQM"); var command = CreateTestCommand(password: "Z8]TNAYVDFNLU:]AQM");
ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Uppercase_Character() public void Should_Be_Invalid_For_Missing_Uppercase_Character()
{ {
var command = CreateTestCommand(password: "z8]tnayvd5fnlu9:]aqm"); var command = CreateTestCommand(password: "z8]tnayvd5fnlu9:]aqm");
ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Password_Too_Short() public void Should_Be_Invalid_For_Password_Too_Short()
{ {
var command = CreateTestCommand(password: "zA6{"); var command = CreateTestCommand(password: "zA6{");
ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Password_Too_Long() public void Should_Be_Invalid_For_Password_Too_Long()
{ {
var command = CreateTestCommand(password: string.Concat(Enumerable.Repeat("zA6{", 12), 12)); var command = CreateTestCommand(password: string.Concat(Enumerable.Repeat("zA6{", 12), 12));
ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword);
} }
private CreateUserCommand CreateTestCommand( private CreateUserCommand CreateTestCommand(
Guid? userId = null, Guid? userId = null,
string? email = null, string? email = null,
string? surName = null, string? surName = null,
string? givenName = null, string? givenName = null,
string? password = null) => string? password = null)
new ( {
return new(
userId ?? Guid.NewGuid(), userId ?? Guid.NewGuid(),
email ?? "test@email.com", email ?? "test@email.com",
surName ?? "test", surName ?? "test",
givenName ?? "email", givenName ?? "email",
password ?? "Po=PF]PC6t.?8?ks)A6W"); password ?? "Po=PF]PC6t.?8?ks)A6W");
}
} }

View File

@ -9,14 +9,14 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.DeleteUser;
public sealed class DeleteUserCommandHandlerTests public sealed class DeleteUserCommandHandlerTests
{ {
private readonly DeleteUserCommandTestFixture _fixture = new(); private readonly DeleteUserCommandTestFixture _fixture = new();
[Fact] [Fact]
public void Should_Delete_User() public void Should_Delete_User()
{ {
var user = _fixture.SetupUser(); var user = _fixture.SetupUser();
var command = new DeleteUserCommand(user.Id); var command = new DeleteUserCommand(user.Id);
_fixture.CommandHandler.Handle(command, default).Wait(); _fixture.CommandHandler.Handle(command, default).Wait();
_fixture _fixture
@ -24,14 +24,14 @@ public sealed class DeleteUserCommandHandlerTests
.VerifyCommit() .VerifyCommit()
.VerifyRaisedEvent<UserDeletedEvent>(x => x.UserId == user.Id); .VerifyRaisedEvent<UserDeletedEvent>(x => x.UserId == user.Id);
} }
[Fact] [Fact]
public void Should_Not_Delete_Non_Existing_User() public void Should_Not_Delete_Non_Existing_User()
{ {
_fixture.SetupUser(); _fixture.SetupUser();
var command = new DeleteUserCommand(Guid.NewGuid()); var command = new DeleteUserCommand(Guid.NewGuid());
_fixture.CommandHandler.Handle(command, default).Wait(); _fixture.CommandHandler.Handle(command, default).Wait();
_fixture _fixture

View File

@ -8,14 +8,11 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.DeleteUser;
public sealed class DeleteUserCommandTestFixture : CommandHandlerFixtureBase public sealed class DeleteUserCommandTestFixture : CommandHandlerFixtureBase
{ {
public DeleteUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public DeleteUserCommandTestFixture() public DeleteUserCommandTestFixture()
{ {
UserRepository = new Mock<IUserRepository>(); UserRepository = new Mock<IUserRepository>();
CommandHandler = new ( CommandHandler = new DeleteUserCommandHandler(
Bus.Object, Bus.Object,
UnitOfWork.Object, UnitOfWork.Object,
NotificationHandler.Object, NotificationHandler.Object,
@ -23,6 +20,9 @@ public sealed class DeleteUserCommandTestFixture : CommandHandlerFixtureBase
User.Object); User.Object);
} }
public DeleteUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public Entities.User SetupUser() public Entities.User SetupUser()
{ {
var user = new Entities.User( var user = new Entities.User(

View File

@ -16,21 +16,23 @@ public sealed class DeleteUserCommandValidationTests :
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
var command = CreateTestCommand(); var command = CreateTestCommand();
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_User_Id() public void Should_Be_Invalid_For_Empty_User_Id()
{ {
var command = CreateTestCommand(userId: Guid.Empty); var command = CreateTestCommand(Guid.Empty);
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptyId, DomainErrorCodes.UserEmptyId,
"User id may not be empty"); "User id may not be empty");
} }
private DeleteUserCommand CreateTestCommand(Guid? userId = null) => private DeleteUserCommand CreateTestCommand(Guid? userId = null)
new (userId ?? Guid.NewGuid()); {
return new(userId ?? Guid.NewGuid());
}
} }

View File

@ -79,4 +79,4 @@ public sealed class LoginUserCommandHandlerTests
token.Should().BeEmpty(); token.Should().BeEmpty();
} }
} }

View File

@ -1,20 +1,16 @@
using CleanArchitecture.Domain.Commands.Users.LoginUser; using System;
using CleanArchitecture.Domain.Commands.Users.LoginUser;
using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Interfaces.Repositories;
using CleanArchitecture.Domain.Settings; using CleanArchitecture.Domain.Settings;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq; using Moq;
using System;
using BC = BCrypt.Net.BCrypt; using BC = BCrypt.Net.BCrypt;
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.LoginUser; namespace CleanArchitecture.Domain.Tests.CommandHandler.User.LoginUser;
public sealed class LoginUserCommandTestFixture : CommandHandlerFixtureBase public sealed class LoginUserCommandTestFixture : CommandHandlerFixtureBase
{ {
public LoginUserCommandHandler CommandHandler { get; set; }
public Mock<IUserRepository> UserRepository { get; set; }
public IOptions<TokenSettings> TokenSettings { get; set; }
public LoginUserCommandTestFixture() public LoginUserCommandTestFixture()
{ {
UserRepository = new Mock<IUserRepository>(); UserRepository = new Mock<IUserRepository>();
@ -26,7 +22,7 @@ public sealed class LoginUserCommandTestFixture : CommandHandlerFixtureBase
Secret = "asjdlkasjd87439284)@#(*" Secret = "asjdlkasjd87439284)@#(*"
}); });
CommandHandler = new( CommandHandler = new LoginUserCommandHandler(
Bus.Object, Bus.Object,
UnitOfWork.Object, UnitOfWork.Object,
NotificationHandler.Object, NotificationHandler.Object,
@ -34,6 +30,10 @@ public sealed class LoginUserCommandTestFixture : CommandHandlerFixtureBase
TokenSettings); TokenSettings);
} }
public LoginUserCommandHandler CommandHandler { get; set; }
public Mock<IUserRepository> UserRepository { get; set; }
public IOptions<TokenSettings> TokenSettings { get; set; }
public Entities.User SetupUser() public Entities.User SetupUser()
{ {
var user = new Entities.User( var user = new Entities.User(
@ -52,4 +52,4 @@ public sealed class LoginUserCommandTestFixture : CommandHandlerFixtureBase
return user; return user;
} }
} }

View File

@ -12,48 +12,48 @@ public sealed class LoginUserCommandValidationTests :
public LoginUserCommandValidationTests() : base(new LoginUserCommandValidation()) public LoginUserCommandValidationTests() : base(new LoginUserCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
var command = CreateTestCommand(); var command = CreateTestCommand();
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Email() public void Should_Be_Invalid_For_Empty_Email()
{ {
var command = CreateTestCommand(email: string.Empty); var command = CreateTestCommand(string.Empty);
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserInvalidEmail, DomainErrorCodes.UserInvalidEmail,
"Email is not a valid email address"); "Email is not a valid email address");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Invalid_Email() public void Should_Be_Invalid_For_Invalid_Email()
{ {
var command = CreateTestCommand(email: "not a email"); var command = CreateTestCommand("not a email");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserInvalidEmail, DomainErrorCodes.UserInvalidEmail,
"Email is not a valid email address"); "Email is not a valid email address");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Email_Exceeds_Max_Length() public void Should_Be_Invalid_For_Email_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(email: new string('a', 320) + "@test.com"); var command = CreateTestCommand(new string('a', 320) + "@test.com");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmailExceedsMaxLength, DomainErrorCodes.UserEmailExceedsMaxLength,
"Email may not be longer than 320 characters"); "Email may not be longer than 320 characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Password() public void Should_Be_Invalid_For_Empty_Password()
{ {
@ -68,62 +68,64 @@ public sealed class LoginUserCommandValidationTests :
DomainErrorCodes.UserUppercaseLetterPassword, DomainErrorCodes.UserUppercaseLetterPassword,
DomainErrorCodes.UserShortPassword DomainErrorCodes.UserShortPassword
}; };
ShouldHaveExpectedErrors(command, errors.ToArray()); ShouldHaveExpectedErrors(command, errors.ToArray());
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Special_Character() public void Should_Be_Invalid_For_Missing_Special_Character()
{ {
var command = CreateTestCommand(password: "z8tnayvd5FNLU9AQm"); var command = CreateTestCommand(password: "z8tnayvd5FNLU9AQm");
ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Number() public void Should_Be_Invalid_For_Missing_Number()
{ {
var command = CreateTestCommand(password: "z]tnayvdFNLU:]AQm"); var command = CreateTestCommand(password: "z]tnayvdFNLU:]AQm");
ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Lowercase_Character() public void Should_Be_Invalid_For_Missing_Lowercase_Character()
{ {
var command = CreateTestCommand(password: "Z8]TNAYVDFNLU:]AQM"); var command = CreateTestCommand(password: "Z8]TNAYVDFNLU:]AQM");
ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Missing_Uppercase_Character() public void Should_Be_Invalid_For_Missing_Uppercase_Character()
{ {
var command = CreateTestCommand(password: "z8]tnayvd5fnlu9:]aqm"); var command = CreateTestCommand(password: "z8]tnayvd5fnlu9:]aqm");
ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Password_Too_Short() public void Should_Be_Invalid_For_Password_Too_Short()
{ {
var command = CreateTestCommand(password: "zA6{"); var command = CreateTestCommand(password: "zA6{");
ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Password_Too_Long() public void Should_Be_Invalid_For_Password_Too_Long()
{ {
var command = CreateTestCommand(password: string.Concat(Enumerable.Repeat("zA6{", 12), 12)); var command = CreateTestCommand(password: string.Concat(Enumerable.Repeat("zA6{", 12), 12));
ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword); ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword);
} }
private LoginUserCommand CreateTestCommand( private LoginUserCommand CreateTestCommand(
string? email = null, string? email = null,
string? password = null) => string? password = null)
new ( {
return new(
email ?? "test@email.com", email ?? "test@email.com",
password ?? "Po=PF]PC6t.?8?ks)A6W"); password ?? "Po=PF]PC6t.?8?ks)A6W");
} }
}

View File

@ -11,19 +11,19 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
public sealed class UpdateUserCommandHandlerTests public sealed class UpdateUserCommandHandlerTests
{ {
private readonly UpdateUserCommandTestFixture _fixture = new(); private readonly UpdateUserCommandTestFixture _fixture = new();
[Fact] [Fact]
public async Task Should_Update_User() public async Task Should_Update_User()
{ {
var user = _fixture.SetupUser(); var user = _fixture.SetupUser();
var command = new UpdateUserCommand( var command = new UpdateUserCommand(
user.Id, user.Id,
"test@email.com", "test@email.com",
"Test", "Test",
"Email", "Email",
UserRole.User); UserRole.User);
await _fixture.CommandHandler.Handle(command, default); await _fixture.CommandHandler.Handle(command, default);
_fixture _fixture
@ -31,19 +31,19 @@ public sealed class UpdateUserCommandHandlerTests
.VerifyCommit() .VerifyCommit()
.VerifyRaisedEvent<UserUpdatedEvent>(x => x.UserId == command.UserId); .VerifyRaisedEvent<UserUpdatedEvent>(x => x.UserId == command.UserId);
} }
[Fact] [Fact]
public async Task Should_Not_Update_Non_Existing_User() public async Task Should_Not_Update_Non_Existing_User()
{ {
_fixture.SetupUser(); _fixture.SetupUser();
var command = new UpdateUserCommand( var command = new UpdateUserCommand(
Guid.NewGuid(), Guid.NewGuid(),
"test@email.com", "test@email.com",
"Test", "Test",
"Email", "Email",
UserRole.User); UserRole.User);
await _fixture.CommandHandler.Handle(command, default); await _fixture.CommandHandler.Handle(command, default);
_fixture _fixture

View File

@ -8,21 +8,21 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
public sealed class UpdateUserCommandTestFixture : CommandHandlerFixtureBase public sealed class UpdateUserCommandTestFixture : CommandHandlerFixtureBase
{ {
public UpdateUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public UpdateUserCommandTestFixture() public UpdateUserCommandTestFixture()
{ {
UserRepository = new Mock<IUserRepository>(); UserRepository = new Mock<IUserRepository>();
CommandHandler = new( CommandHandler = new UpdateUserCommandHandler(
Bus.Object, Bus.Object,
UnitOfWork.Object, UnitOfWork.Object,
NotificationHandler.Object, NotificationHandler.Object,
UserRepository.Object, UserRepository.Object,
User.Object); User.Object);
} }
public UpdateUserCommandHandler CommandHandler { get; }
private Mock<IUserRepository> UserRepository { get; }
public Entities.User SetupUser() public Entities.User SetupUser()
{ {
var user = new Entities.User( var user = new Entities.User(

View File

@ -12,113 +12,115 @@ public sealed class UpdateUserCommandValidationTests :
public UpdateUserCommandValidationTests() : base(new UpdateUserCommandValidation()) public UpdateUserCommandValidationTests() : base(new UpdateUserCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
var command = CreateTestCommand(); var command = CreateTestCommand();
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_User_Id() public void Should_Be_Invalid_For_Empty_User_Id()
{ {
var command = CreateTestCommand(userId: Guid.Empty); var command = CreateTestCommand(Guid.Empty);
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptyId, DomainErrorCodes.UserEmptyId,
"User id may not be empty"); "User id may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Email() public void Should_Be_Invalid_For_Empty_Email()
{ {
var command = CreateTestCommand(email: string.Empty); var command = CreateTestCommand(email: string.Empty);
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserInvalidEmail, DomainErrorCodes.UserInvalidEmail,
"Email is not a valid email address"); "Email is not a valid email address");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Invalid_Email() public void Should_Be_Invalid_For_Invalid_Email()
{ {
var command = CreateTestCommand(email: "not a email"); var command = CreateTestCommand(email: "not a email");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserInvalidEmail, DomainErrorCodes.UserInvalidEmail,
"Email is not a valid email address"); "Email is not a valid email address");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Email_Exceeds_Max_Length() public void Should_Be_Invalid_For_Email_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(email: new string('a', 320) + "@test.com"); var command = CreateTestCommand(email: new string('a', 320) + "@test.com");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmailExceedsMaxLength, DomainErrorCodes.UserEmailExceedsMaxLength,
"Email may not be longer than 320 characters"); "Email may not be longer than 320 characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Surname() public void Should_Be_Invalid_For_Empty_Surname()
{ {
var command = CreateTestCommand(surName: ""); var command = CreateTestCommand(surName: "");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptySurname, DomainErrorCodes.UserEmptySurname,
"Surname may not be empty"); "Surname may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Surname_Exceeds_Max_Length() public void Should_Be_Invalid_For_Surname_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(surName: new string('a', 101)); var command = CreateTestCommand(surName: new string('a', 101));
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserSurnameExceedsMaxLength, DomainErrorCodes.UserSurnameExceedsMaxLength,
"Surname may not be longer than 100 characters"); "Surname may not be longer than 100 characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Given_Name() public void Should_Be_Invalid_For_Empty_Given_Name()
{ {
var command = CreateTestCommand(givenName: ""); var command = CreateTestCommand(givenName: "");
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserEmptyGivenName, DomainErrorCodes.UserEmptyGivenName,
"Given name may not be empty"); "Given name may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Given_Name_Exceeds_Max_Length() public void Should_Be_Invalid_For_Given_Name_Exceeds_Max_Length()
{ {
var command = CreateTestCommand(givenName: new string('a', 101)); var command = CreateTestCommand(givenName: new string('a', 101));
ShouldHaveSingleError( ShouldHaveSingleError(
command, command,
DomainErrorCodes.UserGivenNameExceedsMaxLength, DomainErrorCodes.UserGivenNameExceedsMaxLength,
"Given name may not be longer than 100 characters"); "Given name may not be longer than 100 characters");
} }
private static UpdateUserCommand CreateTestCommand( private static UpdateUserCommand CreateTestCommand(
Guid? userId = null, Guid? userId = null,
string? email = null, string? email = null,
string? surName = null, string? surName = null,
string? givenName = null, string? givenName = null,
UserRole? role = null) => UserRole? role = null)
new ( {
return new(
userId ?? Guid.NewGuid(), userId ?? Guid.NewGuid(),
email ?? "test@email.com", email ?? "test@email.com",
surName ?? "test", surName ?? "test",
givenName ?? "email", givenName ?? "email",
role ?? UserRole.User); role ?? UserRole.User);
}
} }

View File

@ -9,24 +9,24 @@ namespace CleanArchitecture.Domain.Tests;
public class CommandHandlerFixtureBase public class CommandHandlerFixtureBase
{ {
protected Mock<IMediatorHandler> Bus { get; }
protected Mock<IUnitOfWork> UnitOfWork { get; }
protected Mock<DomainNotificationHandler> NotificationHandler { get; }
protected Mock<IUser> User { get; }
protected CommandHandlerFixtureBase() protected CommandHandlerFixtureBase()
{ {
Bus = new Mock<IMediatorHandler>(); Bus = new Mock<IMediatorHandler>();
UnitOfWork = new Mock<IUnitOfWork>(); UnitOfWork = new Mock<IUnitOfWork>();
NotificationHandler = new Mock<DomainNotificationHandler>(); NotificationHandler = new Mock<DomainNotificationHandler>();
User = new Mock<IUser>(); User = new Mock<IUser>();
User.Setup(x => x.GetUserId()).Returns(Guid.NewGuid()); User.Setup(x => x.GetUserId()).Returns(Guid.NewGuid());
User.Setup(x => x.GetUserRole()).Returns(UserRole.Admin); User.Setup(x => x.GetUserRole()).Returns(UserRole.Admin);
UnitOfWork.Setup(unit => unit.CommitAsync()).ReturnsAsync(true); UnitOfWork.Setup(unit => unit.CommitAsync()).ReturnsAsync(true);
} }
protected Mock<IMediatorHandler> Bus { get; }
protected Mock<IUnitOfWork> UnitOfWork { get; }
protected Mock<DomainNotificationHandler> NotificationHandler { get; }
protected Mock<IUser> User { get; }
public CommandHandlerFixtureBase VerifyExistingNotification(string errorCode, string message) public CommandHandlerFixtureBase VerifyExistingNotification(string errorCode, string message)
{ {
Bus.Verify( Bus.Verify(

View File

@ -8,7 +8,7 @@ namespace CleanArchitecture.Domain.Tests;
public class ValidationTestBase<TCommand, TValidation> public class ValidationTestBase<TCommand, TValidation>
where TCommand : CommandBase where TCommand : CommandBase
where TValidation: AbstractValidator<TCommand> where TValidation : AbstractValidator<TCommand>
{ {
private readonly TValidation _validation; private readonly TValidation _validation;
@ -54,7 +54,7 @@ public class ValidationTestBase<TCommand, TValidation>
} }
protected void ShouldHaveExpectedErrors( protected void ShouldHaveExpectedErrors(
TCommand command, TCommand command,
params KeyValuePair<string, string>[] expectedErrors) params KeyValuePair<string, string>[] expectedErrors)
{ {
var result = _validation.Validate(command); var result = _validation.Validate(command);
@ -70,9 +70,9 @@ public class ValidationTestBase<TCommand, TValidation>
.Be(1); .Be(1);
} }
} }
protected void ShouldHaveExpectedErrors( protected void ShouldHaveExpectedErrors(
TCommand command, TCommand command,
params string[] expectedErrors) params string[] expectedErrors)
{ {
var result = _validation.Validate(command); var result = _validation.Validate(command);

View File

@ -29,19 +29,6 @@ public sealed class ApiUser : IUser
throw new ArgumentException("Could not parse user id to guid"); throw new ArgumentException("Could not parse user id to guid");
} }
public string GetUserEmail()
{
var claim = _httpContextAccessor.HttpContext?.User.Claims
.FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.Email));
if (!string.IsNullOrWhiteSpace(claim?.Value))
{
return claim?.Value!;
}
throw new ArgumentException("Could not parse user email");
}
public UserRole GetUserRole() public UserRole GetUserRole()
{ {
var claim = _httpContextAccessor.HttpContext?.User.Claims var claim = _httpContextAccessor.HttpContext?.User.Claims
@ -56,4 +43,17 @@ public sealed class ApiUser : IUser
} }
public string Name => _httpContextAccessor.HttpContext?.User.Identity?.Name ?? string.Empty; public string Name => _httpContextAccessor.HttpContext?.User.Identity?.Name ?? string.Empty;
}
public string GetUserEmail()
{
var claim = _httpContextAccessor.HttpContext?.User.Claims
.FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.Email));
if (!string.IsNullOrWhiteSpace(claim?.Value))
{
return claim?.Value!;
}
throw new ArgumentException("Could not parse user email");
}
}

View File

@ -6,12 +6,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
<PackageReference Include="FluentValidation" Version="11.5.1" /> <PackageReference Include="FluentValidation" Version="11.5.1"/>
<PackageReference Include="MediatR" Version="12.0.1" /> <PackageReference Include="MediatR" Version="12.0.1"/>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0"/>
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1"/>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -6,17 +6,17 @@ namespace CleanArchitecture.Domain.Commands;
public abstract class CommandBase : IRequest public abstract class CommandBase : IRequest
{ {
public Guid AggregateId { get; }
public string MessageType { get; }
public DateTime Timestamp { get; }
public ValidationResult? ValidationResult { get; protected set; }
protected CommandBase(Guid aggregateId) protected CommandBase(Guid aggregateId)
{ {
MessageType = GetType().Name; MessageType = GetType().Name;
Timestamp = DateTime.Now; Timestamp = DateTime.Now;
AggregateId = aggregateId; AggregateId = aggregateId;
} }
public Guid AggregateId { get; }
public string MessageType { get; }
public DateTime Timestamp { get; }
public ValidationResult? ValidationResult { get; protected set; }
public abstract bool IsValid(); public abstract bool IsValid();
} }

View File

@ -10,8 +10,8 @@ namespace CleanArchitecture.Domain.Commands;
public abstract class CommandHandlerBase public abstract class CommandHandlerBase
{ {
protected readonly IMediatorHandler _bus; protected readonly IMediatorHandler _bus;
private readonly IUnitOfWork _unitOfWork;
private readonly DomainNotificationHandler _notifications; private readonly DomainNotificationHandler _notifications;
private readonly IUnitOfWork _unitOfWork;
protected CommandHandlerBase( protected CommandHandlerBase(
IMediatorHandler bus, IMediatorHandler bus,
@ -22,7 +22,7 @@ public abstract class CommandHandlerBase
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_notifications = (DomainNotificationHandler)notifications; _notifications = (DomainNotificationHandler)notifications;
} }
public async Task<bool> CommitAsync() public async Task<bool> CommitAsync()
{ {
if (_notifications.HasNotifications()) if (_notifications.HasNotifications())
@ -43,7 +43,7 @@ public abstract class CommandHandlerBase
return false; return false;
} }
protected async Task NotifyAsync(string key, string message, string code) protected async Task NotifyAsync(string key, string message, string code)
{ {
await _bus.RaiseEventAsync( await _bus.RaiseEventAsync(
@ -54,7 +54,7 @@ public abstract class CommandHandlerBase
{ {
await _bus.RaiseEventAsync(notification); await _bus.RaiseEventAsync(notification);
} }
protected async ValueTask<bool> TestValidityAsync(CommandBase command) protected async ValueTask<bool> TestValidityAsync(CommandBase command)
{ {
if (command.IsValid()) if (command.IsValid())
@ -71,8 +71,8 @@ public abstract class CommandHandlerBase
{ {
await NotifyAsync( await NotifyAsync(
new DomainNotification( new DomainNotification(
command.MessageType, command.MessageType,
error.ErrorMessage, error.ErrorMessage,
error.ErrorCode, error.ErrorCode,
error.FormattedMessagePlaceholderValues)); error.FormattedMessagePlaceholderValues));
} }

View File

@ -6,15 +6,15 @@ public sealed class ChangePasswordCommand : CommandBase
{ {
private readonly ChangePasswordCommandValidation _validation = new(); private readonly ChangePasswordCommandValidation _validation = new();
public string Password { get; }
public string NewPassword { get; }
public ChangePasswordCommand(string password, string newPassword) : base(Guid.NewGuid()) public ChangePasswordCommand(string password, string newPassword) : base(Guid.NewGuid())
{ {
Password = password; Password = password;
NewPassword = newPassword; NewPassword = newPassword;
} }
public string Password { get; }
public string NewPassword { get; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = _validation.Validate(this); ValidationResult = _validation.Validate(this);

View File

@ -13,8 +13,8 @@ namespace CleanArchitecture.Domain.Commands.Users.ChangePassword;
public sealed class ChangePasswordCommandHandler : CommandHandlerBase, public sealed class ChangePasswordCommandHandler : CommandHandlerBase,
IRequestHandler<ChangePasswordCommand> IRequestHandler<ChangePasswordCommand>
{ {
private readonly IUserRepository _userRepository;
private readonly IUser _user; private readonly IUser _user;
private readonly IUserRepository _userRepository;
public ChangePasswordCommandHandler( public ChangePasswordCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
@ -68,4 +68,4 @@ public sealed class ChangePasswordCommandHandler : CommandHandlerBase,
await _bus.RaiseEventAsync(new PasswordChangedEvent(user.Id)); await _bus.RaiseEventAsync(new PasswordChangedEvent(user.Id));
} }
} }
} }

View File

@ -22,4 +22,4 @@ public sealed class ChangePasswordCommandValidation : AbstractValidator<ChangePa
RuleFor(cmd => cmd.NewPassword) RuleFor(cmd => cmd.NewPassword)
.Password(); .Password();
} }
} }

View File

@ -4,13 +4,7 @@ namespace CleanArchitecture.Domain.Commands.Users.CreateUser;
public sealed class CreateUserCommand : CommandBase public sealed class CreateUserCommand : CommandBase
{ {
private readonly CreateUserCommandValidation _validation = new(); private readonly CreateUserCommandValidation _validation = new();
public Guid UserId { get; }
public string Email { get; }
public string Surname { get; }
public string GivenName { get; }
public string Password { get; }
public CreateUserCommand( public CreateUserCommand(
Guid userId, Guid userId,
@ -26,6 +20,12 @@ public sealed class CreateUserCommand : CommandBase
Password = password; Password = password;
} }
public Guid UserId { get; }
public string Email { get; }
public string Surname { get; }
public string GivenName { get; }
public string Password { get; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = _validation.Validate(this); ValidationResult = _validation.Validate(this);

View File

@ -16,7 +16,7 @@ public sealed class CreateUserCommandHandler : CommandHandlerBase,
IRequestHandler<CreateUserCommand> IRequestHandler<CreateUserCommand>
{ {
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
public CreateUserCommandHandler( public CreateUserCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
IUnitOfWork unitOfWork, IUnitOfWork unitOfWork,
@ -44,9 +44,9 @@ public sealed class CreateUserCommandHandler : CommandHandlerBase,
DomainErrorCodes.UserAlreadyExists)); DomainErrorCodes.UserAlreadyExists));
return; return;
} }
existingUser = await _userRepository.GetByEmailAsync(request.Email); existingUser = await _userRepository.GetByEmailAsync(request.Email);
if (existingUser != null) if (existingUser != null)
{ {
await _bus.RaiseEventAsync( await _bus.RaiseEventAsync(
@ -60,15 +60,15 @@ public sealed class CreateUserCommandHandler : CommandHandlerBase,
var passwordHash = BC.HashPassword(request.Password); var passwordHash = BC.HashPassword(request.Password);
var user = new User( var user = new User(
request.UserId, request.UserId,
request.Email, request.Email,
request.Surname, request.Surname,
request.GivenName, request.GivenName,
passwordHash, passwordHash,
UserRole.User); UserRole.User);
_userRepository.Add(user); _userRepository.Add(user);
if (await CommitAsync()) if (await CommitAsync())
{ {
await _bus.RaiseEventAsync(new UserCreatedEvent(user.Id)); await _bus.RaiseEventAsync(new UserCreatedEvent(user.Id));

View File

@ -22,7 +22,7 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
.WithErrorCode(DomainErrorCodes.UserEmptyId) .WithErrorCode(DomainErrorCodes.UserEmptyId)
.WithMessage("User id may not be empty"); .WithMessage("User id may not be empty");
} }
private void AddRuleForEmail() private void AddRuleForEmail()
{ {
RuleFor(cmd => cmd.Email) RuleFor(cmd => cmd.Email)
@ -33,7 +33,7 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
.WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength) .WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength)
.WithMessage("Email may not be longer than 320 characters"); .WithMessage("Email may not be longer than 320 characters");
} }
private void AddRuleForSurname() private void AddRuleForSurname()
{ {
RuleFor(cmd => cmd.Surname) RuleFor(cmd => cmd.Surname)
@ -44,7 +44,7 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
.WithErrorCode(DomainErrorCodes.UserSurnameExceedsMaxLength) .WithErrorCode(DomainErrorCodes.UserSurnameExceedsMaxLength)
.WithMessage("Surname may not be longer than 100 characters"); .WithMessage("Surname may not be longer than 100 characters");
} }
private void AddRuleForGivenName() private void AddRuleForGivenName()
{ {
RuleFor(cmd => cmd.GivenName) RuleFor(cmd => cmd.GivenName)
@ -55,7 +55,7 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
.WithErrorCode(DomainErrorCodes.UserGivenNameExceedsMaxLength) .WithErrorCode(DomainErrorCodes.UserGivenNameExceedsMaxLength)
.WithMessage("Given name may not be longer than 100 characters"); .WithMessage("Given name may not be longer than 100 characters");
} }
private void AddRuleForPassword() private void AddRuleForPassword()
{ {
RuleFor(cmd => cmd.Password) RuleFor(cmd => cmd.Password)

View File

@ -4,15 +4,15 @@ namespace CleanArchitecture.Domain.Commands.Users.DeleteUser;
public sealed class DeleteUserCommand : CommandBase public sealed class DeleteUserCommand : CommandBase
{ {
private readonly DeleteUserCommandValidation _validation = new(); private readonly DeleteUserCommandValidation _validation = new();
public Guid UserId { get; }
public DeleteUserCommand(Guid userId) : base(userId) public DeleteUserCommand(Guid userId) : base(userId)
{ {
UserId = userId; UserId = userId;
} }
public Guid UserId { get; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = _validation.Validate(this); ValidationResult = _validation.Validate(this);

View File

@ -13,8 +13,8 @@ namespace CleanArchitecture.Domain.Commands.Users.DeleteUser;
public sealed class DeleteUserCommandHandler : CommandHandlerBase, public sealed class DeleteUserCommandHandler : CommandHandlerBase,
IRequestHandler<DeleteUserCommand> IRequestHandler<DeleteUserCommand>
{ {
private readonly IUserRepository _userRepository;
private readonly IUser _user; private readonly IUser _user;
private readonly IUserRepository _userRepository;
public DeleteUserCommandHandler( public DeleteUserCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
@ -35,7 +35,7 @@ public sealed class DeleteUserCommandHandler : CommandHandlerBase,
} }
var user = await _userRepository.GetByIdAsync(request.UserId); var user = await _userRepository.GetByIdAsync(request.UserId);
if (user == null) if (user == null)
{ {
await NotifyAsync( await NotifyAsync(
@ -54,7 +54,7 @@ public sealed class DeleteUserCommandHandler : CommandHandlerBase,
request.MessageType, request.MessageType,
$"No permission to delete user {request.UserId}", $"No permission to delete user {request.UserId}",
ErrorCodes.InsufficientPermissions)); ErrorCodes.InsufficientPermissions));
return; return;
} }

View File

@ -8,9 +8,6 @@ public sealed class LoginUserCommand : CommandBase,
{ {
private readonly LoginUserCommandValidation _validation = new(); private readonly LoginUserCommandValidation _validation = new();
public string Email { get; set; }
public string Password { get; set; }
public LoginUserCommand( public LoginUserCommand(
string email, string email,
@ -20,6 +17,9 @@ public sealed class LoginUserCommand : CommandBase,
Password = password; Password = password;
} }
public string Email { get; set; }
public string Password { get; set; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = _validation.Validate(this); ValidationResult = _validation.Validate(this);

View File

@ -1,6 +1,7 @@
using System.Security.Claims; using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text; using System.Text;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Enums;
@ -10,10 +11,9 @@ using CleanArchitecture.Domain.Interfaces.Repositories;
using CleanArchitecture.Domain.Notifications; using CleanArchitecture.Domain.Notifications;
using CleanArchitecture.Domain.Settings; using CleanArchitecture.Domain.Settings;
using MediatR; using MediatR;
using BC = BCrypt.Net.BCrypt;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using BC = BCrypt.Net.BCrypt;
namespace CleanArchitecture.Domain.Commands.Users.LoginUser; namespace CleanArchitecture.Domain.Commands.Users.LoginUser;
@ -21,9 +21,9 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
IRequestHandler<LoginUserCommand, string> IRequestHandler<LoginUserCommand, string>
{ {
private const double ExpiryDurationMinutes = 30; private const double ExpiryDurationMinutes = 30;
private readonly TokenSettings _tokenSettings;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly TokenSettings _tokenSettings;
public LoginUserCommandHandler( public LoginUserCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
@ -80,10 +80,10 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
{ {
var claims = new[] var claims = new[]
{ {
new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Email, email),
new Claim(ClaimTypes.Role, role.ToString()), new Claim(ClaimTypes.Role, role.ToString()),
new Claim(ClaimTypes.NameIdentifier, id.ToString()) new Claim(ClaimTypes.NameIdentifier, id.ToString())
}; };
var securityKey = new SymmetricSecurityKey( var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(tokenSettings.Secret)); Encoding.UTF8.GetBytes(tokenSettings.Secret));
@ -101,4 +101,4 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor); return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
} }
} }

View File

@ -28,4 +28,4 @@ public sealed class LoginUserCommandValidation : AbstractValidator<LoginUserComm
RuleFor(cmd => cmd.Password) RuleFor(cmd => cmd.Password)
.Password(); .Password();
} }
} }

View File

@ -6,12 +6,6 @@ namespace CleanArchitecture.Domain.Commands.Users.UpdateUser;
public sealed class UpdateUserCommand : CommandBase public sealed class UpdateUserCommand : CommandBase
{ {
private readonly UpdateUserCommandValidation _validation = new(); private readonly UpdateUserCommandValidation _validation = new();
public Guid UserId { get; }
public string Email { get; }
public string Surname { get; }
public string GivenName { get; }
public UserRole Role { get; }
public UpdateUserCommand( public UpdateUserCommand(
Guid userId, Guid userId,
@ -27,6 +21,12 @@ public sealed class UpdateUserCommand : CommandBase
Role = role; Role = role;
} }
public Guid UserId { get; }
public string Email { get; }
public string Surname { get; }
public string GivenName { get; }
public UserRole Role { get; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = _validation.Validate(this); ValidationResult = _validation.Validate(this);

View File

@ -1,6 +1,5 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Domain.Errors; using CleanArchitecture.Domain.Errors;
using CleanArchitecture.Domain.Events.User; using CleanArchitecture.Domain.Events.User;
@ -14,8 +13,8 @@ namespace CleanArchitecture.Domain.Commands.Users.UpdateUser;
public sealed class UpdateUserCommandHandler : CommandHandlerBase, public sealed class UpdateUserCommandHandler : CommandHandlerBase,
IRequestHandler<UpdateUserCommand> IRequestHandler<UpdateUserCommand>
{ {
private readonly IUserRepository _userRepository;
private readonly IUser _user; private readonly IUser _user;
private readonly IUserRepository _userRepository;
public UpdateUserCommandHandler( public UpdateUserCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
@ -54,7 +53,7 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
request.MessageType, request.MessageType,
$"No permission to update user {request.UserId}", $"No permission to update user {request.UserId}",
ErrorCodes.InsufficientPermissions)); ErrorCodes.InsufficientPermissions));
return; return;
} }
@ -68,7 +67,7 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
user.SetGivenName(request.GivenName); user.SetGivenName(request.GivenName);
_userRepository.Update(user); _userRepository.Update(user);
if (await CommitAsync()) if (await CommitAsync())
{ {
await _bus.RaiseEventAsync(new UserUpdatedEvent(user.Id)); await _bus.RaiseEventAsync(new UserUpdatedEvent(user.Id));

View File

@ -21,7 +21,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
.WithErrorCode(DomainErrorCodes.UserEmptyId) .WithErrorCode(DomainErrorCodes.UserEmptyId)
.WithMessage("User id may not be empty"); .WithMessage("User id may not be empty");
} }
private void AddRuleForEmail() private void AddRuleForEmail()
{ {
RuleFor(cmd => cmd.Email) RuleFor(cmd => cmd.Email)
@ -32,7 +32,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
.WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength) .WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength)
.WithMessage("Email may not be longer than 320 characters"); .WithMessage("Email may not be longer than 320 characters");
} }
private void AddRuleForSurname() private void AddRuleForSurname()
{ {
RuleFor(cmd => cmd.Surname) RuleFor(cmd => cmd.Surname)
@ -43,7 +43,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
.WithErrorCode(DomainErrorCodes.UserSurnameExceedsMaxLength) .WithErrorCode(DomainErrorCodes.UserSurnameExceedsMaxLength)
.WithMessage("Surname may not be longer than 100 characters"); .WithMessage("Surname may not be longer than 100 characters");
} }
private void AddRuleForGivenName() private void AddRuleForGivenName()
{ {
RuleFor(cmd => cmd.GivenName) RuleFor(cmd => cmd.GivenName)

View File

@ -6,14 +6,6 @@ namespace CleanArchitecture.Domain.Entities;
public class User : Entity public class User : Entity
{ {
public string Email { get; private set; }
public string GivenName { get; private set; }
public string Surname { get; private set; }
public string Password { get; private set; }
public UserRole Role { get; private set; }
public string FullName => $"{Surname}, {GivenName}";
public User( public User(
Guid id, Guid id,
string email, string email,
@ -29,6 +21,14 @@ public class User : Entity
Role = role; Role = role;
} }
public string Email { get; private set; }
public string GivenName { get; private set; }
public string Surname { get; private set; }
public string Password { get; private set; }
public UserRole Role { get; private set; }
public string FullName => $"{Surname}, {GivenName}";
[MemberNotNull(nameof(Email))] [MemberNotNull(nameof(Email))]
public void SetEmail(string email) public void SetEmail(string email)
{ {
@ -45,7 +45,7 @@ public class User : Entity
Email = email; Email = email;
} }
[MemberNotNull(nameof(GivenName))] [MemberNotNull(nameof(GivenName))]
public void SetGivenName(string givenName) public void SetGivenName(string givenName)
{ {
@ -62,7 +62,7 @@ public class User : Entity
GivenName = givenName; GivenName = givenName;
} }
[MemberNotNull(nameof(Surname))] [MemberNotNull(nameof(Surname))]
public void SetSurname(string surname) public void SetSurname(string surname)
{ {

View File

@ -4,4 +4,4 @@ public enum UserRole
{ {
Admin, Admin,
User User
} }

View File

@ -20,7 +20,7 @@ public static class DomainErrorCodes
public const string UserLowercaseLetterPassword = "USER_PASSWORD_MUST_CONTAIN_A_LOWERCASE_LETTER"; public const string UserLowercaseLetterPassword = "USER_PASSWORD_MUST_CONTAIN_A_LOWERCASE_LETTER";
public const string UserNumberPassword = "USER_PASSWORD_MUST_CONTAIN_A_NUMBER"; public const string UserNumberPassword = "USER_PASSWORD_MUST_CONTAIN_A_NUMBER";
public const string UserSpecialCharPassword = "USER_PASSWORD_MUST_CONTAIN_A_SPECIAL_CHARACTER"; public const string UserSpecialCharPassword = "USER_PASSWORD_MUST_CONTAIN_A_SPECIAL_CHARACTER";
// User // User
public const string UserAlreadyExists = "USER_ALREADY_EXISTS"; public const string UserAlreadyExists = "USER_ALREADY_EXISTS";
public const string UserPasswordIncorrect = "USER_PASSWORD_INCORRECT"; public const string UserPasswordIncorrect = "USER_PASSWORD_INCORRECT";

View File

@ -11,7 +11,7 @@ public sealed class UserEventHandler :
INotificationHandler<UserUpdatedEvent>, INotificationHandler<UserUpdatedEvent>,
INotificationHandler<PasswordChangedEvent> INotificationHandler<PasswordChangedEvent>
{ {
public Task Handle(UserDeletedEvent notification, CancellationToken cancellationToken) public Task Handle(PasswordChangedEvent notification, CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -20,13 +20,13 @@ public sealed class UserEventHandler :
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken) public Task Handle(UserDeletedEvent notification, CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task Handle(PasswordChangedEvent notification, CancellationToken cancellationToken) public Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -4,10 +4,10 @@ namespace CleanArchitecture.Domain.Events.User;
public sealed class PasswordChangedEvent : DomainEvent public sealed class PasswordChangedEvent : DomainEvent
{ {
public Guid UserId { get; }
public PasswordChangedEvent(Guid userId) : base(userId) public PasswordChangedEvent(Guid userId) : base(userId)
{ {
UserId = userId; UserId = userId;
} }
}
public Guid UserId { get; }
}

View File

@ -4,10 +4,10 @@ namespace CleanArchitecture.Domain.Events.User;
public sealed class UserCreatedEvent : DomainEvent public sealed class UserCreatedEvent : DomainEvent
{ {
public Guid UserId { get; }
public UserCreatedEvent(Guid userId) : base(userId) public UserCreatedEvent(Guid userId) : base(userId)
{ {
UserId = userId; UserId = userId;
} }
public Guid UserId { get; }
} }

View File

@ -4,10 +4,10 @@ namespace CleanArchitecture.Domain.Events.User;
public sealed class UserDeletedEvent : DomainEvent public sealed class UserDeletedEvent : DomainEvent
{ {
public Guid UserId { get; }
public UserDeletedEvent(Guid userId) : base(userId) public UserDeletedEvent(Guid userId) : base(userId)
{ {
UserId = userId; UserId = userId;
} }
public Guid UserId { get; }
} }

View File

@ -4,10 +4,10 @@ namespace CleanArchitecture.Domain.Events.User;
public sealed class UserUpdatedEvent : DomainEvent public sealed class UserUpdatedEvent : DomainEvent
{ {
public Guid UserId { get; }
public UserUpdatedEvent(Guid userId) : base(userId) public UserUpdatedEvent(Guid userId) : base(userId)
{ {
UserId = userId; UserId = userId;
} }
public Guid UserId { get; }
} }

View File

@ -25,7 +25,7 @@ public static class ServiceCollectionExtension
return services; return services;
} }
public static IServiceCollection AddNotificationHandlers(this IServiceCollection services) public static IServiceCollection AddNotificationHandlers(this IServiceCollection services)
{ {
// User // User
@ -41,7 +41,7 @@ public static class ServiceCollectionExtension
{ {
// User // User
services.AddScoped<IUser, ApiUser>(); services.AddScoped<IUser, ApiUser>();
return services; return services;
} }
} }

View File

@ -17,7 +17,10 @@ public static class CustomValidator
return base64.Length % 4 == 0 && Regex.IsMatch(base64, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None); return base64.Length % 4 == 0 && Regex.IsMatch(base64, @"^[a-zA-Z0-9\+/]*={0,3}$", RegexOptions.None);
} }
public static IRuleBuilder<T, string> Password<T>(this IRuleBuilder<T, string> ruleBuilder, int minLength = 8, int maxLength = 50) public static IRuleBuilder<T, string> Password<T>(
this IRuleBuilder<T, string> ruleBuilder,
int minLength = 8,
int maxLength = 50)
{ {
var options = ruleBuilder var options = ruleBuilder
.NotEmpty().WithErrorCode(DomainErrorCodes.UserEmptyPassword) .NotEmpty().WithErrorCode(DomainErrorCodes.UserEmptyPassword)

View File

@ -5,8 +5,7 @@ namespace CleanArchitecture.Domain.Interfaces;
public interface IUser public interface IUser
{ {
string Name { get; }
Guid GetUserId(); Guid GetUserId();
UserRole GetUserRole(); UserRole GetUserRole();
}
string Name { get; }
}

View File

@ -4,11 +4,6 @@ namespace CleanArchitecture.Domain.Notifications;
public sealed class DomainNotification : DomainEvent public sealed class DomainNotification : DomainEvent
{ {
public string Key { get; private set; }
public string Value { get; private set; }
public string Code { get; private set; }
public object? Data { get; set; }
public DomainNotification( public DomainNotification(
string key, string key,
string value, string value,
@ -23,4 +18,9 @@ public sealed class DomainNotification : DomainEvent
Data = data; Data = data;
} }
public string Key { get; }
public string Value { get; }
public string Code { get; }
public object? Data { get; set; }
} }

View File

@ -15,18 +15,18 @@ public class DomainNotificationHandler : INotificationHandler<DomainNotification
_notifications = new List<DomainNotification>(); _notifications = new List<DomainNotification>();
} }
public virtual List<DomainNotification> GetNotifications()
{
return _notifications;
}
public Task Handle(DomainNotification notification, CancellationToken cancellationToken = default) public Task Handle(DomainNotification notification, CancellationToken cancellationToken = default)
{ {
_notifications.Add(notification); _notifications.Add(notification);
return Task.CompletedTask; return Task.CompletedTask;
} }
public virtual List<DomainNotification> GetNotifications()
{
return _notifications;
}
public virtual bool HasNotifications() public virtual bool HasNotifications()
{ {
return GetNotifications().Any(); return GetNotifications().Any();

View File

@ -8,10 +8,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.10.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -23,7 +23,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" /> <ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -16,9 +16,9 @@ public sealed class DomainNotificationHandlerTests
[Fact] [Fact]
public void Should_Handle_DomainNotification() public void Should_Handle_DomainNotification()
{ {
string key = "Key"; var key = "Key";
string value = "Value"; var value = "Value";
string code = "Code"; var code = "Code";
var domainNotification = new DomainNotification(key, value, code); var domainNotification = new DomainNotification(key, value, code);
var domainNotificationHandler = new DomainNotificationHandler(); var domainNotificationHandler = new DomainNotificationHandler();
@ -29,9 +29,9 @@ public sealed class DomainNotificationHandlerTests
[Fact] [Fact]
public void Should_Handle_DomainNotification_Overload() public void Should_Handle_DomainNotification_Overload()
{ {
string key = "Key"; var key = "Key";
string value = "Value"; var value = "Value";
string code = "Code"; var code = "Code";
var domainNotification = new DomainNotification(key, value, code); var domainNotification = new DomainNotification(key, value, code);
var domainNotificationHandler = new DomainNotificationHandler(); var domainNotificationHandler = new DomainNotificationHandler();
@ -42,9 +42,9 @@ public sealed class DomainNotificationHandlerTests
[Fact] [Fact]
public void DomainNotification_HasNotifications_After_Handling_One() public void DomainNotification_HasNotifications_After_Handling_One()
{ {
string key = "Key"; var key = "Key";
string value = "Value"; var value = "Value";
string code = "Code"; var code = "Code";
var domainNotification = new DomainNotification(key, value, code); var domainNotification = new DomainNotification(key, value, code);
var domainNotificationHandler = new DomainNotificationHandler(); var domainNotificationHandler = new DomainNotificationHandler();
@ -60,4 +60,4 @@ public sealed class DomainNotificationHandlerTests
domainNotificationHandler.HasNotifications().Should().BeFalse(); domainNotificationHandler.HasNotifications().Should().BeFalse();
} }
} }

View File

@ -10,9 +10,9 @@ public sealed class DomainNotificationTests
[Fact] [Fact]
public void Should_Create_DomainNotification_Instance() public void Should_Create_DomainNotification_Instance()
{ {
string key = "Key"; var key = "Key";
string value = "Value"; var value = "Value";
string code = "Code"; var code = "Code";
var domainNotification = new DomainNotification( var domainNotification = new DomainNotification(
key, value, code); key, value, code);
@ -26,9 +26,9 @@ public sealed class DomainNotificationTests
[Fact] [Fact]
public void Should_Create_DomainNotification_Overload_Instance() public void Should_Create_DomainNotification_Overload_Instance()
{ {
string key = "Key"; var key = "Key";
string value = "Value"; var value = "Value";
string code = "Code"; var code = "Code";
var domainNotification = new DomainNotification( var domainNotification = new DomainNotification(
key, value, code); key, value, code);
@ -38,4 +38,4 @@ public sealed class DomainNotificationTests
domainNotification.Code.Should().Be(code); domainNotification.Code.Should().Be(code);
domainNotification.Should().NotBe(default(Guid)); domainNotification.Should().NotBe(default(Guid));
} }
} }

View File

@ -19,18 +19,18 @@ public sealed class UnitOfWorkTests
var options = new DbContextOptionsBuilder<ApplicationDbContext>(); var options = new DbContextOptionsBuilder<ApplicationDbContext>();
var dbContextMock = new Mock<ApplicationDbContext>(options.Options); var dbContextMock = new Mock<ApplicationDbContext>(options.Options);
var loggerMock = new Mock<ILogger<UnitOfWork<ApplicationDbContext>>>(); var loggerMock = new Mock<ILogger<UnitOfWork<ApplicationDbContext>>>();
dbContextMock dbContextMock
.Setup(x => x.SaveChangesAsync(CancellationToken.None)) .Setup(x => x.SaveChangesAsync(CancellationToken.None))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object); var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object);
var result = await unitOfWork.CommitAsync(); var result = await unitOfWork.CommitAsync();
result.Should().BeTrue(); result.Should().BeTrue();
} }
[Fact] [Fact]
public async Task Should_Commit_Async_Returns_False() public async Task Should_Commit_Async_Returns_False()
{ {
@ -45,7 +45,7 @@ public sealed class UnitOfWorkTests
var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object); var unitOfWork = UnitOfWorkTestFixture.GetUnitOfWork(dbContextMock.Object, loggerMock.Object);
var result = await unitOfWork.CommitAsync(); var result = await unitOfWork.CommitAsync();
result.Should().BeFalse(); result.Should().BeFalse();
} }

View File

@ -6,18 +6,18 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" /> <PackageReference Include="MediatR" Version="12.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -24,7 +24,7 @@ public sealed class UserConfiguration : IEntityTypeConfiguration<User>
.Property(user => user.Surname) .Property(user => user.Surname)
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
builder builder
.Property(user => user.Password) .Property(user => user.Password)
.IsRequired() .IsRequired()

View File

@ -6,12 +6,12 @@ namespace CleanArchitecture.Infrastructure.Database;
public class ApplicationDbContext : DbContext public class ApplicationDbContext : DbContext
{ {
public DbSet<User> Users { get; set; } = null!;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{ {
} }
public DbSet<User> Users { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {
builder.ApplyConfiguration(new UserConfiguration()); builder.ApplyConfiguration(new UserConfiguration());

View File

@ -18,4 +18,4 @@ public static class DbContextExtension
context.Database.Migrate(); context.Database.Migrate();
} }
} }
} }

View File

@ -22,4 +22,4 @@ public static class ServiceCollectionExtensions
return services; return services;
} }
} }

View File

@ -14,7 +14,7 @@ public sealed class InMemoryBus : IMediatorHandler
{ {
_mediator = mediator; _mediator = mediator;
} }
public Task<TResponse> QueryAsync<TResponse>(IRequest<TResponse> query) public Task<TResponse> QueryAsync<TResponse>(IRequest<TResponse> query)
{ {
return _mediator.Send(query); return _mediator.Send(query);

View File

@ -50,19 +50,6 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
return await DbSet.FindAsync(id); return await DbSet.FindAsync(id);
} }
public int SaveChanges()
{
return _dbContext.SaveChanges();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_dbContext.Dispose();
}
}
public virtual void Update(TEntity entity) public virtual void Update(TEntity entity)
{ {
DbSet.Update(entity); DbSet.Update(entity);
@ -72,7 +59,7 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
{ {
return DbSet.AnyAsync(entity => entity.Id == id); return DbSet.AnyAsync(entity => entity.Id == id);
} }
public void Remove(TEntity entity, bool hardDelete = false) public void Remove(TEntity entity, bool hardDelete = false)
{ {
if (hardDelete) if (hardDelete)
@ -86,4 +73,16 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
} }
} }
public int SaveChanges()
{
return _dbContext.SaveChanges();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_dbContext.Dispose();
}
}
} }

View File

@ -45,4 +45,4 @@ public sealed class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContex
_context.Dispose(); _context.Dispose();
} }
} }
} }

View File

@ -8,14 +8,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.10.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.4"/>
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.4" /> <PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="Xunit.Priority" Version="1.1.6" /> <PackageReference Include="Xunit.Priority" Version="1.1.6"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -27,8 +27,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj" /> <ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" /> <ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -24,12 +24,13 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
_fixture = fixture; _fixture = fixture;
} }
[Fact, Priority(0)] [Fact]
[Priority(0)]
public async Task Should_Create_User() public async Task Should_Create_User()
{ {
var user = new CreateUserViewModel( var user = new CreateUserViewModel(
_fixture.CreatedUserEmail, _fixture.CreatedUserEmail,
"Test", "Test",
"Email", "Email",
_fixture.CreatedUserPassword); _fixture.CreatedUserPassword);
@ -43,12 +44,13 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
_fixture.CreatedUserId = message!.Data; _fixture.CreatedUserId = message!.Data;
} }
[Fact, Priority(5)] [Fact]
[Priority(5)]
public async Task Should_Login_User() public async Task Should_Login_User()
{ {
var user = new LoginUserViewModel( var user = new LoginUserViewModel(
_fixture.CreatedUserEmail, _fixture.CreatedUserEmail,
_fixture.CreatedUserPassword); _fixture.CreatedUserPassword);
var response = await _fixture.ServerClient.PostAsJsonAsync("/api/v1/user/login", user); var response = await _fixture.ServerClient.PostAsJsonAsync("/api/v1/user/login", user);
@ -63,7 +65,8 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
_fixture.EnableAuthentication(); _fixture.EnableAuthentication();
} }
[Fact, Priority(10)] [Fact]
[Priority(10)]
public async Task Should_Get_Created_Users() public async Task Should_Get_Created_Users()
{ {
var response = await _fixture.ServerClient.GetAsync("/api/v1/user/" + _fixture.CreatedUserId); var response = await _fixture.ServerClient.GetAsync("/api/v1/user/" + _fixture.CreatedUserId);
@ -81,8 +84,9 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
content.Surname.Should().Be("Test"); content.Surname.Should().Be("Test");
content.GivenName.Should().Be("Email"); content.GivenName.Should().Be("Email");
} }
[Fact, Priority(10)] [Fact]
[Priority(10)]
public async Task Should_Get_The_Current_Active_Users() public async Task Should_Get_The_Current_Active_Users()
{ {
var response = await _fixture.ServerClient.GetAsync("/api/v1/user/me"); var response = await _fixture.ServerClient.GetAsync("/api/v1/user/me");
@ -101,7 +105,8 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
content.GivenName.Should().Be("Email"); content.GivenName.Should().Be("Email");
} }
[Fact, Priority(15)] [Fact]
[Priority(15)]
public async Task Should_Update_User() public async Task Should_Update_User()
{ {
var user = new UpdateUserViewModel( var user = new UpdateUserViewModel(
@ -124,7 +129,8 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
content.Should().BeEquivalentTo(user); content.Should().BeEquivalentTo(user);
} }
[Fact, Priority(20)] [Fact]
[Priority(20)]
public async Task Should_Get_Updated_Users() public async Task Should_Get_Updated_Users()
{ {
var response = await _fixture.ServerClient.GetAsync("/api/v1/user/" + _fixture.CreatedUserId); var response = await _fixture.ServerClient.GetAsync("/api/v1/user/" + _fixture.CreatedUserId);
@ -141,11 +147,12 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
content.Email.Should().Be("newtest@email.com"); content.Email.Should().Be("newtest@email.com");
content.Surname.Should().Be("NewTest"); content.Surname.Should().Be("NewTest");
content.GivenName.Should().Be("NewEmail"); content.GivenName.Should().Be("NewEmail");
_fixture.CreatedUserEmail = content.Email; _fixture.CreatedUserEmail = content.Email;
} }
[Fact, Priority(25)] [Fact]
[Priority(25)]
public async Task Should_Change_User_Password() public async Task Should_Change_User_Password()
{ {
var user = new ChangePasswordViewModel( var user = new ChangePasswordViewModel(
@ -163,10 +170,10 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
var content = message!.Data; var content = message!.Data;
content.Should().BeEquivalentTo(user); content.Should().BeEquivalentTo(user);
// Verify the user can login with the new password // Verify the user can login with the new password
var login = new LoginUserViewModel( var login = new LoginUserViewModel(
_fixture.CreatedUserEmail, _fixture.CreatedUserEmail,
_fixture.CreatedUserPassword + "1"); _fixture.CreatedUserPassword + "1");
var loginResponse = await _fixture.ServerClient.PostAsJsonAsync("/api/v1/user/login", login); var loginResponse = await _fixture.ServerClient.PostAsJsonAsync("/api/v1/user/login", login);
@ -178,7 +185,8 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
loginMessage?.Data.Should().NotBeEmpty(); loginMessage?.Data.Should().NotBeEmpty();
} }
[Fact, Priority(30)] [Fact]
[Priority(30)]
public async Task Should_Get_All_User() public async Task Should_Get_All_User()
{ {
var response = await _fixture.ServerClient.GetAsync("/api/v1/user"); var response = await _fixture.ServerClient.GetAsync("/api/v1/user");
@ -192,23 +200,24 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
var content = message!.Data!.ToList(); var content = message!.Data!.ToList();
content.Count.Should().Be(2); content.Count.Should().Be(2);
var currentUser = content.First(x => x.Id == _fixture.CreatedUserId); var currentUser = content.First(x => x.Id == _fixture.CreatedUserId);
currentUser.Id.Should().Be(_fixture.CreatedUserId); currentUser.Id.Should().Be(_fixture.CreatedUserId);
currentUser.Role.Should().Be(UserRole.User); currentUser.Role.Should().Be(UserRole.User);
currentUser.Email.Should().Be("newtest@email.com"); currentUser.Email.Should().Be("newtest@email.com");
currentUser.Surname.Should().Be("NewTest"); currentUser.Surname.Should().Be("NewTest");
currentUser.GivenName.Should().Be("NewEmail"); currentUser.GivenName.Should().Be("NewEmail");
var adminUser = content.First(x => x.Role == UserRole.Admin); var adminUser = content.First(x => x.Role == UserRole.Admin);
adminUser.Email.Should().Be("admin@email.com"); adminUser.Email.Should().Be("admin@email.com");
adminUser.Surname.Should().Be("Admin"); adminUser.Surname.Should().Be("Admin");
adminUser.GivenName.Should().Be("User"); adminUser.GivenName.Should().Be("User");
} }
[Fact, Priority(35)] [Fact]
[Priority(35)]
public async Task Should_Delete_User() public async Task Should_Delete_User()
{ {
var response = await _fixture.ServerClient.DeleteAsync("/api/v1/user/" + _fixture.CreatedUserId); var response = await _fixture.ServerClient.DeleteAsync("/api/v1/user/" + _fixture.CreatedUserId);
@ -222,4 +231,4 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
var content = message!.Data; var content = message!.Data;
content.Should().Be(_fixture.CreatedUserId); content.Should().Be(_fixture.CreatedUserId);
} }
} }

View File

@ -2,28 +2,29 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Common; using System.Data.Common;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.IntegrationTests.Extensions; namespace CleanArchitecture.IntegrationTests.Extensions;
public static class FunctionalTestsServiceCollectionExtensions public static class FunctionalTestsServiceCollectionExtensions
{ {
public static IServiceCollection SetupTestDatabase<TContext>(this IServiceCollection services, DbConnection connection) where TContext : DbContext public static IServiceCollection SetupTestDatabase<TContext>(this IServiceCollection services,
DbConnection connection) where TContext : DbContext
{ {
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TContext>)); var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TContext>));
if (descriptor != null) if (descriptor != null)
services.Remove(descriptor); services.Remove(descriptor);
services.AddScoped(p => services.AddScoped(p =>
DbContextOptionsFactory<TContext>( DbContextOptionsFactory<TContext>(
p, p,
(_, options) => options (_, options) => options
.ConfigureWarnings(b => b.Log(CoreEventId.ManyServiceProvidersCreatedWarning)) .ConfigureWarnings(b => b.Log(CoreEventId.ManyServiceProvidersCreatedWarning))
.UseLazyLoadingProxies() .UseLazyLoadingProxies()
.UseSqlite(connection))); .UseSqlite(connection)));
return services; return services;
} }
@ -42,4 +43,4 @@ public static class FunctionalTestsServiceCollectionExtensions
return builder.Options; return builder.Options;
} }
} }

View File

@ -10,7 +10,7 @@ public static class HttpExtensions
{ {
private static readonly JsonSerializerOptions JsonSerializerOptions = new() private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{ {
PropertyNameCaseInsensitive = true, PropertyNameCaseInsensitive = true
}; };
private static T? Deserialize<T>(string json) private static T? Deserialize<T>(string json)
@ -55,4 +55,4 @@ public static class HttpExtensions
return httpClient.PutAsync(url, content); return httpClient.PutAsync(url, content);
} }
} }

View File

@ -9,8 +9,6 @@ namespace CleanArchitecture.IntegrationTests.Fixtures;
public class TestFixtureBase public class TestFixtureBase
{ {
public HttpClient ServerClient { get; }
public TestFixtureBase() public TestFixtureBase()
{ {
var projectDir = Directory.GetCurrentDirectory(); var projectDir = Directory.GetCurrentDirectory();
@ -25,6 +23,8 @@ public class TestFixtureBase
ServerClient.Timeout = TimeSpan.FromMinutes(5); ServerClient.Timeout = TimeSpan.FromMinutes(5);
} }
public HttpClient ServerClient { get; }
protected virtual void SeedTestData(ApplicationDbContext context) protected virtual void SeedTestData(ApplicationDbContext context)
{ {
} }
@ -35,4 +35,4 @@ public class TestFixtureBase
IServiceProvider scopedServices) IServiceProvider scopedServices)
{ {
} }
} }

View File

@ -8,9 +8,9 @@ public sealed class UserTestFixture : TestFixtureBase
public string CreatedUserEmail { get; set; } = "test@email.com"; public string CreatedUserEmail { get; set; } = "test@email.com";
public string CreatedUserPassword { get; set; } = "z8]tnayvd5FNLU9:]AQm"; public string CreatedUserPassword { get; set; } = "z8]tnayvd5FNLU9:]AQm";
public string CreatedUserToken { get; set; } = string.Empty; public string CreatedUserToken { get; set; } = string.Empty;
public void EnableAuthentication() public void EnableAuthentication()
{ {
ServerClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {CreatedUserToken}"); ServerClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {CreatedUserToken}");
} }
} }

View File

@ -18,11 +18,11 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto
ServiceProvider serviceProvider, ServiceProvider serviceProvider,
IServiceProvider scopedServices); IServiceProvider scopedServices);
private readonly SqliteConnection _connection = new($"DataSource=:memory:");
private readonly AddCustomSeedDataHandler? _addCustomSeedDataHandler; private readonly AddCustomSeedDataHandler? _addCustomSeedDataHandler;
private readonly RegisterCustomServicesHandler? _registerCustomServicesHandler;
private readonly SqliteConnection _connection = new("DataSource=:memory:");
private readonly string? _environment; private readonly string? _environment;
private readonly RegisterCustomServicesHandler? _registerCustomServicesHandler;
public CleanArchitectureWebApplicationFactory( public CleanArchitectureWebApplicationFactory(
AddCustomSeedDataHandler? addCustomSeedDataHandler, AddCustomSeedDataHandler? addCustomSeedDataHandler,
@ -51,7 +51,7 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto
var sp = services.BuildServiceProvider(); var sp = services.BuildServiceProvider();
using IServiceScope scope = sp.CreateScope(); using var scope = sp.CreateScope();
var scopedServices = scope.ServiceProvider; var scopedServices = scope.ServiceProvider;
var applicationDbContext = scopedServices.GetRequiredService<ApplicationDbContext>(); var applicationDbContext = scopedServices.GetRequiredService<ApplicationDbContext>();
@ -62,4 +62,4 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto
_registerCustomServicesHandler?.Invoke(services, sp, scopedServices); _registerCustomServicesHandler?.Invoke(services, sp, scopedServices);
}); });
} }
} }

View File

@ -6,19 +6,19 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Users\Models.proto" /> <None Remove="Users\Models.proto"/>
<None Remove="Users\UsersApi.proto" /> <None Remove="Users\UsersApi.proto"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Protobuf Include="Users\Models.proto" GrpcServices="Both" /> <Protobuf Include="Users\Models.proto" GrpcServices="Both"/>
<Protobuf Include="Users\UsersApi.proto" GrpcServices="Both" /> <Protobuf Include="Users\UsersApi.proto" GrpcServices="Both"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.22.1" /> <PackageReference Include="Google.Protobuf" Version="3.22.1"/>
<PackageReference Include="Google.Protobuf.Tools" Version="3.22.1" /> <PackageReference Include="Google.Protobuf.Tools" Version="3.22.1"/>
<PackageReference Include="Grpc.AspNetCore" Version="2.52.0" /> <PackageReference Include="Grpc.AspNetCore" Version="2.52.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.10.0" /> <PackageReference Include="FluentAssertions" Version="6.10.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="MockQueryable.Moq" Version="7.0.0" /> <PackageReference Include="MockQueryable.Moq" Version="7.0.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -24,8 +24,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" /> <ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -11,37 +11,31 @@ namespace CleanArchitecture.gRPC.Tests.Fixtures;
public sealed class UserTestsFixture public sealed class UserTestsFixture
{ {
private Mock<IUserRepository> UserRepository { get; } = new ();
public UsersApiImplementation UsersApiImplementation { get; }
public IEnumerable<User> ExistingUsers { get; }
public UserTestsFixture() public UserTestsFixture()
{ {
ExistingUsers = new List<User>() ExistingUsers = new List<User>
{ {
new ( new(
Guid.NewGuid(), Guid.NewGuid(),
"test@test.de", "test@test.de",
"Test First Name", "Test First Name",
"Test Last Name", "Test Last Name",
"Test Password", "Test Password",
UserRole.User), UserRole.User),
new ( new(
Guid.NewGuid(), Guid.NewGuid(),
"email@Email.de", "email@Email.de",
"Email First Name", "Email First Name",
"Email Last Name", "Email Last Name",
"Email Password", "Email Password",
UserRole.Admin), UserRole.Admin),
new ( new(
Guid.NewGuid(), Guid.NewGuid(),
"user@user.de", "user@user.de",
"User First Name", "User First Name",
"User Last Name", "User Last Name",
"User Password", "User Password",
UserRole.User), UserRole.User)
}; };
var queryable = ExistingUsers.AsQueryable().BuildMock(); var queryable = ExistingUsers.AsQueryable().BuildMock();
@ -52,4 +46,10 @@ public sealed class UserTestsFixture
UsersApiImplementation = new UsersApiImplementation(UserRepository.Object); UsersApiImplementation = new UsersApiImplementation(UserRepository.Object);
} }
private Mock<IUserRepository> UserRepository { get; } = new();
public UsersApiImplementation UsersApiImplementation { get; }
public IEnumerable<User> ExistingUsers { get; }
} }

View File

@ -6,12 +6,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj" /> <ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4"/>
</ItemGroup> </ItemGroup>
</Project> </Project>