From a56fc0e5bb818770b4622827a8fafb4400997c3d Mon Sep 17 00:00:00 2001 From: Alexander Konietzko Date: Tue, 21 Mar 2023 11:03:05 +0100 Subject: [PATCH] Add Swagger and domain tests --- .../CleanArchitecture.Api.csproj | 3 +- .../Controllers/UserController.cs | 21 ++++- CleanArchitecture.Api/Program.cs | 53 +++++++++++- .../CleanArchitecture.Domain.Tests.csproj | 1 + .../ChangePasswordCommandHandlerTests.cs | 63 ++++++++++++++ .../ChangePasswordCommandTestFixture.cs | 52 ++++++++++++ .../ChangePasswordCommandValidationTests.cs | 11 +++ .../CreateUserCommandHandlerTests.cs | 4 +- .../CreateUserCommandValidationTests.cs | 2 +- .../LoginUser/LoginUserCommandHandlerTests.cs | 82 +++++++++++++++++++ .../LoginUser/LoginUserCommandTestFixture.cs | 55 +++++++++++++ .../LoginUserCommandValidationTests.cs | 11 +++ .../UpdateUserCommandValidationTests.cs | 2 +- .../ChangePasswordCommandValidation.cs | 20 ++++- .../LoginUser/LoginUserCommandValidation.cs | 26 +++++- .../UpdateUser/UpdateUserCommandValidation.cs | 9 ++ .../Errors/DomainErrorCodes.cs | 3 +- Todo.txt | 2 - 18 files changed, 406 insertions(+), 14 deletions(-) create mode 100644 CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs create mode 100644 CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandTestFixture.cs create mode 100644 CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandValidationTests.cs create mode 100644 CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandHandlerTests.cs create mode 100644 CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandTestFixture.cs create mode 100644 CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandValidationTests.cs delete mode 100644 Todo.txt diff --git a/CleanArchitecture.Api/CleanArchitecture.Api.csproj b/CleanArchitecture.Api/CleanArchitecture.Api.csproj index 3ba9cc6..3662059 100644 --- a/CleanArchitecture.Api/CleanArchitecture.Api.csproj +++ b/CleanArchitecture.Api/CleanArchitecture.Api.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -15,6 +15,7 @@ + diff --git a/CleanArchitecture.Api/Controllers/UserController.cs b/CleanArchitecture.Api/Controllers/UserController.cs index 07f2645..3d822b4 100644 --- a/CleanArchitecture.Api/Controllers/UserController.cs +++ b/CleanArchitecture.Api/Controllers/UserController.cs @@ -1,16 +1,19 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; +using CleanArchitecture.Api.Models; using CleanArchitecture.Application.Interfaces; using CleanArchitecture.Application.ViewModels.Users; using CleanArchitecture.Domain.Notifications; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace CleanArchitecture.Api.Controllers; [ApiController] -[Route("[controller]")] +[Route("/api/v1/[controller]")] public class UserController : ApiController { private readonly IUserService _userService; @@ -24,6 +27,8 @@ public class UserController : ApiController [Authorize] [HttpGet] + [SwaggerOperation("Get a list of all users")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage>))] public async Task GetAllUsersAsync() { var users = await _userService.GetAllUsersAsync(); @@ -32,6 +37,8 @@ public class UserController : ApiController [Authorize] [HttpGet("{id}")] + [SwaggerOperation("Get a user by id")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task GetUserByIdAsync( [FromRoute] Guid id, [FromQuery] bool isDeleted = false) @@ -42,6 +49,8 @@ public class UserController : ApiController [Authorize] [HttpGet("me")] + [SwaggerOperation("Get the current active user")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task GetCurrentUserAsync() { var user = await _userService.GetCurrentUserAsync(); @@ -49,6 +58,8 @@ public class UserController : ApiController } [HttpPost] + [SwaggerOperation("Create a new user")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task CreateUserAsync([FromBody] CreateUserViewModel viewModel) { var userId = await _userService.CreateUserAsync(viewModel); @@ -57,6 +68,8 @@ public class UserController : ApiController [Authorize] [HttpDelete("{id}")] + [SwaggerOperation("Delete a user")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task DeleteUserAsync([FromRoute] Guid id) { await _userService.DeleteUserAsync(id); @@ -65,6 +78,8 @@ public class UserController : ApiController [Authorize] [HttpPut] + [SwaggerOperation("Update a user")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task UpdateUserAsync([FromBody] UpdateUserViewModel viewModel) { await _userService.UpdateUserAsync(viewModel); @@ -73,6 +88,8 @@ public class UserController : ApiController [Authorize] [HttpPost("changePassword")] + [SwaggerOperation("Change a password for the current active user")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task ChangePasswordAsync([FromBody] ChangePasswordViewModel viewModel) { await _userService.ChangePasswordAsync(viewModel); @@ -80,6 +97,8 @@ public class UserController : ApiController } [HttpPost("login")] + [SwaggerOperation("Get a signed token for a user")] + [SwaggerResponse(200, "Request successful", typeof(ResponseMessage))] public async Task LoginUserAsync([FromBody] LoginUserViewModel viewModel) { var token = await _userService.LoginUserAsync(viewModel); diff --git a/CleanArchitecture.Api/Program.cs b/CleanArchitecture.Api/Program.cs index 5288d7d..9a76f47 100644 --- a/CleanArchitecture.Api/Program.cs +++ b/CleanArchitecture.Api/Program.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text; using CleanArchitecture.Application.Extensions; using CleanArchitecture.Domain.Extensions; @@ -10,15 +11,57 @@ using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddGrpc(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(c => +{ + c.EnableAnnotations(); + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "CleanArchitecture", + Version = "v1", + Description = "A clean architecture API", + }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme. " + + "Use the /auth/azureLogin endpoint to generate a token (use the id_token here), " + + "or create a personal access token in centralhub.", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "bearer" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + }, + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header, + }, + new List() + } + }); +}); + +builder.Services.AddHealthChecks(); builder.Services.AddHttpContextAccessor(); builder.Services.AddDbContext(options => @@ -58,8 +101,11 @@ builder.Services.AddMediatR(cfg => var app = builder.Build(); -app.UseSwagger(); -app.UseSwaggerUI(); +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} app.UseHttpsRedirection(); @@ -67,6 +113,7 @@ app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); +app.MapHealthChecks("/health"); app.MapGrpcService(); using (IServiceScope scope = app.Services.CreateScope()) diff --git a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj index 6cc4fcb..72a5f62 100644 --- a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj +++ b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs new file mode 100644 index 0000000..a6fc1e9 --- /dev/null +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandHandlerTests.cs @@ -0,0 +1,63 @@ +using System.Threading.Tasks; +using CleanArchitecture.Domain.Commands.Users.ChangePassword; +using CleanArchitecture.Domain.Errors; +using CleanArchitecture.Domain.Events.User; +using Xunit; + +namespace CleanArchitecture.Domain.Tests.CommandHandler.User.ChangePassword; + +public sealed class ChangePasswordCommandHandlerTests +{ + private readonly ChangePasswordCommandTestFixture _fixture = new(); + + [Fact] + public async Task Should_Change_Password() + { + var user = _fixture.SetupUser(); + + var command = new ChangePasswordCommand("z8]tnayvd5FNLU9:]AQm", "z8]tnayvd5FNLU9:]AQw"); + + await _fixture.CommandHandler.Handle(command, default); + + _fixture + .VerifyNoDomainNotification() + .VerifyCommit() + .VerifyRaisedEvent(x => x.UserId == user.Id); + } + + [Fact] + public async Task Should_Not_Change_Password_No_User() + { + var userId = _fixture.SetupMissingUser(); + + var command = new ChangePasswordCommand("z8]tnayvd5FNLU9:]AQm", "z8]tnayvd5FNLU9:]AQw"); + + await _fixture.CommandHandler.Handle(command, default); + + _fixture + .VerifyNoCommit() + .VerifyNoRaisedEvent() + .VerifyAnyDomainNotification() + .VerifyExistingNotification( + ErrorCodes.ObjectNotFound, + $"There is no User with Id {userId}"); + } + + [Fact] + public async Task Should_Not_Change_Password_Incorrect_Password() + { + _fixture.SetupUser(); + + var command = new ChangePasswordCommand("z8]tnayvd5FNLU9:]AQw", "z8]tnayvd5FNLU9:]AQx"); + + await _fixture.CommandHandler.Handle(command, default); + + _fixture + .VerifyNoCommit() + .VerifyNoRaisedEvent() + .VerifyAnyDomainNotification() + .VerifyExistingNotification( + DomainErrorCodes.UserPasswordIncorrect, + "The password is incorrect"); + } +} diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandTestFixture.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandTestFixture.cs new file mode 100644 index 0000000..6162a21 --- /dev/null +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandTestFixture.cs @@ -0,0 +1,52 @@ +using System; +using CleanArchitecture.Domain.Commands.Users.ChangePassword; +using CleanArchitecture.Domain.Enums; +using CleanArchitecture.Domain.Interfaces.Repositories; +using Moq; +using BC = BCrypt.Net.BCrypt; + +namespace CleanArchitecture.Domain.Tests.CommandHandler.User.ChangePassword; + +public sealed class ChangePasswordCommandTestFixture : CommandHandlerFixtureBase +{ + public ChangePasswordCommandHandler CommandHandler { get; set; } + public Mock UserRepository { get; set; } + + public ChangePasswordCommandTestFixture() + { + UserRepository = new Mock(); + + CommandHandler = new( + Bus.Object, + UnitOfWork.Object, + NotificationHandler.Object, + UserRepository.Object, + User.Object); + } + + public Entities.User SetupUser() + { + var user = new Entities.User( + Guid.NewGuid(), + "max@mustermann.com", + "Max", + "Mustermann", + BC.HashPassword("z8]tnayvd5FNLU9:]AQm"), + UserRole.User); + + User.Setup(x => x.GetUserId()).Returns(user.Id); + + UserRepository + .Setup(x => x.GetByIdAsync(It.Is(y => y == user.Id))) + .ReturnsAsync(user); + + return user; + } + + public Guid SetupMissingUser() + { + var id = Guid.NewGuid(); + User.Setup(x => x.GetUserId()).Returns(id); + return id; + } +} diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandValidationTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandValidationTests.cs new file mode 100644 index 0000000..52fd44a --- /dev/null +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/ChangePassword/ChangePasswordCommandValidationTests.cs @@ -0,0 +1,11 @@ +using CleanArchitecture.Domain.Commands.Users.ChangePassword; + +namespace CleanArchitecture.Domain.Tests.CommandHandler.User.ChangePassword; + +public sealed class ChangePasswordCommandValidationTests : + ValidationTestBase +{ + public ChangePasswordCommandValidationTests() : base(new ChangePasswordCommandValidation()) + { + } +} diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs index c4072be..50eaa79 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandHandlerTests.cs @@ -20,7 +20,7 @@ public sealed class CreateUserCommandHandlerTests "test@email.com", "Test", "Email", - "SomePassword"); + "Po=PF]PC6t.?8?ks)A6W"); _fixture.CommandHandler.Handle(command, default).Wait(); @@ -40,7 +40,7 @@ public sealed class CreateUserCommandHandlerTests "test@email.com", "Test", "Email", - "SomePassword"); + "Po=PF]PC6t.?8?ks)A6W"); _fixture.CommandHandler.Handle(command, default).Wait(); diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandValidationTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandValidationTests.cs index 2b92763..e418aa0 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandValidationTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/CreateUser/CreateUserCommandValidationTests.cs @@ -119,5 +119,5 @@ public sealed class CreateUserCommandValidationTests : email ?? "test@email.com", surName ?? "test", givenName ?? "email", - password ?? "some password"); + password ?? "Po=PF]PC6t.?8?ks)A6W"); } \ No newline at end of file diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandHandlerTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandHandlerTests.cs new file mode 100644 index 0000000..700da13 --- /dev/null +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandHandlerTests.cs @@ -0,0 +1,82 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using CleanArchitecture.Domain.Commands.Users.LoginUser; +using CleanArchitecture.Domain.Errors; +using FluentAssertions; +using Xunit; + +namespace CleanArchitecture.Domain.Tests.CommandHandler.User.LoginUser; + +public sealed class LoginUserCommandHandlerTests +{ + private readonly LoginUserCommandTestFixture _fixture = new(); + + [Fact] + public async Task Should_Login_User() + { + var user = _fixture.SetupUser(); + + var command = new LoginUserCommand(user.Email, "z8]tnayvd5FNLU9:]AQm"); + + var token = await _fixture.CommandHandler.Handle(command, default); + + _fixture.VerifyNoDomainNotification(); + + token.Should().NotBeNullOrEmpty(); + + var handler = new JwtSecurityTokenHandler(); + var decodedToken = handler.ReadToken(token) as JwtSecurityToken; + + var userIdClaim = decodedToken!.Claims + .FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.NameIdentifier)); + + Guid.Parse(userIdClaim!.Value).Should().Be(user.Id); + + var userEmailClaim = decodedToken!.Claims + .FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.Email)); + + userEmailClaim!.Value.Should().Be(user.Email); + + var userRoleClaim = decodedToken!.Claims + .FirstOrDefault(x => string.Equals(x.Type, ClaimTypes.Role)); + + userRoleClaim!.Value.Should().Be(user.Role.ToString()); + } + + [Fact] + public async Task Should_Not_Login_User_No_User() + { + var command = new LoginUserCommand("test@email.com", "z8]tnayvd5FNLU9:]AQm"); + + var token = await _fixture.CommandHandler.Handle(command, default); + + _fixture + .VerifyAnyDomainNotification() + .VerifyExistingNotification( + ErrorCodes.ObjectNotFound, + $"There is no User with Email {command.Email}"); + + token.Should().BeEmpty(); + } + + [Fact] + public async Task Should_Not_Login_User_Incorrect_Password() + { + var user = _fixture.SetupUser(); + + var command = new LoginUserCommand(user.Email, "z8]tnayvd5FNLU9:]AQw"); + + var token = await _fixture.CommandHandler.Handle(command, default); + + _fixture + .VerifyAnyDomainNotification() + .VerifyExistingNotification( + DomainErrorCodes.UserPasswordIncorrect, + "The password is incorrect"); + + token.Should().BeEmpty(); + } +} diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandTestFixture.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandTestFixture.cs new file mode 100644 index 0000000..8010194 --- /dev/null +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandTestFixture.cs @@ -0,0 +1,55 @@ +using CleanArchitecture.Domain.Commands.Users.LoginUser; +using CleanArchitecture.Domain.Enums; +using CleanArchitecture.Domain.Interfaces.Repositories; +using CleanArchitecture.Domain.Settings; +using Microsoft.Extensions.Options; +using Moq; +using System; +using BC = BCrypt.Net.BCrypt; + +namespace CleanArchitecture.Domain.Tests.CommandHandler.User.LoginUser; + +public sealed class LoginUserCommandTestFixture : CommandHandlerFixtureBase +{ + public LoginUserCommandHandler CommandHandler { get; set; } + public Mock UserRepository { get; set; } + public IOptions TokenSettings { get; set; } + + public LoginUserCommandTestFixture() + { + UserRepository = new Mock(); + + TokenSettings = Options.Create(new TokenSettings + { + Issuer = "TestIssuer", + Audience = "TestAudience", + Secret = "asjdlkasjd87439284)@#(*" + }); + + CommandHandler = new( + Bus.Object, + UnitOfWork.Object, + NotificationHandler.Object, + UserRepository.Object, + TokenSettings); + } + + public Entities.User SetupUser() + { + var user = new Entities.User( + Guid.NewGuid(), + "max@mustermann.com", + "Max", + "Mustermann", + BC.HashPassword("z8]tnayvd5FNLU9:]AQm"), + UserRole.User); + + User.Setup(x => x.GetUserId()).Returns(user.Id); + + UserRepository + .Setup(x => x.GetByEmailAsync(It.Is(y => y == user.Email))) + .ReturnsAsync(user); + + return user; + } +} diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandValidationTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandValidationTests.cs new file mode 100644 index 0000000..746e492 --- /dev/null +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/LoginUser/LoginUserCommandValidationTests.cs @@ -0,0 +1,11 @@ +using CleanArchitecture.Domain.Commands.Users.LoginUser; + +namespace CleanArchitecture.Domain.Tests.CommandHandler.User.LoginUser; + +public sealed class LoginUserCommandValidationTests : + ValidationTestBase +{ + public LoginUserCommandValidationTests() : base(new LoginUserCommandValidation()) + { + } +} diff --git a/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandValidationTests.cs b/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandValidationTests.cs index 0c80928..dcf9d11 100644 --- a/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandValidationTests.cs +++ b/CleanArchitecture.Domain.Tests/CommandHandler/User/UpdateUser/UpdateUserCommandValidationTests.cs @@ -109,7 +109,7 @@ public sealed class UpdateUserCommandValidationTests : "Given name may not be longer than 100 characters"); } - private UpdateUserCommand CreateTestCommand( + private static UpdateUserCommand CreateTestCommand( Guid? userId = null, string? email = null, string? surName = null, diff --git a/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandValidation.cs b/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandValidation.cs index 2fe6a99..e90e964 100644 --- a/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandValidation.cs +++ b/CleanArchitecture.Domain/Commands/Users/ChangePassword/ChangePasswordCommandValidation.cs @@ -1,7 +1,25 @@ -using FluentValidation; +using CleanArchitecture.Domain.Extensions.Validation; +using FluentValidation; namespace CleanArchitecture.Domain.Commands.Users.ChangePassword; public sealed class ChangePasswordCommandValidation : AbstractValidator { + public ChangePasswordCommandValidation() + { + AddRuleForPassword(); + AddRuleForNewPassword(); + } + + private void AddRuleForPassword() + { + RuleFor(cmd => cmd.Password) + .Password(); + } + + private void AddRuleForNewPassword() + { + RuleFor(cmd => cmd.NewPassword) + .Password(); + } } diff --git a/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandValidation.cs b/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandValidation.cs index 18bd23f..64bc452 100644 --- a/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandValidation.cs +++ b/CleanArchitecture.Domain/Commands/Users/LoginUser/LoginUserCommandValidation.cs @@ -1,7 +1,31 @@ -using FluentValidation; +using CleanArchitecture.Domain.Errors; +using CleanArchitecture.Domain.Extensions.Validation; +using FluentValidation; namespace CleanArchitecture.Domain.Commands.Users.LoginUser; public sealed class LoginUserCommandValidation : AbstractValidator { + public LoginUserCommandValidation() + { + AddRuleForEmail(); + AddRuleForPassword(); + } + + private void AddRuleForEmail() + { + RuleFor(cmd => cmd.Email) + .EmailAddress() + .WithErrorCode(DomainErrorCodes.UserInvalidEmail) + .WithMessage("Email is not a valid email address") + .MaximumLength(320) + .WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength) + .WithMessage("Email may not be longer than 320 characters"); + } + + private void AddRuleForPassword() + { + RuleFor(cmd => cmd.Password) + .Password(); + } } diff --git a/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandValidation.cs b/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandValidation.cs index 0b7cc35..58a901f 100644 --- a/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandValidation.cs +++ b/CleanArchitecture.Domain/Commands/Users/UpdateUser/UpdateUserCommandValidation.cs @@ -11,6 +11,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator cmd.Role) + .IsInEnum() + .WithErrorCode(DomainErrorCodes.UserInvalidRole) + .WithMessage("Role is not a valid role"); + } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Errors/DomainErrorCodes.cs b/CleanArchitecture.Domain/Errors/DomainErrorCodes.cs index 147c19f..76925e2 100644 --- a/CleanArchitecture.Domain/Errors/DomainErrorCodes.cs +++ b/CleanArchitecture.Domain/Errors/DomainErrorCodes.cs @@ -10,7 +10,8 @@ public static class DomainErrorCodes public const string UserSurnameExceedsMaxLength = "USER_SURNAME_EXCEEDS_MAX_LENGTH"; public const string UserGivenNameExceedsMaxLength = "USER_GIVEN_NAME_EXCEEDS_MAX_LENGTH"; public const string UserInvalidEmail = "USER_INVALID_EMAIL"; - + public const string UserInvalidRole = "USER_INVALID_ROLE"; + // User Password Validation public const string UserEmptyPassword = "USER_PASSWORD_MAY_NOT_BE_EMPTY"; public const string UserShortPassword = "USER_PASSWORD_MAY_NOT_BE_SHORTER_THAN_6_CHARACTERS"; diff --git a/Todo.txt b/Todo.txt deleted file mode 100644 index 3931b92..0000000 --- a/Todo.txt +++ /dev/null @@ -1,2 +0,0 @@ -- Remove warnings and apply suggestions -- Add authentication and authorization