mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-08-24 12:18:34 +00:00
Add Swagger and domain tests
This commit is contained in:
parent
3b1a76438b
commit
a56fc0e5bb
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
@ -15,6 +15,7 @@
|
|||||||
</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" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CleanArchitecture.Api.Models;
|
||||||
using CleanArchitecture.Application.Interfaces;
|
using CleanArchitecture.Application.Interfaces;
|
||||||
using CleanArchitecture.Application.ViewModels.Users;
|
using CleanArchitecture.Application.ViewModels.Users;
|
||||||
using CleanArchitecture.Domain.Notifications;
|
using CleanArchitecture.Domain.Notifications;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
|
|
||||||
namespace CleanArchitecture.Api.Controllers;
|
namespace CleanArchitecture.Api.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("/api/v1/[controller]")]
|
||||||
public class UserController : ApiController
|
public class UserController : ApiController
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
@ -24,6 +27,8 @@ public class UserController : ApiController
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
[SwaggerOperation("Get a list of all users")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<IEnumerable<UserViewModel>>))]
|
||||||
public async Task<IActionResult> GetAllUsersAsync()
|
public async Task<IActionResult> GetAllUsersAsync()
|
||||||
{
|
{
|
||||||
var users = await _userService.GetAllUsersAsync();
|
var users = await _userService.GetAllUsersAsync();
|
||||||
@ -32,6 +37,8 @@ public class UserController : ApiController
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
|
[SwaggerOperation("Get a user by id")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UserViewModel>))]
|
||||||
public async Task<IActionResult> GetUserByIdAsync(
|
public async Task<IActionResult> GetUserByIdAsync(
|
||||||
[FromRoute] Guid id,
|
[FromRoute] Guid id,
|
||||||
[FromQuery] bool isDeleted = false)
|
[FromQuery] bool isDeleted = false)
|
||||||
@ -42,6 +49,8 @@ public class UserController : ApiController
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("me")]
|
[HttpGet("me")]
|
||||||
|
[SwaggerOperation("Get the current active user")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UserViewModel>))]
|
||||||
public async Task<IActionResult> GetCurrentUserAsync()
|
public async Task<IActionResult> GetCurrentUserAsync()
|
||||||
{
|
{
|
||||||
var user = await _userService.GetCurrentUserAsync();
|
var user = await _userService.GetCurrentUserAsync();
|
||||||
@ -49,6 +58,8 @@ public class UserController : ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
[SwaggerOperation("Create a new user")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
|
||||||
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserViewModel viewModel)
|
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserViewModel viewModel)
|
||||||
{
|
{
|
||||||
var userId = await _userService.CreateUserAsync(viewModel);
|
var userId = await _userService.CreateUserAsync(viewModel);
|
||||||
@ -57,6 +68,8 @@ public class UserController : ApiController
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
|
[SwaggerOperation("Delete a user")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
|
||||||
public async Task<IActionResult> DeleteUserAsync([FromRoute] Guid id)
|
public async Task<IActionResult> DeleteUserAsync([FromRoute] Guid id)
|
||||||
{
|
{
|
||||||
await _userService.DeleteUserAsync(id);
|
await _userService.DeleteUserAsync(id);
|
||||||
@ -65,6 +78,8 @@ public class UserController : ApiController
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
|
[SwaggerOperation("Update a user")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UpdateUserViewModel>))]
|
||||||
public async Task<IActionResult> UpdateUserAsync([FromBody] UpdateUserViewModel viewModel)
|
public async Task<IActionResult> UpdateUserAsync([FromBody] UpdateUserViewModel viewModel)
|
||||||
{
|
{
|
||||||
await _userService.UpdateUserAsync(viewModel);
|
await _userService.UpdateUserAsync(viewModel);
|
||||||
@ -73,6 +88,8 @@ public class UserController : ApiController
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPost("changePassword")]
|
[HttpPost("changePassword")]
|
||||||
|
[SwaggerOperation("Change a password for the current active user")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<ChangePasswordViewModel>))]
|
||||||
public async Task<IActionResult> ChangePasswordAsync([FromBody] ChangePasswordViewModel viewModel)
|
public async Task<IActionResult> ChangePasswordAsync([FromBody] ChangePasswordViewModel viewModel)
|
||||||
{
|
{
|
||||||
await _userService.ChangePasswordAsync(viewModel);
|
await _userService.ChangePasswordAsync(viewModel);
|
||||||
@ -80,6 +97,8 @@ public class UserController : ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
|
[SwaggerOperation("Get a signed token for a user")]
|
||||||
|
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<string>))]
|
||||||
public async Task<IActionResult> LoginUserAsync([FromBody] LoginUserViewModel viewModel)
|
public async Task<IActionResult> LoginUserAsync([FromBody] LoginUserViewModel viewModel)
|
||||||
{
|
{
|
||||||
var token = await _userService.LoginUserAsync(viewModel);
|
var token = await _userService.LoginUserAsync(viewModel);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using CleanArchitecture.Application.Extensions;
|
using CleanArchitecture.Application.Extensions;
|
||||||
using CleanArchitecture.Domain.Extensions;
|
using CleanArchitecture.Domain.Extensions;
|
||||||
@ -10,15 +11,57 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddGrpc();
|
builder.Services.AddGrpc();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
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<string>()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddHealthChecks();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
@ -58,8 +101,11 @@ builder.Services.AddMediatR(cfg =>
|
|||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.UseSwagger();
|
if (app.Environment.IsDevelopment())
|
||||||
app.UseSwaggerUI();
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
@ -67,6 +113,7 @@ app.UseAuthentication();
|
|||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
app.MapHealthChecks("/health");
|
||||||
app.MapGrpcService<UsersApiImplementation>();
|
app.MapGrpcService<UsersApiImplementation>();
|
||||||
|
|
||||||
using (IServiceScope scope = app.Services.CreateScope())
|
using (IServiceScope scope = app.Services.CreateScope())
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<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" />
|
||||||
|
@ -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<PasswordChangedEvent>(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<UserUpdatedEvent>()
|
||||||
|
.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<UserUpdatedEvent>()
|
||||||
|
.VerifyAnyDomainNotification()
|
||||||
|
.VerifyExistingNotification(
|
||||||
|
DomainErrorCodes.UserPasswordIncorrect,
|
||||||
|
"The password is incorrect");
|
||||||
|
}
|
||||||
|
}
|
@ -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<IUserRepository> UserRepository { get; set; }
|
||||||
|
|
||||||
|
public ChangePasswordCommandTestFixture()
|
||||||
|
{
|
||||||
|
UserRepository = new Mock<IUserRepository>();
|
||||||
|
|
||||||
|
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<Guid>(y => y == user.Id)))
|
||||||
|
.ReturnsAsync(user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid SetupMissingUser()
|
||||||
|
{
|
||||||
|
var id = Guid.NewGuid();
|
||||||
|
User.Setup(x => x.GetUserId()).Returns(id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using CleanArchitecture.Domain.Commands.Users.ChangePassword;
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.ChangePassword;
|
||||||
|
|
||||||
|
public sealed class ChangePasswordCommandValidationTests :
|
||||||
|
ValidationTestBase<ChangePasswordCommand, ChangePasswordCommandValidation>
|
||||||
|
{
|
||||||
|
public ChangePasswordCommandValidationTests() : base(new ChangePasswordCommandValidation())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ public sealed class CreateUserCommandHandlerTests
|
|||||||
"test@email.com",
|
"test@email.com",
|
||||||
"Test",
|
"Test",
|
||||||
"Email",
|
"Email",
|
||||||
"SomePassword");
|
"Po=PF]PC6t.?8?ks)A6W");
|
||||||
|
|
||||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ public sealed class CreateUserCommandHandlerTests
|
|||||||
"test@email.com",
|
"test@email.com",
|
||||||
"Test",
|
"Test",
|
||||||
"Email",
|
"Email",
|
||||||
"SomePassword");
|
"Po=PF]PC6t.?8?ks)A6W");
|
||||||
|
|
||||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||||
|
|
||||||
|
@ -119,5 +119,5 @@ public sealed class CreateUserCommandValidationTests :
|
|||||||
email ?? "test@email.com",
|
email ?? "test@email.com",
|
||||||
surName ?? "test",
|
surName ?? "test",
|
||||||
givenName ?? "email",
|
givenName ?? "email",
|
||||||
password ?? "some password");
|
password ?? "Po=PF]PC6t.?8?ks)A6W");
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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<IUserRepository> UserRepository { get; set; }
|
||||||
|
public IOptions<TokenSettings> TokenSettings { get; set; }
|
||||||
|
|
||||||
|
public LoginUserCommandTestFixture()
|
||||||
|
{
|
||||||
|
UserRepository = new Mock<IUserRepository>();
|
||||||
|
|
||||||
|
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<string>(y => y == user.Email)))
|
||||||
|
.ReturnsAsync(user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using CleanArchitecture.Domain.Commands.Users.LoginUser;
|
||||||
|
|
||||||
|
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.LoginUser;
|
||||||
|
|
||||||
|
public sealed class LoginUserCommandValidationTests :
|
||||||
|
ValidationTestBase<LoginUserCommand, LoginUserCommandValidation>
|
||||||
|
{
|
||||||
|
public LoginUserCommandValidationTests() : base(new LoginUserCommandValidation())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -109,7 +109,7 @@ public sealed class UpdateUserCommandValidationTests :
|
|||||||
"Given name may not be longer than 100 characters");
|
"Given name may not be longer than 100 characters");
|
||||||
}
|
}
|
||||||
|
|
||||||
private UpdateUserCommand CreateTestCommand(
|
private static UpdateUserCommand CreateTestCommand(
|
||||||
Guid? userId = null,
|
Guid? userId = null,
|
||||||
string? email = null,
|
string? email = null,
|
||||||
string? surName = null,
|
string? surName = null,
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
using FluentValidation;
|
using CleanArchitecture.Domain.Extensions.Validation;
|
||||||
|
using FluentValidation;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Commands.Users.ChangePassword;
|
namespace CleanArchitecture.Domain.Commands.Users.ChangePassword;
|
||||||
|
|
||||||
public sealed class ChangePasswordCommandValidation : AbstractValidator<ChangePasswordCommand>
|
public sealed class ChangePasswordCommandValidation : AbstractValidator<ChangePasswordCommand>
|
||||||
{
|
{
|
||||||
|
public ChangePasswordCommandValidation()
|
||||||
|
{
|
||||||
|
AddRuleForPassword();
|
||||||
|
AddRuleForNewPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddRuleForPassword()
|
||||||
|
{
|
||||||
|
RuleFor(cmd => cmd.Password)
|
||||||
|
.Password();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddRuleForNewPassword()
|
||||||
|
{
|
||||||
|
RuleFor(cmd => cmd.NewPassword)
|
||||||
|
.Password();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,31 @@
|
|||||||
using FluentValidation;
|
using CleanArchitecture.Domain.Errors;
|
||||||
|
using CleanArchitecture.Domain.Extensions.Validation;
|
||||||
|
using FluentValidation;
|
||||||
|
|
||||||
namespace CleanArchitecture.Domain.Commands.Users.LoginUser;
|
namespace CleanArchitecture.Domain.Commands.Users.LoginUser;
|
||||||
|
|
||||||
public sealed class LoginUserCommandValidation : AbstractValidator<LoginUserCommand>
|
public sealed class LoginUserCommandValidation : AbstractValidator<LoginUserCommand>
|
||||||
{
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
|||||||
AddRuleForEmail();
|
AddRuleForEmail();
|
||||||
AddRuleForSurname();
|
AddRuleForSurname();
|
||||||
AddRuleForGivenName();
|
AddRuleForGivenName();
|
||||||
|
AddRuleForRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddRuleForId()
|
private void AddRuleForId()
|
||||||
@ -53,4 +54,12 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
|||||||
.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 AddRuleForRole()
|
||||||
|
{
|
||||||
|
RuleFor(cmd => cmd.Role)
|
||||||
|
.IsInEnum()
|
||||||
|
.WithErrorCode(DomainErrorCodes.UserInvalidRole)
|
||||||
|
.WithMessage("Role is not a valid role");
|
||||||
|
}
|
||||||
}
|
}
|
@ -10,7 +10,8 @@ public static class DomainErrorCodes
|
|||||||
public const string UserSurnameExceedsMaxLength = "USER_SURNAME_EXCEEDS_MAX_LENGTH";
|
public const string UserSurnameExceedsMaxLength = "USER_SURNAME_EXCEEDS_MAX_LENGTH";
|
||||||
public const string UserGivenNameExceedsMaxLength = "USER_GIVEN_NAME_EXCEEDS_MAX_LENGTH";
|
public const string UserGivenNameExceedsMaxLength = "USER_GIVEN_NAME_EXCEEDS_MAX_LENGTH";
|
||||||
public const string UserInvalidEmail = "USER_INVALID_EMAIL";
|
public const string UserInvalidEmail = "USER_INVALID_EMAIL";
|
||||||
|
public const string UserInvalidRole = "USER_INVALID_ROLE";
|
||||||
|
|
||||||
// User Password Validation
|
// User Password Validation
|
||||||
public const string UserEmptyPassword = "USER_PASSWORD_MAY_NOT_BE_EMPTY";
|
public const string UserEmptyPassword = "USER_PASSWORD_MAY_NOT_BE_EMPTY";
|
||||||
public const string UserShortPassword = "USER_PASSWORD_MAY_NOT_BE_SHORTER_THAN_6_CHARACTERS";
|
public const string UserShortPassword = "USER_PASSWORD_MAY_NOT_BE_SHORTER_THAN_6_CHARACTERS";
|
||||||
|
Loading…
Reference in New Issue
Block a user