From 50bd42abf0a5cb4e6d7129d45ac2d1b7348d6a90 Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Wed, 1 Feb 2023 04:48:24 -0800 Subject: [PATCH 1/6] Implementing the repository and uow patterns --- README.md | 2 +- src/Wax.Api/Filters/GlobalExceptionFilter.cs | 4 +-- src/Wax.Core/ApplicationModule.cs | 9 +++--- src/Wax.Core/Data/ApplicationDbContext.cs | 3 +- .../Domain/Customers/ICustomerRepository.cs | 6 ---- .../Customers/CreateCustomerCommandHandler.cs | 10 +++--- .../Customers/DeleteCustomerCommandHandler.cs | 13 ++++---- .../Customers/UpdateCustomerCommandHandler.cs | 13 ++++---- .../Customers/GetCustomerRequestHandler.cs | 17 +++++----- .../EfCoreBasicRepository.cs} | 31 ++++++++++--------- .../Repositories/EfCoreCustomerRepository.cs | 5 +-- src/Wax.Core/Repositories/EfCoreRepository.cs | 23 ++++++++++++++ .../IBasicRepository.cs} | 7 +++-- .../Repositories/ICustomerRepository.cs | 8 +++++ src/Wax.Core/Repositories/IRepository.cs | 7 +++++ src/Wax.Core/Repositories/IUnitOfWork.cs | 6 ++++ .../Customers/CustomerTests.cs | 16 +++++----- .../Customers/CreateCustomerTests.cs | 11 ++++--- .../Customers/CustomerTestFixture.cs | 9 ++++-- .../Customers/GetCustomerTests.cs | 5 ++- .../Customers/UpdateCustomerTests.cs | 10 +++--- 21 files changed, 132 insertions(+), 83 deletions(-) delete mode 100644 src/Wax.Core/Domain/Customers/ICustomerRepository.cs rename src/Wax.Core/{Data/Repositories/EfCoreRepository.cs => Repositories/EfCoreBasicRepository.cs} (57%) rename src/Wax.Core/{Data => }/Repositories/EfCoreCustomerRepository.cs (76%) create mode 100644 src/Wax.Core/Repositories/EfCoreRepository.cs rename src/Wax.Core/{Domain/IRepository.cs => Repositories/IBasicRepository.cs} (76%) create mode 100644 src/Wax.Core/Repositories/ICustomerRepository.cs create mode 100644 src/Wax.Core/Repositories/IRepository.cs create mode 100644 src/Wax.Core/Repositories/IUnitOfWork.cs diff --git a/README.md b/README.md index 0c655ed..eb972e8 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ WilTechs Architecture Solution Template for .NET 6 Using dotnet cli template, install the template: ``` -dotnet new -i Wax.Template +dotnet new install Wax.Template ``` Run this command to create the solution: diff --git a/src/Wax.Api/Filters/GlobalExceptionFilter.cs b/src/Wax.Api/Filters/GlobalExceptionFilter.cs index 3b749a6..a928398 100644 --- a/src/Wax.Api/Filters/GlobalExceptionFilter.cs +++ b/src/Wax.Api/Filters/GlobalExceptionFilter.cs @@ -40,12 +40,12 @@ private void HandleBusinessException(ExceptionContext context) var problemDetails = new ProblemDetails { - Status = StatusCodes.Status403Forbidden, + Status = StatusCodes.Status409Conflict, Title = "Business error", Detail = context.Exception.Message, - Instance = context.HttpContext.Request.Path }; + //problemDetails.Extensions.Add(new KeyValuePair("code", "1234")); context.Result = new ObjectResult(problemDetails); } diff --git a/src/Wax.Core/ApplicationModule.cs b/src/Wax.Core/ApplicationModule.cs index dd67cf9..f4af135 100644 --- a/src/Wax.Core/ApplicationModule.cs +++ b/src/Wax.Core/ApplicationModule.cs @@ -6,11 +6,10 @@ using Microsoft.EntityFrameworkCore; using Serilog; using Wax.Core.Data; -using Wax.Core.Data.Repositories; using Wax.Core.DependencyInjection; -using Wax.Core.Domain; using Wax.Core.Middlewares.FluentMessageValidator; using Wax.Core.Middlewares.Logging; +using Wax.Core.Repositories; using Wax.Core.Services.Identity; using Module = Autofac.Module; @@ -84,9 +83,11 @@ private void RegisterDatabase(ContainerBuilder builder) }).AsSelf().As() .InstancePerLifetimeScope(); - builder.RegisterGeneric(typeof(EfCoreRepository<>)) - .As(typeof(IRepository<>)) + builder.RegisterGeneric(typeof(EfCoreBasicRepository<>)) + .As(typeof(IBasicRepository<>)) .InstancePerLifetimeScope(); + + builder.RegisterType().As().InstancePerLifetimeScope(); } private void RegisterIdentity(ContainerBuilder builder) diff --git a/src/Wax.Core/Data/ApplicationDbContext.cs b/src/Wax.Core/Data/ApplicationDbContext.cs index 9eabfd9..6133933 100644 --- a/src/Wax.Core/Data/ApplicationDbContext.cs +++ b/src/Wax.Core/Data/ApplicationDbContext.cs @@ -1,9 +1,10 @@ using Microsoft.EntityFrameworkCore; using Wax.Core.Data.Configurations; +using Wax.Core.Repositories; namespace Wax.Core.Data; -public class ApplicationDbContext : DbContext +public class ApplicationDbContext : DbContext, IUnitOfWork { public ApplicationDbContext(DbContextOptions options) : base(options) { diff --git a/src/Wax.Core/Domain/Customers/ICustomerRepository.cs b/src/Wax.Core/Domain/Customers/ICustomerRepository.cs deleted file mode 100644 index cbc8272..0000000 --- a/src/Wax.Core/Domain/Customers/ICustomerRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Wax.Core.Domain.Customers; - -public interface ICustomerRepository : IRepository -{ - Task CheckIsUniqueNameAsync(string name, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs index aa3e086..164c080 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs @@ -3,6 +3,7 @@ using Mediator.Net.Contracts; using Wax.Core.Domain.Customers; using Wax.Core.Domain.Customers.Exceptions; +using Wax.Core.Repositories; using Wax.Messages.Commands.Customers; namespace Wax.Core.Handlers.CommandHandlers.Customers @@ -10,9 +11,9 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers public class CreateCustomerCommandHandler : ICommandHandler { private readonly IMapper _mapper; - private readonly ICustomerRepository _repository; + private readonly IRepository _repository; - public CreateCustomerCommandHandler(IMapper mapper, ICustomerRepository repository) + public CreateCustomerCommandHandler(IMapper mapper, IRepository repository) { _mapper = mapper; _repository = repository; @@ -21,7 +22,7 @@ public CreateCustomerCommandHandler(IMapper mapper, ICustomerRepository reposito public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - if (!await _repository.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) + if (!await _repository.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) .ConfigureAwait(false)) { throw new CustomerNameAlreadyExistsException(); @@ -29,7 +30,8 @@ public async Task Handle(IReceiveContext(context.Message); - await _repository.InsertAsync(customer, cancellationToken).ConfigureAwait(false); + await _repository.Customers.InsertAsync(customer, cancellationToken).ConfigureAwait(false); + await _repository.SaveChangesAsync(cancellationToken); return new CreateCustomerResponse { CustomerId = customer.Id }; } diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs index 3866e4a..23b1d7b 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs @@ -1,23 +1,24 @@ using Mediator.Net.Context; using Mediator.Net.Contracts; -using Wax.Core.Domain.Customers; +using Wax.Core.Repositories; using Wax.Messages.Commands.Customers; namespace Wax.Core.Handlers.CommandHandlers.Customers; public class DeleteCustomerCommandHandler: ICommandHandler { - private readonly ICustomerRepository _customerRepository; + private readonly IRepository _repository; - public DeleteCustomerCommandHandler(ICustomerRepository customerRepository) + public DeleteCustomerCommandHandler(IRepository repository) { - _customerRepository = customerRepository; + _repository = repository; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _customerRepository.GetByIdAsync(context.Message.CustomerId, cancellationToken); + var customer = await _repository.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); - await _customerRepository.DeleteAsync(customer, cancellationToken); + await _repository.Customers.DeleteAsync(customer, cancellationToken); + await _repository.SaveChangesAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs index 170d838..1df74a8 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs @@ -1,8 +1,8 @@ using AutoMapper; using Mediator.Net.Context; using Mediator.Net.Contracts; -using Wax.Core.Domain.Customers; using Wax.Core.Domain.Customers.Exceptions; +using Wax.Core.Repositories; using Wax.Messages.Commands.Customers; namespace Wax.Core.Handlers.CommandHandlers.Customers; @@ -10,9 +10,9 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers; public class UpdateCustomerCommandHandler : ICommandHandler { private readonly IMapper _mapper; - private readonly ICustomerRepository _repository; + private readonly IRepository _repository; - public UpdateCustomerCommandHandler(IMapper mapper, ICustomerRepository repository) + public UpdateCustomerCommandHandler(IMapper mapper, IRepository repository) { _mapper = mapper; _repository = repository; @@ -20,12 +20,12 @@ public UpdateCustomerCommandHandler(IMapper mapper, ICustomerRepository reposito public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _repository.GetByIdAsync(context.Message.CustomerId, cancellationToken) + var customer = await _repository.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken) .ConfigureAwait(false); if (customer.Name != context.Message.Name) { - if (!await _repository.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) + if (!await _repository.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) .ConfigureAwait(false)) { throw new CustomerNameAlreadyExistsException(); @@ -34,6 +34,7 @@ public async Task Handle(IReceiveContext context, Cancell _mapper.Map(context.Message, customer); - await _repository.UpdateAsync(customer, cancellationToken).ConfigureAwait(false); + await _repository.Customers.UpdateAsync(customer, cancellationToken).ConfigureAwait(false); + await _repository.SaveChangesAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs b/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs index 9a8c10f..4f2e901 100644 --- a/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs +++ b/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs @@ -1,9 +1,7 @@ using AutoMapper; -using AutoMapper.QueryableExtensions; using Mediator.Net.Context; using Mediator.Net.Contracts; -using Microsoft.EntityFrameworkCore; -using Wax.Core.Domain.Customers; +using Wax.Core.Repositories; using Wax.Messages.Dtos.Customers; using Wax.Messages.Requests.Customers; @@ -12,23 +10,22 @@ namespace Wax.Core.Handlers.RequestHandlers.Customers; public class GetCustomerRequestHandler : IRequestHandler { private readonly IMapper _mapper; - private readonly ICustomerRepository _customerRepository; + private readonly IRepository _repository; - public GetCustomerRequestHandler(IMapper mapper, ICustomerRepository customerRepository) + public GetCustomerRequestHandler(IMapper mapper, IRepository repository) { _mapper = mapper; - _customerRepository = customerRepository; + _repository = repository; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { + var customer = await _repository.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); + return new GetCustomerResponse { - Customer = await _customerRepository.Query.AsNoTracking() - .Where(c => c.Id == context.Message.CustomerId) - .ProjectTo(_mapper.ConfigurationProvider) - .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false) + Customer = _mapper.Map(customer) }; } } \ No newline at end of file diff --git a/src/Wax.Core/Data/Repositories/EfCoreRepository.cs b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs similarity index 57% rename from src/Wax.Core/Data/Repositories/EfCoreRepository.cs rename to src/Wax.Core/Repositories/EfCoreBasicRepository.cs index 3ed76a7..a54092a 100644 --- a/src/Wax.Core/Data/Repositories/EfCoreRepository.cs +++ b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs @@ -1,13 +1,16 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Wax.Core.Data; using Wax.Core.Domain; using Wax.Core.Exceptions; -namespace Wax.Core.Data.Repositories; +namespace Wax.Core.Repositories; -public class EfCoreRepository : IRepository where TEntity : class, IEntity +public class EfCoreBasicRepository : IBasicRepository where TEntity : class, IEntity { private readonly ApplicationDbContext _dbContext; - public EfCoreRepository(ApplicationDbContext dbContext) + public EfCoreBasicRepository(ApplicationDbContext dbContext) { _dbContext = dbContext; } @@ -29,7 +32,6 @@ public async Task GetByIdAsync(TKey id, CancellationToken cancell public async Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) { await _dbContext.Set().AddAsync(entity, cancellationToken).ConfigureAwait(false); - await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); return entity; } @@ -37,29 +39,30 @@ public async Task> InsertRangeAsync(IEnumerable en CancellationToken cancellationToken = default) { await _dbContext.Set().AddRangeAsync(entity, cancellationToken).ConfigureAwait(false); - await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); return entity; } - public async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) + public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) { - _dbContext.Update(entity); - await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + _dbContext.Entry(entity).State = EntityState.Modified; + return Task.CompletedTask; } - public async Task UpdateRangeAsync(IEnumerable entities, + public Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) { _dbContext.Set().UpdateRange(entities); - - await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + return Task.CompletedTask; } - public async Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) + public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) { _dbContext.Set().Remove(entity); - await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + return Task.CompletedTask; } - public IQueryable Query => _dbContext.Set(); + public IQueryable Query(Expression> predicate) + { + return _dbContext.Set().Where(predicate); + } } \ No newline at end of file diff --git a/src/Wax.Core/Data/Repositories/EfCoreCustomerRepository.cs b/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs similarity index 76% rename from src/Wax.Core/Data/Repositories/EfCoreCustomerRepository.cs rename to src/Wax.Core/Repositories/EfCoreCustomerRepository.cs index eaa0cbc..e3f5e15 100644 --- a/src/Wax.Core/Data/Repositories/EfCoreCustomerRepository.cs +++ b/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs @@ -1,10 +1,11 @@ using Microsoft.EntityFrameworkCore; +using Wax.Core.Data; using Wax.Core.DependencyInjection; using Wax.Core.Domain.Customers; -namespace Wax.Core.Data.Repositories; +namespace Wax.Core.Repositories; -public class EfCoreCustomerRepository : EfCoreRepository, ICustomerRepository, IScopedDependency +public class EfCoreCustomerRepository : EfCoreBasicRepository, ICustomerRepository, IScopedDependency { private readonly DbSet _customers; diff --git a/src/Wax.Core/Repositories/EfCoreRepository.cs b/src/Wax.Core/Repositories/EfCoreRepository.cs new file mode 100644 index 0000000..ed1f585 --- /dev/null +++ b/src/Wax.Core/Repositories/EfCoreRepository.cs @@ -0,0 +1,23 @@ +using Autofac; +using Wax.Core.Data; + +namespace Wax.Core.Repositories; + +public class EfCoreRepository : IRepository +{ + private readonly ApplicationDbContext _context; + private readonly ILifetimeScope _lifetimeScope; + + public EfCoreRepository(ApplicationDbContext context, ILifetimeScope lifetimeScope) + { + _context = context; + _lifetimeScope = lifetimeScope; + } + + public ICustomerRepository Customers => _lifetimeScope.Resolve(); + + public Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + return _context.SaveChangesAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Wax.Core/Domain/IRepository.cs b/src/Wax.Core/Repositories/IBasicRepository.cs similarity index 76% rename from src/Wax.Core/Domain/IRepository.cs rename to src/Wax.Core/Repositories/IBasicRepository.cs index 656997f..a557eb5 100644 --- a/src/Wax.Core/Domain/IRepository.cs +++ b/src/Wax.Core/Repositories/IBasicRepository.cs @@ -1,8 +1,9 @@ using System.Linq.Expressions; +using Wax.Core.Domain; -namespace Wax.Core.Domain; +namespace Wax.Core.Repositories; -public interface IRepository where TEntity : class, IEntity +public interface IBasicRepository where TEntity : class, IEntity { Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default) where TKey : notnull; @@ -17,5 +18,5 @@ Task> InsertRangeAsync(IEnumerable entity, Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default); - IQueryable Query { get; } + IQueryable Query(Expression> predicate); } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/ICustomerRepository.cs b/src/Wax.Core/Repositories/ICustomerRepository.cs new file mode 100644 index 0000000..9298c08 --- /dev/null +++ b/src/Wax.Core/Repositories/ICustomerRepository.cs @@ -0,0 +1,8 @@ +using Wax.Core.Domain.Customers; + +namespace Wax.Core.Repositories; + +public interface ICustomerRepository : IBasicRepository +{ + Task CheckIsUniqueNameAsync(string name, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IRepository.cs b/src/Wax.Core/Repositories/IRepository.cs new file mode 100644 index 0000000..9363fa1 --- /dev/null +++ b/src/Wax.Core/Repositories/IRepository.cs @@ -0,0 +1,7 @@ +namespace Wax.Core.Repositories; + +public interface IRepository +{ + public ICustomerRepository Customers { get; } + Task SaveChangesAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IUnitOfWork.cs b/src/Wax.Core/Repositories/IUnitOfWork.cs new file mode 100644 index 0000000..6c32826 --- /dev/null +++ b/src/Wax.Core/Repositories/IUnitOfWork.cs @@ -0,0 +1,6 @@ +namespace Wax.Core.Repositories; + +public interface IUnitOfWork +{ + Task SaveChangesAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/tests/Wax.IntegrationTests/Customers/CustomerTests.cs b/tests/Wax.IntegrationTests/Customers/CustomerTests.cs index 6e6053c..d44aaeb 100644 --- a/tests/Wax.IntegrationTests/Customers/CustomerTests.cs +++ b/tests/Wax.IntegrationTests/Customers/CustomerTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Mediator.Net; using Shouldly; +using Wax.Core.Exceptions; using Wax.Messages.Commands.Customers; using Wax.Messages.Requests.Customers; using Xunit; @@ -68,7 +69,7 @@ await Run(async mediator => public async Task ShouldDeleteCustomer() { var customerId = await CreateDefaultCustomer(); - + await Run(async mediator => { await mediator.SendAsync(new DeleteCustomerCommand @@ -76,13 +77,12 @@ await mediator.SendAsync(new DeleteCustomerCommand CustomerId = customerId }); - var getCustomerResponse = await mediator.RequestAsync( - new GetCustomerRequest - { - CustomerId = customerId - }); - - getCustomerResponse.Customer.ShouldBeNull(); + await Should.ThrowAsync(async () => + await mediator.RequestAsync( + new GetCustomerRequest + { + CustomerId = customerId + })); }); } diff --git a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs index ca2de73..b724080 100644 --- a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs @@ -27,8 +27,9 @@ public async Task ShouldNotCreateCustomerWhenNameAlreadyExists() { Name = "microsoft" }; - - Repository.CheckIsUniqueNameAsync(command.Name).Returns(false); + + + Customers.CheckIsUniqueNameAsync(command.Name).Returns(false); await Should.ThrowAsync(async () => await _handler.Handle(new ReceiveContext(command), CancellationToken.None)); @@ -42,11 +43,11 @@ public async Task ShouldCallInsert() Name = "microsoft", Contact = "+861306888888" }; - - Repository.CheckIsUniqueNameAsync(command.Name).Returns(true); + + Customers.CheckIsUniqueNameAsync(command.Name).Returns(true); await _handler.Handle(new ReceiveContext(command), CancellationToken.None); - await Repository.Received().InsertAsync(Arg.Any()); + await Customers.Received().InsertAsync(Arg.Any()); } } \ No newline at end of file diff --git a/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs b/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs index 09302ab..d654797 100644 --- a/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs +++ b/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs @@ -1,18 +1,21 @@ using AutoMapper; using NSubstitute; -using Wax.Core.Domain.Customers; using Wax.Core.Profiles; +using Wax.Core.Repositories; namespace Wax.UnitTests.Customers; public class CustomerTestFixture { protected readonly IMapper Mapper; - protected readonly ICustomerRepository Repository; + protected readonly ICustomerRepository Customers; + protected readonly IRepository Repository; protected CustomerTestFixture() { Mapper = new MapperConfiguration(x => x.AddProfile(new CustomerProfile())).CreateMapper(); - Repository = Substitute.For(); + Customers = Substitute.For(); + Repository = Substitute.For(); + Repository.Customers.Returns(Customers); } } \ No newline at end of file diff --git a/tests/Wax.UnitTests/Customers/GetCustomerTests.cs b/tests/Wax.UnitTests/Customers/GetCustomerTests.cs index c880d1c..f12e33f 100644 --- a/tests/Wax.UnitTests/Customers/GetCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/GetCustomerTests.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using Mediator.Net.Context; -using MockQueryable.NSubstitute; using NSubstitute; using Shouldly; using Wax.Core.Domain.Customers; @@ -15,7 +14,7 @@ namespace Wax.UnitTests.Customers; public class GetCustomerTests : CustomerTestFixture { private readonly GetCustomerRequestHandler _handler; - + public GetCustomerTests() { _handler = new GetCustomerRequestHandler(Mapper, Repository); @@ -32,7 +31,7 @@ public async Task ShouldGetCustomer() Contact = "+861306888888" }; - Repository.Query.Returns(new[] { customer }.BuildMock()); + Customers.GetByIdAsync(customer.Id).Returns(Task.FromResult(customer)); var response = await _handler.Handle(new ReceiveContext( new GetCustomerRequest diff --git a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs index e9890d3..11e98de 100644 --- a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs @@ -32,10 +32,10 @@ public async Task ShouldNotUpdateCustomerWhenNameAlreadyExists() Name = "microsoft" }; - Repository.GetByIdAsync(command.CustomerId) + Customers.GetByIdAsync(command.CustomerId) .Returns(new Customer { Id = command.CustomerId, Name = "google" }); - Repository.CheckIsUniqueNameAsync(command.Name).Returns(false); + Customers.CheckIsUniqueNameAsync(command.Name).Returns(false); await Should.ThrowAsync(async () => await _handler.Handle(new ReceiveContext(command), CancellationToken.None)); @@ -53,14 +53,14 @@ public async Task ShouldUpdateCustomer() Contact = "+861306888888" }; - Repository.GetByIdAsync(command.CustomerId).Returns(customer); - Repository.CheckIsUniqueNameAsync(command.Name).Returns(true); + Customers.GetByIdAsync(command.CustomerId).Returns(customer); + Customers.CheckIsUniqueNameAsync(command.Name).Returns(true); await _handler.Handle(new ReceiveContext(command), CancellationToken.None); customer.Name.ShouldBe(command.Name); customer.Contact.ShouldBe(command.Contact); - await Repository.Received().UpdateAsync(Arg.Any()); + await Customers.Received().UpdateAsync(Arg.Any()); } } \ No newline at end of file From 11b94b3a6b07d69d9b4845ee67c72f540e48dcf1 Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Wed, 1 Feb 2023 17:27:18 -0800 Subject: [PATCH 2/6] Rename uow --- src/Wax.Core/ApplicationModule.cs | 2 +- src/Wax.Core/Data/ApplicationDbContext.cs | 3 +-- .../Customers/CreateCustomerCommandHandler.cs | 12 ++++++------ .../Customers/DeleteCustomerCommandHandler.cs | 12 ++++++------ .../Customers/UpdateCustomerCommandHandler.cs | 14 +++++++------- .../Customers/GetCustomerRequestHandler.cs | 8 ++++---- src/Wax.Core/Repositories/EfCoreBasicRepository.cs | 4 ++-- src/Wax.Core/Repositories/IBasicRepository.cs | 2 +- src/Wax.Core/Repositories/IRepository.cs | 7 ------- src/Wax.Core/Repositories/IUnitOfWork.cs | 3 ++- .../{EfCoreRepository.cs => UnitOfWork.cs} | 4 ++-- .../{UnitTest1.cs => GlobalExceptionTests.cs} | 2 +- .../Wax.UnitTests/Customers/CreateCustomerTests.cs | 2 +- .../Wax.UnitTests/Customers/CustomerTestFixture.cs | 6 +++--- tests/Wax.UnitTests/Customers/GetCustomerTests.cs | 2 +- .../Wax.UnitTests/Customers/UpdateCustomerTests.cs | 2 +- 16 files changed, 39 insertions(+), 46 deletions(-) delete mode 100644 src/Wax.Core/Repositories/IRepository.cs rename src/Wax.Core/Repositories/{EfCoreRepository.cs => UnitOfWork.cs} (79%) rename tests/Wax.E2ETests/{UnitTest1.cs => GlobalExceptionTests.cs} (75%) diff --git a/src/Wax.Core/ApplicationModule.cs b/src/Wax.Core/ApplicationModule.cs index f4af135..c4e4b8c 100644 --- a/src/Wax.Core/ApplicationModule.cs +++ b/src/Wax.Core/ApplicationModule.cs @@ -87,7 +87,7 @@ private void RegisterDatabase(ContainerBuilder builder) .As(typeof(IBasicRepository<>)) .InstancePerLifetimeScope(); - builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); } private void RegisterIdentity(ContainerBuilder builder) diff --git a/src/Wax.Core/Data/ApplicationDbContext.cs b/src/Wax.Core/Data/ApplicationDbContext.cs index 6133933..9eabfd9 100644 --- a/src/Wax.Core/Data/ApplicationDbContext.cs +++ b/src/Wax.Core/Data/ApplicationDbContext.cs @@ -1,10 +1,9 @@ using Microsoft.EntityFrameworkCore; using Wax.Core.Data.Configurations; -using Wax.Core.Repositories; namespace Wax.Core.Data; -public class ApplicationDbContext : DbContext, IUnitOfWork +public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions options) : base(options) { diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs index 164c080..9aff87e 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs @@ -11,18 +11,18 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers public class CreateCustomerCommandHandler : ICommandHandler { private readonly IMapper _mapper; - private readonly IRepository _repository; + private readonly IUnitOfWork _unitOfWork; - public CreateCustomerCommandHandler(IMapper mapper, IRepository repository) + public CreateCustomerCommandHandler(IMapper mapper, IUnitOfWork unitOfWork) { _mapper = mapper; - _repository = repository; + _unitOfWork = unitOfWork; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - if (!await _repository.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) + if (!await _unitOfWork.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) .ConfigureAwait(false)) { throw new CustomerNameAlreadyExistsException(); @@ -30,8 +30,8 @@ public async Task Handle(IReceiveContext(context.Message); - await _repository.Customers.InsertAsync(customer, cancellationToken).ConfigureAwait(false); - await _repository.SaveChangesAsync(cancellationToken); + await _unitOfWork.Customers.InsertAsync(customer, cancellationToken).ConfigureAwait(false); + await _unitOfWork.SaveChangesAsync(cancellationToken); return new CreateCustomerResponse { CustomerId = customer.Id }; } diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs index 23b1d7b..1b8d94e 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs @@ -7,18 +7,18 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers; public class DeleteCustomerCommandHandler: ICommandHandler { - private readonly IRepository _repository; + private readonly IUnitOfWork _unitOfWork; - public DeleteCustomerCommandHandler(IRepository repository) + public DeleteCustomerCommandHandler(IUnitOfWork unitOfWork) { - _repository = repository; + _unitOfWork = unitOfWork; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _repository.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); + var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); - await _repository.Customers.DeleteAsync(customer, cancellationToken); - await _repository.SaveChangesAsync(cancellationToken); + await _unitOfWork.Customers.DeleteAsync(customer, cancellationToken); + await _unitOfWork.SaveChangesAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs index 1df74a8..89573ac 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs @@ -10,22 +10,22 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers; public class UpdateCustomerCommandHandler : ICommandHandler { private readonly IMapper _mapper; - private readonly IRepository _repository; + private readonly IUnitOfWork _unitOfWork; - public UpdateCustomerCommandHandler(IMapper mapper, IRepository repository) + public UpdateCustomerCommandHandler(IMapper mapper, IUnitOfWork unitOfWork) { _mapper = mapper; - _repository = repository; + _unitOfWork = unitOfWork; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _repository.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken) + var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken) .ConfigureAwait(false); if (customer.Name != context.Message.Name) { - if (!await _repository.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) + if (!await _unitOfWork.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) .ConfigureAwait(false)) { throw new CustomerNameAlreadyExistsException(); @@ -34,7 +34,7 @@ public async Task Handle(IReceiveContext context, Cancell _mapper.Map(context.Message, customer); - await _repository.Customers.UpdateAsync(customer, cancellationToken).ConfigureAwait(false); - await _repository.SaveChangesAsync(cancellationToken); + await _unitOfWork.Customers.UpdateAsync(customer, cancellationToken).ConfigureAwait(false); + await _unitOfWork.SaveChangesAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs b/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs index 4f2e901..a4dae7f 100644 --- a/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs +++ b/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs @@ -10,18 +10,18 @@ namespace Wax.Core.Handlers.RequestHandlers.Customers; public class GetCustomerRequestHandler : IRequestHandler { private readonly IMapper _mapper; - private readonly IRepository _repository; + private readonly IUnitOfWork _unitOfWork; - public GetCustomerRequestHandler(IMapper mapper, IRepository repository) + public GetCustomerRequestHandler(IMapper mapper, IUnitOfWork unitOfWork) { _mapper = mapper; - _repository = repository; + _unitOfWork = unitOfWork; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _repository.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); + var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); return new GetCustomerResponse { diff --git a/src/Wax.Core/Repositories/EfCoreBasicRepository.cs b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs index a54092a..e38585b 100644 --- a/src/Wax.Core/Repositories/EfCoreBasicRepository.cs +++ b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs @@ -61,8 +61,8 @@ public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = de return Task.CompletedTask; } - public IQueryable Query(Expression> predicate) + public IQueryable Query(Expression> predicate = null) { - return _dbContext.Set().Where(predicate); + return predicate != null ? _dbContext.Set().Where(predicate) : _dbContext.Set(); } } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IBasicRepository.cs b/src/Wax.Core/Repositories/IBasicRepository.cs index a557eb5..1213aaa 100644 --- a/src/Wax.Core/Repositories/IBasicRepository.cs +++ b/src/Wax.Core/Repositories/IBasicRepository.cs @@ -18,5 +18,5 @@ Task> InsertRangeAsync(IEnumerable entity, Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default); - IQueryable Query(Expression> predicate); + IQueryable Query(Expression> predicate = null); } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IRepository.cs b/src/Wax.Core/Repositories/IRepository.cs deleted file mode 100644 index 9363fa1..0000000 --- a/src/Wax.Core/Repositories/IRepository.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Wax.Core.Repositories; - -public interface IRepository -{ - public ICustomerRepository Customers { get; } - Task SaveChangesAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IUnitOfWork.cs b/src/Wax.Core/Repositories/IUnitOfWork.cs index 6c32826..c466450 100644 --- a/src/Wax.Core/Repositories/IUnitOfWork.cs +++ b/src/Wax.Core/Repositories/IUnitOfWork.cs @@ -2,5 +2,6 @@ namespace Wax.Core.Repositories; public interface IUnitOfWork { - Task SaveChangesAsync(CancellationToken cancellationToken = default); + public ICustomerRepository Customers { get; } + Task SaveChangesAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/EfCoreRepository.cs b/src/Wax.Core/Repositories/UnitOfWork.cs similarity index 79% rename from src/Wax.Core/Repositories/EfCoreRepository.cs rename to src/Wax.Core/Repositories/UnitOfWork.cs index ed1f585..3714e08 100644 --- a/src/Wax.Core/Repositories/EfCoreRepository.cs +++ b/src/Wax.Core/Repositories/UnitOfWork.cs @@ -3,12 +3,12 @@ namespace Wax.Core.Repositories; -public class EfCoreRepository : IRepository +public class UnitOfWork : IUnitOfWork { private readonly ApplicationDbContext _context; private readonly ILifetimeScope _lifetimeScope; - public EfCoreRepository(ApplicationDbContext context, ILifetimeScope lifetimeScope) + public UnitOfWork(ApplicationDbContext context, ILifetimeScope lifetimeScope) { _context = context; _lifetimeScope = lifetimeScope; diff --git a/tests/Wax.E2ETests/UnitTest1.cs b/tests/Wax.E2ETests/GlobalExceptionTests.cs similarity index 75% rename from tests/Wax.E2ETests/UnitTest1.cs rename to tests/Wax.E2ETests/GlobalExceptionTests.cs index c8262d0..eb08979 100644 --- a/tests/Wax.E2ETests/UnitTest1.cs +++ b/tests/Wax.E2ETests/GlobalExceptionTests.cs @@ -2,7 +2,7 @@ namespace Wax.E2ETests { - public class UnitTest1 + public class GlobalExceptionTests { [Fact] public void Test1() diff --git a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs index b724080..16d2b9f 100644 --- a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs @@ -17,7 +17,7 @@ public class CreateCustomerTests : CustomerTestFixture public CreateCustomerTests() { - _handler = new CreateCustomerCommandHandler(Mapper, Repository); + _handler = new CreateCustomerCommandHandler(Mapper, UnitOfWork); } [Fact] diff --git a/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs b/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs index d654797..21bef07 100644 --- a/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs +++ b/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs @@ -9,13 +9,13 @@ public class CustomerTestFixture { protected readonly IMapper Mapper; protected readonly ICustomerRepository Customers; - protected readonly IRepository Repository; + protected readonly IUnitOfWork UnitOfWork; protected CustomerTestFixture() { Mapper = new MapperConfiguration(x => x.AddProfile(new CustomerProfile())).CreateMapper(); Customers = Substitute.For(); - Repository = Substitute.For(); - Repository.Customers.Returns(Customers); + UnitOfWork = Substitute.For(); + UnitOfWork.Customers.Returns(Customers); } } \ No newline at end of file diff --git a/tests/Wax.UnitTests/Customers/GetCustomerTests.cs b/tests/Wax.UnitTests/Customers/GetCustomerTests.cs index f12e33f..8282e77 100644 --- a/tests/Wax.UnitTests/Customers/GetCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/GetCustomerTests.cs @@ -17,7 +17,7 @@ public class GetCustomerTests : CustomerTestFixture public GetCustomerTests() { - _handler = new GetCustomerRequestHandler(Mapper, Repository); + _handler = new GetCustomerRequestHandler(Mapper, UnitOfWork); } [Fact] diff --git a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs index 11e98de..52ff41f 100644 --- a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs @@ -20,7 +20,7 @@ public class UpdateCustomerTests : CustomerTestFixture public UpdateCustomerTests() { - _handler = new UpdateCustomerCommandHandler(Mapper, Repository); + _handler = new UpdateCustomerCommandHandler(Mapper, UnitOfWork); } [Fact] From 581c964cf1333580db320d7e595daea1ff9566f8 Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Wed, 1 Feb 2023 18:29:41 -0800 Subject: [PATCH 3/6] Enrich ex filter and add test --- src/Wax.Api/Filters/GlobalExceptionFilter.cs | 10 ++- .../Repositories/EfCoreBasicRepository.cs | 2 +- tests/Wax.E2ETests/GlobalExceptionTests.cs | 84 ++++++++++++++++++- tests/Wax.E2ETests/Wax.E2ETests.csproj | 6 ++ 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/Wax.Api/Filters/GlobalExceptionFilter.cs b/src/Wax.Api/Filters/GlobalExceptionFilter.cs index a928398..d0d704d 100644 --- a/src/Wax.Api/Filters/GlobalExceptionFilter.cs +++ b/src/Wax.Api/Filters/GlobalExceptionFilter.cs @@ -7,10 +7,12 @@ namespace Wax.Api.Filters; public class GlobalExceptionFilter : IExceptionFilter { private readonly Serilog.ILogger _logger; + private readonly IWebHostEnvironment _env; - public GlobalExceptionFilter(Serilog.ILogger logger) + public GlobalExceptionFilter(Serilog.ILogger logger, IWebHostEnvironment env) { _logger = logger; + _env = env; } public void OnException(ExceptionContext context) @@ -41,7 +43,7 @@ private void HandleBusinessException(ExceptionContext context) var problemDetails = new ProblemDetails { Status = StatusCodes.Status409Conflict, - Title = "Business error", + Title = "A business error occur.", Detail = context.Exception.Message, }; @@ -84,8 +86,8 @@ private void HandleInternalServerError(ExceptionContext context) var problemDetails = new ProblemDetails { Status = StatusCodes.Status500InternalServerError, - Title = "Internal error", - Detail = "An error occur.Try it again later." + Title = "Internal error.", + Detail = _env.IsDevelopment() ? context.Exception.Message : "An error occur. Try it again later." }; context.Result = new ObjectResult(problemDetails); diff --git a/src/Wax.Core/Repositories/EfCoreBasicRepository.cs b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs index e38585b..f606546 100644 --- a/src/Wax.Core/Repositories/EfCoreBasicRepository.cs +++ b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs @@ -19,7 +19,7 @@ public async Task GetByIdAsync(TKey id, CancellationToken cancell where TKey : notnull { var entity = await _dbContext.Set() - .FindAsync(new object?[] { id }, cancellationToken).ConfigureAwait(false); + .FindAsync(new object[] { id }, cancellationToken).ConfigureAwait(false); if (entity == null) { diff --git a/tests/Wax.E2ETests/GlobalExceptionTests.cs b/tests/Wax.E2ETests/GlobalExceptionTests.cs index eb08979..0cde9da 100644 --- a/tests/Wax.E2ETests/GlobalExceptionTests.cs +++ b/tests/Wax.E2ETests/GlobalExceptionTests.cs @@ -1,13 +1,93 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Testing; +using Newtonsoft.Json; +using Shouldly; +using Wax.Api; +using Wax.Messages.Commands.Customers; using Xunit; namespace Wax.E2ETests { - public class GlobalExceptionTests + public class GlobalExceptionTests : IClassFixture> { + private readonly WebApplicationFactory _factory; + + public GlobalExceptionTests(WebApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task ShouldReturn400StatusCodeWhenInvalidCommand() + { + var response = await PostAsync("customers", new CreateCustomerCommand { }); + + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + + var content = await response.Content.ReadAsStringAsync(); + + var problemDetails = JsonConvert.DeserializeObject(content); + + problemDetails.Title.ShouldBe("One or more validation errors occurred."); + problemDetails.Errors.Count.ShouldBeGreaterThanOrEqualTo(1); + } + + [Fact] + public async Task ShouldReturn404StatusCodeWhenEntityNotFound() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync($"/customers/{Guid.NewGuid()}"); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + + var content = await response.Content.ReadAsStringAsync(); + + var problemDetails = JsonConvert.DeserializeObject(content); + + problemDetails.Title.ShouldBe("The specified resource was not found."); + problemDetails.Detail.ShouldStartWith("Entity not found"); + } + [Fact] - public void Test1() + public async Task ShouldReturn409StatusCodeWhenBusinessError() + { + var message = new CreateCustomerCommand + { + Name = "Microsoft", + Address = "Microsoft Corporation One Microsoft Way Redmond, WA 98052-6399 USA", + Contact = "(800) 426-9400" + }; + + var response = await PostAsync("customers", message); + + response.EnsureSuccessStatusCode(); + + response = await PostAsync("customers", message); + + response.StatusCode.ShouldBe(HttpStatusCode.Conflict); + + var content = await response.Content.ReadAsStringAsync(); + + var problemDetails = JsonConvert.DeserializeObject(content); + + problemDetails.Title.ShouldBe("A business error occur."); + problemDetails.Detail.ShouldBe("Customer with this name already exists."); + } + + private async Task PostAsync(string url, T body) where T : class { + var client = _factory.CreateClient(); + + var response = await client.PostAsync(url, + new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json")); + return response; } } } \ No newline at end of file diff --git a/tests/Wax.E2ETests/Wax.E2ETests.csproj b/tests/Wax.E2ETests/Wax.E2ETests.csproj index cb11089..73474ab 100644 --- a/tests/Wax.E2ETests/Wax.E2ETests.csproj +++ b/tests/Wax.E2ETests/Wax.E2ETests.csproj @@ -8,7 +8,9 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,4 +22,8 @@ + + + + From 6218c5b9be3af1d0a3535201d6b2108cfb916f56 Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Wed, 1 Feb 2023 22:30:00 -0800 Subject: [PATCH 4/6] Add FindByName --- .../Customers/CreateCustomerCommandHandler.cs | 5 +++-- .../Customers/UpdateCustomerCommandHandler.cs | 8 ++++---- src/Wax.Core/Repositories/EfCoreCustomerRepository.cs | 7 ++----- src/Wax.Core/Repositories/ICustomerRepository.cs | 2 +- tests/Wax.UnitTests/Customers/CreateCustomerTests.cs | 6 +++--- tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs | 7 +++---- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs index 9aff87e..4b8fe04 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs @@ -22,8 +22,9 @@ public CreateCustomerCommandHandler(IMapper mapper, IUnitOfWork unitOfWork) public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - if (!await _unitOfWork.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) - .ConfigureAwait(false)) + var existing = await _unitOfWork.Customers.FindByNameAsync(context.Message.Name, cancellationToken); + + if (existing != null) { throw new CustomerNameAlreadyExistsException(); } diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs index 89573ac..ac5cac8 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs @@ -20,13 +20,13 @@ public UpdateCustomerCommandHandler(IMapper mapper, IUnitOfWork unitOfWork) public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken) - .ConfigureAwait(false); + var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); if (customer.Name != context.Message.Name) { - if (!await _unitOfWork.Customers.CheckIsUniqueNameAsync(context.Message.Name, cancellationToken) - .ConfigureAwait(false)) + var existing = await _unitOfWork.Customers.FindByNameAsync(context.Message.Name, cancellationToken); + + if (existing != null) { throw new CustomerNameAlreadyExistsException(); } diff --git a/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs b/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs index e3f5e15..f6b7e75 100644 --- a/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs +++ b/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs @@ -7,15 +7,12 @@ namespace Wax.Core.Repositories; public class EfCoreCustomerRepository : EfCoreBasicRepository, ICustomerRepository, IScopedDependency { - private readonly DbSet _customers; - public EfCoreCustomerRepository(ApplicationDbContext dbContext) : base(dbContext) { - _customers = dbContext.Set(); } - public async Task CheckIsUniqueNameAsync(string name, CancellationToken cancellationToken = default) + public Task FindByNameAsync(string name, CancellationToken cancellationToken = default) { - return !await _customers.AnyAsync(c => c.Name == name, cancellationToken).ConfigureAwait(false); + return Query(c => c.Name == name).FirstOrDefaultAsync(cancellationToken); } } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/ICustomerRepository.cs b/src/Wax.Core/Repositories/ICustomerRepository.cs index 9298c08..1344642 100644 --- a/src/Wax.Core/Repositories/ICustomerRepository.cs +++ b/src/Wax.Core/Repositories/ICustomerRepository.cs @@ -4,5 +4,5 @@ namespace Wax.Core.Repositories; public interface ICustomerRepository : IBasicRepository { - Task CheckIsUniqueNameAsync(string name, CancellationToken cancellationToken = default); + Task FindByNameAsync(string name, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs index 16d2b9f..15200d8 100644 --- a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Mediator.Net.Context; using NSubstitute; +using NSubstitute.ReturnsExtensions; using Shouldly; using Wax.Core.Domain.Customers; using Wax.Core.Domain.Customers.Exceptions; @@ -28,8 +29,7 @@ public async Task ShouldNotCreateCustomerWhenNameAlreadyExists() Name = "microsoft" }; - - Customers.CheckIsUniqueNameAsync(command.Name).Returns(false); + Customers.FindByNameAsync(command.Name).Returns(new Customer { Name = "google" }); await Should.ThrowAsync(async () => await _handler.Handle(new ReceiveContext(command), CancellationToken.None)); @@ -44,7 +44,7 @@ public async Task ShouldCallInsert() Contact = "+861306888888" }; - Customers.CheckIsUniqueNameAsync(command.Name).Returns(true); + Customers.FindByNameAsync(command.Name).ReturnsNull(); await _handler.Handle(new ReceiveContext(command), CancellationToken.None); diff --git a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs index 52ff41f..948798d 100644 --- a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs @@ -1,14 +1,13 @@ using System; using System.Threading; using System.Threading.Tasks; -using AutoMapper; using Mediator.Net.Context; using NSubstitute; +using NSubstitute.ReturnsExtensions; using Shouldly; using Wax.Core.Domain.Customers; using Wax.Core.Domain.Customers.Exceptions; using Wax.Core.Handlers.CommandHandlers.Customers; -using Wax.Core.Profiles; using Wax.Messages.Commands.Customers; using Xunit; @@ -35,7 +34,7 @@ public async Task ShouldNotUpdateCustomerWhenNameAlreadyExists() Customers.GetByIdAsync(command.CustomerId) .Returns(new Customer { Id = command.CustomerId, Name = "google" }); - Customers.CheckIsUniqueNameAsync(command.Name).Returns(false); + Customers.FindByNameAsync(command.Name).Returns(new Customer { Name = "meta" }); await Should.ThrowAsync(async () => await _handler.Handle(new ReceiveContext(command), CancellationToken.None)); @@ -54,7 +53,7 @@ public async Task ShouldUpdateCustomer() }; Customers.GetByIdAsync(command.CustomerId).Returns(customer); - Customers.CheckIsUniqueNameAsync(command.Name).Returns(true); + Customers.FindByNameAsync(command.Name).ReturnsNull(); await _handler.Handle(new ReceiveContext(command), CancellationToken.None); From 73ca9b8d4dae60234930fcbef700eb388c702bfe Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Thu, 2 Feb 2023 03:40:22 -0800 Subject: [PATCH 5/6] Use IComponentContext --- src/Wax.Core/Repositories/UnitOfWork.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Wax.Core/Repositories/UnitOfWork.cs b/src/Wax.Core/Repositories/UnitOfWork.cs index 3714e08..aeeed19 100644 --- a/src/Wax.Core/Repositories/UnitOfWork.cs +++ b/src/Wax.Core/Repositories/UnitOfWork.cs @@ -6,15 +6,15 @@ namespace Wax.Core.Repositories; public class UnitOfWork : IUnitOfWork { private readonly ApplicationDbContext _context; - private readonly ILifetimeScope _lifetimeScope; + private readonly IComponentContext _componentContext; - public UnitOfWork(ApplicationDbContext context, ILifetimeScope lifetimeScope) + public UnitOfWork(ApplicationDbContext context, IComponentContext componentContext) { _context = context; - _lifetimeScope = lifetimeScope; + _componentContext = componentContext; } - public ICustomerRepository Customers => _lifetimeScope.Resolve(); + public ICustomerRepository Customers => _componentContext.Resolve(); public Task SaveChangesAsync(CancellationToken cancellationToken = default) { From c521599974a3c479af9bea6fbf2fddb25feeb22d Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Thu, 2 Feb 2023 03:57:23 -0800 Subject: [PATCH 6/6] Implementing dispose --- src/Wax.Core/Repositories/IUnitOfWork.cs | 2 +- src/Wax.Core/Repositories/UnitOfWork.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Wax.Core/Repositories/IUnitOfWork.cs b/src/Wax.Core/Repositories/IUnitOfWork.cs index c466450..16ad33c 100644 --- a/src/Wax.Core/Repositories/IUnitOfWork.cs +++ b/src/Wax.Core/Repositories/IUnitOfWork.cs @@ -1,6 +1,6 @@ namespace Wax.Core.Repositories; -public interface IUnitOfWork +public interface IUnitOfWork : IDisposable { public ICustomerRepository Customers { get; } Task SaveChangesAsync(CancellationToken cancellationToken = default); diff --git a/src/Wax.Core/Repositories/UnitOfWork.cs b/src/Wax.Core/Repositories/UnitOfWork.cs index aeeed19..bc3a91f 100644 --- a/src/Wax.Core/Repositories/UnitOfWork.cs +++ b/src/Wax.Core/Repositories/UnitOfWork.cs @@ -20,4 +20,9 @@ public Task SaveChangesAsync(CancellationToken cancellationToken = default) { return _context.SaveChangesAsync(cancellationToken); } + + public void Dispose() + { + _context?.Dispose(); + } } \ No newline at end of file