mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-06-30 02:31:08 +00:00
Add domain tests
This commit is contained in:
parent
305e6320f0
commit
e937e786a7
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -35,20 +36,23 @@ public class UserController : ApiController
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public string CreateUserAsync()
|
||||
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserViewModel viewModel)
|
||||
{
|
||||
return "test";
|
||||
await _userService.CreateUserAsync(viewModel);
|
||||
return Response();
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public string DeleteUserAsync([FromRoute] Guid id)
|
||||
public async Task<IActionResult> DeleteUserAsync([FromRoute] Guid id)
|
||||
{
|
||||
return "test";
|
||||
await _userService.DeleteUserAsync(id);
|
||||
return Response(id);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public string UpdateUserAsync()
|
||||
public async Task<IActionResult> UpdateUserAsync([FromBody] UpdateUserViewModel viewModel)
|
||||
{
|
||||
return "test";
|
||||
await _userService.UpdateUserAsync(viewModel);
|
||||
return Response(viewModel);
|
||||
}
|
||||
}
|
@ -19,6 +19,6 @@ public sealed class GetAllUsersQueryHandlerTests
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.Should().ContainSingle();
|
||||
result.FirstOrDefault().Id.Should().Be(_fixture.ExistingUserId);
|
||||
result.FirstOrDefault()!.Id.Should().Be(_fixture.ExistingUserId);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
using CleanArchitecture.Application.Services;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
|
||||
namespace CleanArchitecture.Application.Interfaces;
|
||||
|
||||
@ -9,4 +9,7 @@ public interface IUserService
|
||||
{
|
||||
public Task<UserViewModel?> GetUserByUserIdAsync(Guid userId);
|
||||
public Task<IEnumerable<UserViewModel>> GetAllUsersAsync();
|
||||
public Task CreateUserAsync(CreateUserViewModel user);
|
||||
public Task UpdateUserAsync(UpdateUserViewModel user);
|
||||
public Task DeleteUserAsync(Guid userId);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
|
@ -4,7 +4,10 @@ using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
using CleanArchitecture.Application.ViewModels;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
|
||||
namespace CleanArchitecture.Application.Services;
|
||||
@ -27,4 +30,27 @@ public sealed class UserService : IUserService
|
||||
{
|
||||
return await _bus.QueryAsync(new GetAllUsersQuery());
|
||||
}
|
||||
|
||||
public async Task CreateUserAsync(CreateUserViewModel user)
|
||||
{
|
||||
await _bus.SendCommandAsync(new CreateUserCommand(
|
||||
Guid.NewGuid(),
|
||||
user.Email,
|
||||
user.Surname,
|
||||
user.GivenName));
|
||||
}
|
||||
|
||||
public async Task UpdateUserAsync(UpdateUserViewModel user)
|
||||
{
|
||||
await _bus.SendCommandAsync(new UpdateUserCommand(
|
||||
user.Id,
|
||||
user.Email,
|
||||
user.Surname,
|
||||
user.GivenName));
|
||||
}
|
||||
|
||||
public async Task DeleteUserAsync(Guid userId)
|
||||
{
|
||||
await _bus.SendCommandAsync(new DeleteUserCommand(userId));
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace CleanArchitecture.Application.ViewModels.Users;
|
||||
|
||||
public sealed record CreateUserViewModel(
|
||||
string Email,
|
||||
string Surname,
|
||||
string GivenName);
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Application.ViewModels.Users;
|
||||
|
||||
public sealed record UpdateUserViewModel(
|
||||
Guid Id,
|
||||
string Email,
|
||||
string Surname,
|
||||
string GivenName);
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
|
||||
namespace CleanArchitecture.Application.ViewModels;
|
||||
namespace CleanArchitecture.Application.ViewModels.Users;
|
||||
|
||||
public sealed class UserViewModel
|
||||
{
|
@ -0,0 +1,52 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommandHandlerTests
|
||||
{
|
||||
private readonly CreateUserCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Create_User()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
var command = new CreateUserCommand(
|
||||
Guid.NewGuid(),
|
||||
"test@email.com",
|
||||
"Test",
|
||||
"Email");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<UserCreatedEvent>(x => x.UserId == command.UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_Already_Existing_User()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
var command = new CreateUserCommand(
|
||||
user.Id,
|
||||
"test@email.com",
|
||||
"Test",
|
||||
"Email");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
.VerifyNoRaisedEvent<UserCreatedEvent>()
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
DomainErrorCodes.UserAlreadyExists,
|
||||
$"There is already a User with Id {command.UserId}");
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommandTestFixture : CommandHandlerFixtureBase
|
||||
{
|
||||
public CreateUserCommandHandler CommandHandler { get; }
|
||||
private Mock<IUserRepository> UserRepository { get; }
|
||||
|
||||
public CreateUserCommandTestFixture()
|
||||
{
|
||||
UserRepository = new Mock<IUserRepository>();
|
||||
|
||||
CommandHandler = new(
|
||||
Bus.Object,
|
||||
UnitOfWork.Object,
|
||||
NotificationHandler.Object,
|
||||
UserRepository.Object);
|
||||
}
|
||||
|
||||
public Entities.User SetupUser()
|
||||
{
|
||||
var user = new Entities.User(
|
||||
Guid.NewGuid(),
|
||||
"max@mustermann.com",
|
||||
"Max",
|
||||
"Mustermann");
|
||||
|
||||
UserRepository
|
||||
.Setup(x => x.GetByIdAsync(It.Is<Guid>(y => y == user.Id)))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommandValidationTests :
|
||||
ValidationTestBase<CreateUserCommand, CreateUserCommandValidation>
|
||||
{
|
||||
public CreateUserCommandValidationTests() : base(new CreateUserCommandValidation())
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Valid()
|
||||
{
|
||||
var command = CreateTestCommand();
|
||||
|
||||
ShouldBeValid(command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_User_Id()
|
||||
{
|
||||
var command = CreateTestCommand(userId: Guid.Empty);
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyId,
|
||||
"User id may not be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_Email()
|
||||
{
|
||||
var command = CreateTestCommand(email: string.Empty);
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Invalid_Email()
|
||||
{
|
||||
var command = CreateTestCommand(email: "not a email");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Email_Exceeds_Max_Length()
|
||||
{
|
||||
var command = CreateTestCommand(email: new string('a', 320) + "@test.com");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmailExceedsMaxLength,
|
||||
"Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_Surname()
|
||||
{
|
||||
var command = CreateTestCommand(surName: "");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptySurname,
|
||||
"Surname may not be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Surname_Exceeds_Max_Length()
|
||||
{
|
||||
var command = CreateTestCommand(surName: new string('a', 101));
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserSurnameExceedsMaxLength,
|
||||
"Surname may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_Given_Name()
|
||||
{
|
||||
var command = CreateTestCommand(givenName: "");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyGivenName,
|
||||
"Given name may not be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Given_Name_Exceeds_Max_Length()
|
||||
{
|
||||
var command = CreateTestCommand(givenName: new string('a', 101));
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserGivenNameExceedsMaxLength,
|
||||
"Given name may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
private CreateUserCommand CreateTestCommand(
|
||||
Guid? userId = null,
|
||||
string? email = null,
|
||||
string? surName = null,
|
||||
string? givenName = null) =>
|
||||
new (
|
||||
userId ?? Guid.NewGuid(),
|
||||
email ?? "test@email.com",
|
||||
surName ?? "test",
|
||||
givenName ?? "email");
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommandHandlerTests
|
||||
{
|
||||
private readonly DeleteUserCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Delete_User()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
var command = new DeleteUserCommand(user.Id);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<UserDeletedEvent>(x => x.UserId == user.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Delete_Non_Existing_User()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
var command = new DeleteUserCommand(Guid.NewGuid());
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
.VerifyNoRaisedEvent<UserDeletedEvent>()
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
ErrorCodes.ObjectNotFound,
|
||||
$"There is no User with Id {command.UserId}");
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommandTestFixture : CommandHandlerFixtureBase
|
||||
{
|
||||
public DeleteUserCommandHandler CommandHandler { get; }
|
||||
private Mock<IUserRepository> UserRepository { get; }
|
||||
|
||||
public DeleteUserCommandTestFixture()
|
||||
{
|
||||
UserRepository = new Mock<IUserRepository>();
|
||||
|
||||
CommandHandler = new (
|
||||
Bus.Object,
|
||||
UnitOfWork.Object,
|
||||
NotificationHandler.Object,
|
||||
UserRepository.Object);
|
||||
}
|
||||
|
||||
public Entities.User SetupUser()
|
||||
{
|
||||
var user = new Entities.User(
|
||||
Guid.NewGuid(),
|
||||
"max@mustermann.com",
|
||||
"Max",
|
||||
"Mustermann");
|
||||
|
||||
UserRepository
|
||||
.Setup(x => x.GetByIdAsync(It.Is<Guid>(y => y == user.Id)))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommandValidationTests :
|
||||
ValidationTestBase<DeleteUserCommand, DeleteUserCommandValidation>
|
||||
{
|
||||
public DeleteUserCommandValidationTests() : base(new DeleteUserCommandValidation())
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Valid()
|
||||
{
|
||||
var command = CreateTestCommand();
|
||||
|
||||
ShouldBeValid(command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_User_Id()
|
||||
{
|
||||
var command = CreateTestCommand(userId: Guid.Empty);
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyId,
|
||||
"User id may not be empty");
|
||||
}
|
||||
|
||||
private DeleteUserCommand CreateTestCommand(Guid? userId = null) =>
|
||||
new (userId ?? Guid.NewGuid());
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommandHandlerTests
|
||||
{
|
||||
private readonly UpdateUserCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Update_User()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
var command = new UpdateUserCommand(
|
||||
user.Id,
|
||||
"test@email.com",
|
||||
"Test",
|
||||
"Email");
|
||||
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<UserUpdatedEvent>(x => x.UserId == command.UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Not_Update_Non_Existing_User()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
var command = new UpdateUserCommand(
|
||||
Guid.NewGuid(),
|
||||
"test@email.com",
|
||||
"Test",
|
||||
"Email");
|
||||
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
.VerifyNoRaisedEvent<UserUpdatedEvent>()
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
ErrorCodes.ObjectNotFound,
|
||||
$"There is no User with Id {command.UserId}");
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using Moq;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommandTestFixture : CommandHandlerFixtureBase
|
||||
{
|
||||
public UpdateUserCommandHandler CommandHandler { get; }
|
||||
private Mock<IUserRepository> UserRepository { get; }
|
||||
|
||||
public UpdateUserCommandTestFixture()
|
||||
{
|
||||
UserRepository = new Mock<IUserRepository>();
|
||||
|
||||
CommandHandler = new(
|
||||
Bus.Object,
|
||||
UnitOfWork.Object,
|
||||
NotificationHandler.Object,
|
||||
UserRepository.Object);
|
||||
}
|
||||
|
||||
public Entities.User SetupUser()
|
||||
{
|
||||
var user = new Entities.User(
|
||||
Guid.NewGuid(),
|
||||
"max@mustermann.com",
|
||||
"Max",
|
||||
"Mustermann");
|
||||
|
||||
UserRepository
|
||||
.Setup(x => x.GetByIdAsync(It.Is<Guid>(y => y == user.Id)))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using Xunit;
|
||||
|
||||
namespace CleanArchitecture.Domain.Tests.CommandHandler.User.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommandValidationTests :
|
||||
ValidationTestBase<UpdateUserCommand, UpdateUserCommandValidation>
|
||||
{
|
||||
public UpdateUserCommandValidationTests() : base(new UpdateUserCommandValidation())
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Valid()
|
||||
{
|
||||
var command = CreateTestCommand();
|
||||
|
||||
ShouldBeValid(command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_User_Id()
|
||||
{
|
||||
var command = CreateTestCommand(userId: Guid.Empty);
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyId,
|
||||
"User id may not be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_Email()
|
||||
{
|
||||
var command = CreateTestCommand(email: string.Empty);
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Invalid_Email()
|
||||
{
|
||||
var command = CreateTestCommand(email: "not a email");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Email_Exceeds_Max_Length()
|
||||
{
|
||||
var command = CreateTestCommand(email: new string('a', 320) + "@test.com");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmailExceedsMaxLength,
|
||||
"Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_Surname()
|
||||
{
|
||||
var command = CreateTestCommand(surName: "");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptySurname,
|
||||
"Surname may not be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Surname_Exceeds_Max_Length()
|
||||
{
|
||||
var command = CreateTestCommand(surName: new string('a', 101));
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserSurnameExceedsMaxLength,
|
||||
"Surname may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Empty_Given_Name()
|
||||
{
|
||||
var command = CreateTestCommand(givenName: "");
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyGivenName,
|
||||
"Given name may not be empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Be_Invalid_For_Given_Name_Exceeds_Max_Length()
|
||||
{
|
||||
var command = CreateTestCommand(givenName: new string('a', 101));
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserGivenNameExceedsMaxLength,
|
||||
"Given name may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
private UpdateUserCommand CreateTestCommand(
|
||||
Guid? userId = null,
|
||||
string? email = null,
|
||||
string? surName = null,
|
||||
string? givenName = null) =>
|
||||
new (
|
||||
userId ?? Guid.NewGuid(),
|
||||
email ?? "test@email.com",
|
||||
surName ?? "test",
|
||||
givenName ?? "email");
|
||||
}
|
@ -10,9 +10,4 @@
|
||||
<PackageReference Include="MediatR" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Commands\Users\CreateUser" />
|
||||
<Folder Include="Commands\Users\UpdateUser" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommand : CommandBase
|
||||
{
|
||||
private static readonly CreateUserCommandValidation _validation = new();
|
||||
|
||||
public Guid UserId { get; }
|
||||
public string Email { get; }
|
||||
public string Surname { get; }
|
||||
public string GivenName { get; }
|
||||
|
||||
public CreateUserCommand(
|
||||
Guid userId,
|
||||
string email,
|
||||
string surname,
|
||||
string givenName) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
Email = email;
|
||||
Surname = surname;
|
||||
GivenName = givenName;
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<CreateUserCommand>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public CreateUserCommandHandler(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
IUserRepository userRepository) : base(bus, unitOfWork, notifications)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task Handle(CreateUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await TestValidityAsync(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existingUser = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (existingUser != null)
|
||||
{
|
||||
await _bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is already a User with Id {request.UserId}",
|
||||
DomainErrorCodes.UserAlreadyExists));
|
||||
return;
|
||||
}
|
||||
|
||||
var user = new User(
|
||||
request.UserId,
|
||||
request.Email,
|
||||
request.Surname,
|
||||
request.GivenName);
|
||||
|
||||
_userRepository.Add(user);
|
||||
|
||||
if (await CommitAsync())
|
||||
{
|
||||
await _bus.RaiseEventAsync(new UserCreatedEvent(user.Id));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCommand>
|
||||
{
|
||||
public CreateUserCommandValidation()
|
||||
{
|
||||
AddRuleForId();
|
||||
AddRuleForEmail();
|
||||
AddRuleForSurname();
|
||||
AddRuleForGivenName();
|
||||
}
|
||||
|
||||
private void AddRuleForId()
|
||||
{
|
||||
RuleFor(cmd => cmd.UserId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyId)
|
||||
.WithMessage("User id may not be empty");
|
||||
}
|
||||
|
||||
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 AddRuleForSurname()
|
||||
{
|
||||
RuleFor(cmd => cmd.Surname)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptySurname)
|
||||
.WithMessage("Surname may not be empty")
|
||||
.MaximumLength(100)
|
||||
.WithErrorCode(DomainErrorCodes.UserSurnameExceedsMaxLength)
|
||||
.WithMessage("Surname may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
private void AddRuleForGivenName()
|
||||
{
|
||||
RuleFor(cmd => cmd.GivenName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyGivenName)
|
||||
.WithMessage("Given name may not be empty")
|
||||
.MaximumLength(100)
|
||||
.WithErrorCode(DomainErrorCodes.UserGivenNameExceedsMaxLength)
|
||||
.WithMessage("Given name may not be longer than 100 characters");
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ public sealed class DeleteUserCommandHandler : CommandHandlerBase,
|
||||
|
||||
_userRepository.Remove(user);
|
||||
|
||||
if (!await CommitAsync())
|
||||
if (await CommitAsync())
|
||||
{
|
||||
await _bus.RaiseEventAsync(new UserDeletedEvent(request.UserId));
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommand : CommandBase
|
||||
{
|
||||
private readonly UpdateUserCommandValidation _validation = new();
|
||||
|
||||
public Guid UserId { get; }
|
||||
public string Email { get; }
|
||||
public string Surname { get; }
|
||||
public string GivenName { get; }
|
||||
|
||||
public UpdateUserCommand(
|
||||
Guid userId,
|
||||
string email,
|
||||
string surname,
|
||||
string givenName) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
Email = email;
|
||||
Surname = surname;
|
||||
GivenName = givenName;
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<UpdateUserCommand>
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public UpdateUserCommandHandler(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
IUserRepository userRepository) : base(bus, unitOfWork, notifications)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task Handle(UpdateUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await TestValidityAsync(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
await _bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no User with Id {request.UserId}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
return;
|
||||
}
|
||||
|
||||
user.SetEmail(request.Email);
|
||||
user.SetSurname(request.Surname);
|
||||
user.SetGivenName(request.GivenName);
|
||||
|
||||
_userRepository.Update(user);
|
||||
|
||||
if (await CommitAsync())
|
||||
{
|
||||
await _bus.RaiseEventAsync(new UserUpdatedEvent(user.Id));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCommand>
|
||||
{
|
||||
public UpdateUserCommandValidation()
|
||||
{
|
||||
AddRuleForId();
|
||||
AddRuleForEmail();
|
||||
AddRuleForSurname();
|
||||
AddRuleForGivenName();
|
||||
}
|
||||
|
||||
private void AddRuleForId()
|
||||
{
|
||||
RuleFor(cmd => cmd.UserId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyId)
|
||||
.WithMessage("User id may not be empty");
|
||||
}
|
||||
|
||||
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 AddRuleForSurname()
|
||||
{
|
||||
RuleFor(cmd => cmd.Surname)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptySurname)
|
||||
.WithMessage("Surname may not be empty")
|
||||
.MaximumLength(100)
|
||||
.WithErrorCode(DomainErrorCodes.UserSurnameExceedsMaxLength)
|
||||
.WithMessage("Surname may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
private void AddRuleForGivenName()
|
||||
{
|
||||
RuleFor(cmd => cmd.GivenName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyGivenName)
|
||||
.WithMessage("Given name may not be empty")
|
||||
.MaximumLength(100)
|
||||
.WithErrorCode(DomainErrorCodes.UserGivenNameExceedsMaxLength)
|
||||
.WithMessage("Given name may not be longer than 100 characters");
|
||||
}
|
||||
}
|
@ -2,5 +2,15 @@ namespace CleanArchitecture.Domain.Errors;
|
||||
|
||||
public static class DomainErrorCodes
|
||||
{
|
||||
// User Validation
|
||||
public const string UserEmptyId = "USER_EMPTY_ID";
|
||||
public const string UserEmptySurname = "USER_EMPTY_SURNAME";
|
||||
public const string UserEmptyGivenName = "USER_EMPTY_GIVEN_NAME";
|
||||
public const string UserEmailExceedsMaxLength = "USER_EMAIL_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 UserInvalidEmail = "USER_INVALID_EMAIL";
|
||||
|
||||
// User
|
||||
public const string UserAlreadyExists = "USER_ALREADY_EXISTS";
|
||||
}
|
@ -6,10 +6,22 @@ using MediatR;
|
||||
namespace CleanArchitecture.Domain.EventHandler;
|
||||
|
||||
public sealed class UserEventHandler :
|
||||
INotificationHandler<UserDeletedEvent>
|
||||
INotificationHandler<UserDeletedEvent>,
|
||||
INotificationHandler<UserCreatedEvent>,
|
||||
INotificationHandler<UserUpdatedEvent>
|
||||
{
|
||||
public Task Handle(UserDeletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(UserUpdatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
13
CleanArchitecture.Domain/Events/User/UserCreatedEvent.cs
Normal file
13
CleanArchitecture.Domain/Events/User/UserCreatedEvent.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Events.User;
|
||||
|
||||
public sealed class UserCreatedEvent : DomainEvent
|
||||
{
|
||||
public Guid UserId { get; }
|
||||
|
||||
public UserCreatedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
}
|
13
CleanArchitecture.Domain/Events/User/UserUpdatedEvent.cs
Normal file
13
CleanArchitecture.Domain/Events/User/UserUpdatedEvent.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Events.User;
|
||||
|
||||
public sealed class UserUpdatedEvent : DomainEvent
|
||||
{
|
||||
public Guid UserId { get; }
|
||||
|
||||
public UserUpdatedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
using CleanArchitecture.Domain.EventHandler;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using MediatR;
|
||||
@ -11,6 +13,8 @@ public static class ServiceCollectionExtension
|
||||
public static IServiceCollection AddCommandHandlers(this IServiceCollection services)
|
||||
{
|
||||
// User
|
||||
services.AddScoped<IRequestHandler<CreateUserCommand>, CreateUserCommandHandler>();
|
||||
services.AddScoped<IRequestHandler<UpdateUserCommand>, UpdateUserCommandHandler>();
|
||||
services.AddScoped<IRequestHandler<DeleteUserCommand>, DeleteUserCommandHandler>();
|
||||
|
||||
return services;
|
||||
@ -19,6 +23,8 @@ public static class ServiceCollectionExtension
|
||||
public static IServiceCollection AddNotificationHandlers(this IServiceCollection services)
|
||||
{
|
||||
// User
|
||||
services.AddScoped<INotificationHandler<UserCreatedEvent>, UserEventHandler>();
|
||||
services.AddScoped<INotificationHandler<UserUpdatedEvent>, UserEventHandler>();
|
||||
services.AddScoped<INotificationHandler<UserDeletedEvent>, UserEventHandler>();
|
||||
|
||||
return services;
|
||||
|
Loading…
Reference in New Issue
Block a user