diff --git a/CommonTestUtilities/CommonTestUtilities.csproj b/CommonTestUtilities/CommonTestUtilities.csproj index e32584d..32cf5e6 100644 --- a/CommonTestUtilities/CommonTestUtilities.csproj +++ b/CommonTestUtilities/CommonTestUtilities.csproj @@ -18,4 +18,8 @@ + + + + \ No newline at end of file diff --git a/CommonTestUtilities/Repositories/ProductsWriteOnlyRepositoryBuild.cs b/CommonTestUtilities/Repositories/ProductsWriteOnlyRepositoryBuild.cs new file mode 100644 index 0000000..1d46752 --- /dev/null +++ b/CommonTestUtilities/Repositories/ProductsWriteOnlyRepositoryBuild.cs @@ -0,0 +1,13 @@ +using Moq; +using ProductClientHub.Domain.Repositories.Product; + +namespace CommonTestUtilities.Repositories; + +public class ProductsWriteOnlyRepositoryBuild +{ + public static IProductsWriteOnlyRepository Build() + { + var mock = new Mock(); + return mock.Object; + } +} diff --git a/CommonTestUtilities/Repositories/UpdateProductWriteOnlyRepository.cs b/CommonTestUtilities/Repositories/UpdateProductWriteOnlyRepository.cs new file mode 100644 index 0000000..fcf8bf4 --- /dev/null +++ b/CommonTestUtilities/Repositories/UpdateProductWriteOnlyRepository.cs @@ -0,0 +1,14 @@ +using Moq; +using ProductClientHub.Domain.Repositories.Product; + +namespace CommonTestUtilities.Repositories; + +public class UpdateProductWriteOnlyRepository +{ + public static IUpdateProductOnlyRepository Build() + { + var mock = new Mock(); + + return mock.Object; + } +} diff --git a/CommonTestUtilities/Repositories/UpdateProductsWriteOnlyRepositoryBuilder.cs b/CommonTestUtilities/Repositories/UpdateProductsWriteOnlyRepositoryBuilder.cs new file mode 100644 index 0000000..4c28e15 --- /dev/null +++ b/CommonTestUtilities/Repositories/UpdateProductsWriteOnlyRepositoryBuilder.cs @@ -0,0 +1,13 @@ +using Moq; +using ProductClientHub.Domain.Repositories.Product; + +namespace CommonTestUtilities.Repositories; + +public class UpdateProductsWriteOnlyRepositoryBuilder +{ + public static IUpdateProductOnlyRepository Build() + { + var mock = new Mock(); + return mock.Object; + } +} diff --git a/CommonTestUtilities/Requests/RequestProductJsonBuilder.cs b/CommonTestUtilities/Requests/RequestProductJsonBuilder.cs new file mode 100644 index 0000000..78215ac --- /dev/null +++ b/CommonTestUtilities/Requests/RequestProductJsonBuilder.cs @@ -0,0 +1,16 @@ +using ProductClientHub.Communication.Requests; + +namespace CommonTestUtilities.Requests; + +public class RequestProductJsonBuilder +{ + public static RequestProductJson Build(string Name, string Brand, decimal Price) + { + return new RequestProductJson + { + Name = Name, + Brand = Brand, + Price = Price + }; + } +} diff --git a/ProductClientHub.API/Controllers/ProductsController.cs b/ProductClientHub.API/Controllers/ProductsController.cs index e4b82f7..face490 100644 --- a/ProductClientHub.API/Controllers/ProductsController.cs +++ b/ProductClientHub.API/Controllers/ProductsController.cs @@ -34,7 +34,7 @@ public async Task Register([FromBody] RequestProductJson request, [ProducesResponseType(typeof(ResponseErrorMessagesJson), StatusCodes.Status404NotFound)] public async Task Update([FromBody] RequestProductJson request, [FromRoute] Guid productId, - [FromServices] IUploadProductUseCase useCase) + [FromServices] IUpdateProductUseCase useCase) { var response = await useCase.Execute(productId, request); diff --git a/ProductClientHub.Application/DependencyInjectionExtension.cs b/ProductClientHub.Application/DependencyInjectionExtension.cs index c40d808..ae28e30 100644 --- a/ProductClientHub.Application/DependencyInjectionExtension.cs +++ b/ProductClientHub.Application/DependencyInjectionExtension.cs @@ -43,7 +43,7 @@ private static void AddUseCases(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); //login services.AddScoped(); diff --git a/ProductClientHub.Application/UseCases/Products/Update/IUploadProductUseCase.cs b/ProductClientHub.Application/UseCases/Products/Update/IUpdateProductUseCase.cs similarity index 86% rename from ProductClientHub.Application/UseCases/Products/Update/IUploadProductUseCase.cs rename to ProductClientHub.Application/UseCases/Products/Update/IUpdateProductUseCase.cs index 64957df..dee4296 100644 --- a/ProductClientHub.Application/UseCases/Products/Update/IUploadProductUseCase.cs +++ b/ProductClientHub.Application/UseCases/Products/Update/IUpdateProductUseCase.cs @@ -3,7 +3,7 @@ namespace ProductClientHub.Application.UseCases.Products.Update; -public interface IUploadProductUseCase +public interface IUpdateProductUseCase { Task Execute(Guid productId, RequestProductJson request); } diff --git a/ProductClientHub.Application/UseCases/Products/Update/UploadProductUseCase.cs b/ProductClientHub.Application/UseCases/Products/Update/UpdateProductUseCase.cs similarity index 91% rename from ProductClientHub.Application/UseCases/Products/Update/UploadProductUseCase.cs rename to ProductClientHub.Application/UseCases/Products/Update/UpdateProductUseCase.cs index e83d55d..e34f893 100644 --- a/ProductClientHub.Application/UseCases/Products/Update/UploadProductUseCase.cs +++ b/ProductClientHub.Application/UseCases/Products/Update/UpdateProductUseCase.cs @@ -11,16 +11,16 @@ namespace ProductClientHub.Application.UseCases.Products.Update; -public class UploadProductUseCase : IUploadProductUseCase +public class UpdateProductUseCase : IUpdateProductUseCase { private readonly IUnitOfWork _unitOfWork; private readonly IClientReadOnlyRepository _clientReadOnlyRepository; private readonly IProductsReadOnlyRepository _productsReadOnlyRepository; - private readonly IUploadProductOnlyRepository _productWriteOnlyRepository; + private readonly IUpdateProductOnlyRepository _productWriteOnlyRepository; private readonly ILoggedClient _loggedClient; - public UploadProductUseCase(IUnitOfWork unitOfWork, - IUploadProductOnlyRepository productWriteOnlyRepository, + public UpdateProductUseCase(IUnitOfWork unitOfWork, + IUpdateProductOnlyRepository productWriteOnlyRepository, IClientReadOnlyRepository clientReadOnlyRepository, IProductsReadOnlyRepository productsReadOnlyRepository, ILoggedClient loggedClient) diff --git a/ProductClientHub.Domain/Repositories/Product/IUploadProductOnlyRepository.cs b/ProductClientHub.Domain/Repositories/Product/IUploadProductOnlyRepository.cs index ef123f3..c1e085b 100644 --- a/ProductClientHub.Domain/Repositories/Product/IUploadProductOnlyRepository.cs +++ b/ProductClientHub.Domain/Repositories/Product/IUploadProductOnlyRepository.cs @@ -1,6 +1,6 @@ namespace ProductClientHub.Domain.Repositories.Product; -public interface IUploadProductOnlyRepository +public interface IUpdateProductOnlyRepository { Task Update(Guid clientId, Guid productId, Domain.Entities.Product product); } diff --git a/ProductClientHub.Infrastructure/DataAcess/Repositories/Products/ProductsWriteOnlyRepository.cs b/ProductClientHub.Infrastructure/DataAcess/Repositories/Products/ProductsWriteOnlyRepository.cs index ca84360..e74aa3a 100644 --- a/ProductClientHub.Infrastructure/DataAcess/Repositories/Products/ProductsWriteOnlyRepository.cs +++ b/ProductClientHub.Infrastructure/DataAcess/Repositories/Products/ProductsWriteOnlyRepository.cs @@ -5,7 +5,7 @@ namespace ProductClientHub.Infrastructure.DataAcess.Repositories.Products; -public class ProductsWriteOnlyRepository : IProductsWriteOnlyRepository, IDeleteProductWriteOnlyRepository, IUploadProductOnlyRepository +public class ProductsWriteOnlyRepository : IProductsWriteOnlyRepository, IDeleteProductWriteOnlyRepository, IUpdateProductOnlyRepository { private readonly ProductClientHubDbContext _dbContext; @@ -32,9 +32,9 @@ public async Task Delete(Guid clientId, Guid productId) public async Task Update(Guid clientId, Guid productId, Product product) { - var productUpload = await _dbContext.Products.Where(p => p.ClientId == clientId && p.Id == productId).FirstOrDefaultAsync(); + var productUpdate = await _dbContext.Products.Where(p => p.ClientId == clientId && p.Id == productId).FirstOrDefaultAsync(); - if (productUpload is null) return; + if (productUpdate is null) return; var createdOn = await _dbContext.Products .Where(x => x.Id == product.Id) @@ -43,7 +43,7 @@ public async Task Update(Guid clientId, Guid productId, Product product) product.CreatedOn = DateTime.SpecifyKind(createdOn, DateTimeKind.Utc); - _dbContext.Products.Update(productUpload); + _dbContext.Products.Update(productUpdate); _dbContext.Entry(product).Property(x => x.CreatedOn).IsModified = false; } diff --git a/ProductClientHub.Infrastructure/DependencyInjectionExtension.cs b/ProductClientHub.Infrastructure/DependencyInjectionExtension.cs index a38c024..d660b9b 100644 --- a/ProductClientHub.Infrastructure/DependencyInjectionExtension.cs +++ b/ProductClientHub.Infrastructure/DependencyInjectionExtension.cs @@ -64,7 +64,7 @@ private static void AddRepositories(IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); } private static void AddTokens(IServiceCollection services, IConfiguration configuration) diff --git a/UseCase.Test/Product/Register/RegisterProductUseCaseTest.cs b/UseCase.Test/Product/Register/RegisterProductUseCaseTest.cs new file mode 100644 index 0000000..b3be1a8 --- /dev/null +++ b/UseCase.Test/Product/Register/RegisterProductUseCaseTest.cs @@ -0,0 +1,60 @@ +using CommonTestUtilities.Entities; +using CommonTestUtilities.loggedClient; +using CommonTestUtilities.Repositories; +using CommonTestUtilities.Requests; +using ProductClientHub.Application.UseCases.Products.Register; +using ProductClientHub.Domain.Extensions; +using ProductClientHub.Exceptions.ExceptionsBase; +using Shouldly; + +namespace UseCase.Test.Product.Register; + +public class RegisterProductUseCaseTest +{ + [Fact] + public async Task RegisterProductUseCaseTest_Success() + { + (var client, _) = ClientBuilder.Build(); + var product = ProductBuilder.Build(client); + + var productJson =RequestProductJsonBuilder.Build(product.Name, product.Brand, product.Price); + + var useCase = CreateUseCase(product, client, ClientNull: false); + + var result = await useCase.Execute(productJson); + + result.ShouldNotBeNull(); + result.Name.ShouldBe(product.Name); + result.Brand.ShouldBe(product.Brand); + result.Price.ShouldBe(product.Price); + } + + [Fact] + public async Task RegisterProductUseCaseTest_Client_NotFound_Error() + { + (var client, _) = ClientBuilder.Build(); + var product = ProductBuilder.Build(client); + + var productJson = RequestProductJsonBuilder.Build(product.Name, product.Brand, product.Price); + + var useCase = CreateUseCase(product, client, ClientNull: true); + + var result = await Should.ThrowAsync(async () => await useCase.Execute(productJson)); + + result.Message.ShouldBe(ResourceMessagesExceptions.CLIENT_NOCONTENT); + result.GetHttpStatusCode().ShouldBe(System.Net.HttpStatusCode.NotFound); + } + + private static RegisterProductUseCase CreateUseCase(ProductClientHub.Domain.Entities.Product product, ProductClientHub.Domain.Entities.Client client, bool ClientNull) + { + var productsWriteOnly = ProductsWriteOnlyRepositoryBuild.Build(); + var clientReadOnly = new ClientReadOnlyRepositoryBuilder(); + var unitOfWork = UnitOfWorkBuilder.Build(); + var loggedClient = LoggedClientBuilder.Build(client); + + if(ClientNull.IsFalse()) + clientReadOnly.GetById(client); + + return new RegisterProductUseCase(productsWriteOnly, unitOfWork, clientReadOnly.Build(), loggedClient); + } +} diff --git a/UseCase.Test/Product/Update/UpdateProductsUseCaseTest.cs b/UseCase.Test/Product/Update/UpdateProductsUseCaseTest.cs new file mode 100644 index 0000000..567ae62 --- /dev/null +++ b/UseCase.Test/Product/Update/UpdateProductsUseCaseTest.cs @@ -0,0 +1,97 @@ +using CommonTestUtilities.Entities; +using CommonTestUtilities.loggedClient; +using CommonTestUtilities.Repositories; +using CommonTestUtilities.Requests; +using ProductClientHub.Application.UseCases.Products.Update; +using ProductClientHub.Domain.Extensions; +using ProductClientHub.Exceptions.ExceptionsBase; +using Shouldly; + +namespace UseCase.Test.Product.Update; + +public class UpdateProductsUseCaseTest +{ + [Fact] + public async Task UpdateProductUseCaseTest_Success() + { + (var client, _) = ClientBuilder.Build(); + + var product = ProductBuilder.Build(client); + + var productUpdate = RequestProductJsonBuilder.Build( + Name: "New Name", + Brand: "New Brand", + Price: 100); + + var useCase = CreateUseCase(product, client, clientNull: false, productNull: false); + + var result = await useCase.Execute(product.Id, productUpdate); + + result.ShouldNotBeNull(); + result.Brand.ShouldBe(product.Brand); + result.Name.ShouldBe(product.Name); + result.Price.ShouldBe(product.Price); + } + + [Fact] + public async Task UpdateProductUseCaseTest_Client_NotFound() + { + (var client, _) = ClientBuilder.Build(); + + var product = ProductBuilder.Build(client); + + var productUpdate = RequestProductJsonBuilder.Build( + Name: "New Name", + Brand: "New Brand", + Price: 100); + + var useCase = CreateUseCase(product, client, clientNull: true, productNull: false); + + var exception = await Should.ThrowAsync(() => useCase.Execute(product.Id, productUpdate)); + + exception.Message.ShouldBe(ResourceMessagesExceptions.CLIENT_NOCONTENT); + exception.GetHttpStatusCode().ShouldBe(System.Net.HttpStatusCode.NotFound); + } + + [Fact] + public async Task UpdateProductUseCaseTest_Product_NotFound() + { + (var client, _) = ClientBuilder.Build(); + + var product = ProductBuilder.Build(client); + + var productUpdate = RequestProductJsonBuilder.Build( + Name: "New Name", + Brand: "New Brand", + Price: 100); + + var useCase = CreateUseCase(product, client, clientNull: false, productNull: true); + + var exception = await Should.ThrowAsync(() => useCase.Execute(product.Id, productUpdate)); + + exception.Message.ShouldBe(ResourceMessagesExceptions.PRODUCT_NOTFOUND); + exception.GetHttpStatusCode().ShouldBe(System.Net.HttpStatusCode.NotFound); + } + + private static UpdateProductUseCase CreateUseCase(ProductClientHub.Domain.Entities.Product product, ProductClientHub.Domain.Entities.Client client, bool clientNull, bool productNull) + { + var unitOfWork = UnitOfWorkBuilder.Build(); + var clientReadOnlyRepository = new ClientReadOnlyRepositoryBuilder(); + var productUpdateWriteOnlyRepository = UpdateProductsWriteOnlyRepositoryBuilder.Build(); + var productReadOnlyRepository = new ProductsReadOnlyRepositoryBuild(); + var loggedClient = LoggedClientBuilder.Build(client); + + if (clientNull.IsFalse()) + { + clientReadOnlyRepository.GetById(client); + } + + if (productNull.IsFalse()) + { + productReadOnlyRepository.GetById(product); + } + + + return new UpdateProductUseCase(unitOfWork, productUpdateWriteOnlyRepository, clientReadOnlyRepository.Build(), productReadOnlyRepository.Build(), loggedClient); + } +} diff --git a/Validators.Test/Client/RegisterClientValidatorTest.cs b/Validators.Test/Client/RegisterClientValidatorTest.cs new file mode 100644 index 0000000..3226541 --- /dev/null +++ b/Validators.Test/Client/RegisterClientValidatorTest.cs @@ -0,0 +1,68 @@ +using CommonTestUtilities.Requests; +using ProductClientHub.Application.UseCases.Users.SharedValidator; +using ProductClientHub.Communication.Requests; +using Shouldly; + +namespace Validators.Test.Client; + +public class RegisterClientValidatorTest +{ + [Fact] + public void Success() + { + var validate = new RequestClientValidator(); + + var request = RequestClientJsonBuilder.Build("John Carlos", "john@gmail.com", "123456a"); + + var result = validate.Validate(request); + + result.IsValid.ShouldBeTrue(); + } + + [Fact] + public void Error_Email_Empty() + { + var validate = new RequestClientValidator(); + + var request = RequestClientJsonBuilder.Build("John Carlos", "", "123456a"); + + var result = validate.Validate(request); + + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void Error_Email_Invalid() + { + var validate = new RequestClientValidator(); + + var request = RequestClientJsonBuilder.Build("John Carlos", "john", "123456a"); + + var result = validate.Validate(request); + + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void Error_Password_Empty() + { + var validate = new RequestClientValidator(); + + var request = RequestClientJsonBuilder.Build("John Carlos", "john@gmail.com", ""); + + var result = validate.Validate(request); + + result.IsValid.ShouldBeFalse(); + } + + [Fact] + public void Error_Password_Short() + { + var validate = new RequestClientValidator(); + var request = RequestClientJsonBuilder.Build("John Carlos", "john@gmail.com", "123"); + + var result = validate.Validate(request); + + result.IsValid.ShouldBeFalse(); + } +} \ No newline at end of file diff --git a/Validators.Test/Validators.Test.csproj b/Validators.Test/Validators.Test.csproj index 9c57052..ee91285 100644 --- a/Validators.Test/Validators.Test.csproj +++ b/Validators.Test/Validators.Test.csproj @@ -15,6 +15,11 @@ + + + + + diff --git a/WebApi.Test/CustomWebApplicationFactory.cs b/WebApi.Test/CustomWebApplicationFactory.cs index 413ae11..5ed0686 100644 --- a/WebApi.Test/CustomWebApplicationFactory.cs +++ b/WebApi.Test/CustomWebApplicationFactory.cs @@ -5,16 +5,20 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using ProductClientHub.Domain.Repositories.Client; +using ProductClientHub.Domain.Repositories.Product; using ProductClientHub.Domain.Repositories.UnitOfWork; using ProductClientHub.Domain.Security.Tokens; using ProductClientHub.Domain.Services.loggedClient; +using ProductClientHub.Exceptions.ExceptionsBase; using ClientEntity = ProductClientHub.Domain.Entities.Client; +using ProductEntity = ProductClientHub.Domain.Entities.Product; namespace WebApi.Test; public class CustomWebApplicationFactory : WebApplicationFactory { private readonly TestClientStore _clientStore = new(); + private readonly TestProductStore _productStore = new(); public IList ClientsToReturn { @@ -22,6 +26,12 @@ public IList ClientsToReturn set => _clientStore.Clients = value; } + public IList ProductsToReturn + { + get => _productStore.Products; + set => _productStore.Products = value; + } + protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Testing"); @@ -40,6 +50,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.ConfigureServices(services => { services.AddSingleton(_clientStore); + services.AddSingleton(_productStore); services.RemoveAll(); services.RemoveAll(); @@ -47,6 +58,10 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) services.RemoveAll(); services.RemoveAll(); services.RemoveAll(); + services.RemoveAll(); + services.RemoveAll(); + services.RemoveAll(); + services.RemoveAll(); // Remove all hosted services to prevent background services from running during tests services.RemoveAll(typeof(IHostedService)); @@ -57,6 +72,10 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); }); } @@ -140,6 +159,81 @@ public Task Add(ClientEntity client) } } + private sealed class FakeProductsReadOnlyRepository : IProductsReadOnlyRepository + { + private readonly TestProductStore _productStore; + + public FakeProductsReadOnlyRepository(TestProductStore productStore) + { + _productStore = productStore; + } + + public Task> GetAll() + => Task.FromResult>(_productStore.Products); + + public Task GetById(Guid productId) + { + var product = _productStore.Products.FirstOrDefault(p => p.Id == productId); + return Task.FromResult(product); + } + } + + private sealed class FakeProductsWriteOnlyRepository : IProductsWriteOnlyRepository, IDeleteProductWriteOnlyRepository, IUpdateProductOnlyRepository + { + private readonly TestProductStore _productStore; + private readonly TestClientStore _clientStore; + + public FakeProductsWriteOnlyRepository(TestProductStore productStore, TestClientStore clientStore) + { + _productStore = productStore; + _clientStore = clientStore; + } + + public Task Add(ProductEntity product) + { + if (product.Client is null) + { + var client = _clientStore.Clients.FirstOrDefault(c => c.Id == product.ClientId); + if (client is not null) + { + product.Client = client; + client.Products.Add(product); + } + } + + _productStore.Products.Add(product); + return Task.CompletedTask; + } + + public Task Delete(Guid clientId, Guid productId) + { + var product = _productStore.Products.FirstOrDefault(p => p.Id == productId && p.ClientId == clientId); + if (product is null) + { + return Task.FromResult(false); + } + + _productStore.Products.Remove(product); + var client = _clientStore.Clients.FirstOrDefault(c => c.Id == clientId); + client?.Products.Remove(product); + return Task.FromResult(true); + } + + public Task Update(Guid clientId, Guid productId, ProductEntity product) + { + var existingProduct = _productStore.Products.FirstOrDefault(p => p.Id == productId && p.ClientId == clientId); + if (existingProduct is null) + { + return Task.CompletedTask; + } + + existingProduct.Name = product.Name; + existingProduct.Brand = product.Brand; + existingProduct.Price = product.Price; + return Task.CompletedTask; + } + } + private sealed class FakeloggedClient : ILoggedClient { private readonly TestClientStore _clientStore; @@ -151,7 +245,13 @@ public FakeloggedClient(TestClientStore clientStore) public Task User() { - var user = _clientStore.Clients.First(); + var user = _clientStore.Clients.FirstOrDefault(); + + if (user is null) + { + throw new NotFoundException(ResourceMessagesExceptions.CLIENT_NOCONTENT); + } + return Task.FromResult(user); } } @@ -165,4 +265,9 @@ private sealed class TestClientStore { public IList Clients { get; set; } = []; } + + private sealed class TestProductStore + { + public IList Products { get; set; } = []; + } } diff --git a/WebApi.Test/Product/Delete/DeleteProductIntegrationTest.cs b/WebApi.Test/Product/Delete/DeleteProductIntegrationTest.cs new file mode 100644 index 0000000..7204acb --- /dev/null +++ b/WebApi.Test/Product/Delete/DeleteProductIntegrationTest.cs @@ -0,0 +1,90 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Shouldly; +using System.Net; +using System.Net.Http.Headers; +using ClientEntity = ProductClientHub.Domain.Entities.Client; +using ProductEntity = ProductClientHub.Domain.Entities.Product; + +namespace WebApi.Test.Product.Delete; + +public class DeleteProductIntegrationTest : IClassFixture +{ + private readonly CustomWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public DeleteProductIntegrationTest(CustomWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions + { + BaseAddress = new Uri("https://localhost") + }); + } + + [Fact] + public async Task DeleteProduct_ShouldReturnNoContent_WhenProductExists() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var productId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + var token = CommonTestUtilities.Token.JwtTokenGeneratorBuilder.Build().Generate(clientId); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _factory.ProductsToReturn = + [ + new ProductEntity + { + Id = productId, + Name = "Product 1", + Brand = "Brand 1", + Price = 10.5m, + ClientId = clientId, + Client = _factory.ClientsToReturn[0] + } + ]; + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await _httpClient.DeleteAsync($"/api/products/{productId}"); + + response.StatusCode.ShouldBe(HttpStatusCode.NoContent); + } + + [Fact] + public async Task DeleteProduct_ShouldReturnNoContent_WhenProductDoesNotExist() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var productId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + var token = CommonTestUtilities.Token.JwtTokenGeneratorBuilder.Build().Generate(clientId); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _factory.ProductsToReturn = []; + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var response = await _httpClient.DeleteAsync($"/api/products/{productId}"); + + response.StatusCode.ShouldBe(HttpStatusCode.NoContent); + } +} diff --git a/WebApi.Test/Product/GetAll/GetAllProductsIntegrationTest.cs b/WebApi.Test/Product/GetAll/GetAllProductsIntegrationTest.cs new file mode 100644 index 0000000..2b43a77 --- /dev/null +++ b/WebApi.Test/Product/GetAll/GetAllProductsIntegrationTest.cs @@ -0,0 +1,92 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using ProductClientHub.Communication.Responses; +using Shouldly; +using System.Net; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using ClientEntity = ProductClientHub.Domain.Entities.Client; +using ProductEntity = ProductClientHub.Domain.Entities.Product; + +namespace WebApi.Test.Product.GetAll; + +public class GetAllProductsIntegrationTest : IClassFixture +{ + private readonly CustomWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public GetAllProductsIntegrationTest(CustomWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions + { + BaseAddress = new Uri("https://localhost") + }); + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "fake-token"); + } + + [Fact] + public async Task GetAll_ShouldReturnSuccess_WhenThereAreProductsInTheSystem() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _factory.ProductsToReturn = + [ + new ProductEntity + { + Id = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), + Name = "Product 1", + Brand = "Brand 1", + Price = 10.5m, + ClientId = clientId, + Client = _factory.ClientsToReturn[0] + }, + new ProductEntity + { + Id = Guid.Parse("cccccccc-cccc-cccc-cccc-cccccccccccc"), + Name = "Product 2", + Brand = "Brand 2", + Price = 25m, + ClientId = clientId, + Client = _factory.ClientsToReturn[0] + } + ]; + + var response = await _httpClient.GetAsync("/api/products"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + + var body = await response.Content.ReadFromJsonAsync(); + + body.ShouldNotBeNull(); + body!.Products.Count.ShouldBe(2); + body.Products[0].Id.ShouldBe(Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")); + body.Products[0].Name.ShouldBe("Product 1"); + body.Products[0].Client.Id.ShouldBe(clientId); + body.Products[1].Id.ShouldBe(Guid.Parse("cccccccc-cccc-cccc-cccc-cccccccccccc")); + body.Products[1].Name.ShouldBe("Product 2"); + body.Products[1].Client.Id.ShouldBe(clientId); + } + + [Fact] + public async Task GetAll_ShouldReturnNotFound_WhenThereAreNoProducts() + { + _factory.ProductsToReturn = []; + + var response = await _httpClient.GetAsync("/api/products"); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } +} diff --git a/WebApi.Test/Product/GetById/GetProductByIdIntegrationTest.cs b/WebApi.Test/Product/GetById/GetProductByIdIntegrationTest.cs new file mode 100644 index 0000000..421b01b --- /dev/null +++ b/WebApi.Test/Product/GetById/GetProductByIdIntegrationTest.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Shouldly; +using System.Net; +using System.Net.Http.Headers; +using ClientEntity = ProductClientHub.Domain.Entities.Client; +using ProductEntity = ProductClientHub.Domain.Entities.Product; + +namespace WebApi.Test.Product.GetById; + +public class GetProductByIdIntegrationTest : IClassFixture +{ + private readonly CustomWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public GetProductByIdIntegrationTest(CustomWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions + { + BaseAddress = new Uri("https://localhost") + }); + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "fake-token"); + } + + [Fact] + public async Task GetById_ShouldReturnSuccess_WhenProductExists() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var productId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _factory.ProductsToReturn = + [ + new ProductEntity + { + Id = productId, + Name = "Product 1", + Brand = "Brand 1", + Price = 10.5m, + ClientId = clientId, + Client = _factory.ClientsToReturn[0] + } + ]; + + var response = await _httpClient.GetAsync($"/api/products/{productId}"); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task GetById_ShouldReturnNotFound_WhenProductDoesNotExist() + { + var productId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + _factory.ProductsToReturn = []; + + var response = await _httpClient.GetAsync($"/api/products/{productId}"); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } +} diff --git a/WebApi.Test/Product/Register/RegisterProductIntegrationTest.cs b/WebApi.Test/Product/Register/RegisterProductIntegrationTest.cs new file mode 100644 index 0000000..443d6a0 --- /dev/null +++ b/WebApi.Test/Product/Register/RegisterProductIntegrationTest.cs @@ -0,0 +1,93 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Newtonsoft.Json; +using ProductClientHub.Communication.Requests; +using ProductClientHub.Communication.Responses; +using Shouldly; +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using ClientEntity = ProductClientHub.Domain.Entities.Client; + +namespace WebApi.Test.Product.Register; + +public class RegisterProductIntegrationTest : IClassFixture +{ + private readonly CustomWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public RegisterProductIntegrationTest(CustomWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions + { + BaseAddress = new Uri("https://localhost") + }); + } + + [Fact] + public async Task RegisterProduct_ShouldReturnCreated_WhenRequestIsValid() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var token = CommonTestUtilities.Token.JwtTokenGeneratorBuilder.Build().Generate(clientId); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var request = new RequestProductJson + { + Name = "Product 1", + Brand = "Brand 1", + Price = 10.5m + }; + + var response = await _httpClient.PostAsync( + "/api/products", + new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json")); + + response.StatusCode.ShouldBe(HttpStatusCode.Created); + + var body = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + body.ShouldNotBeNull(); + body!.Name.ShouldBe(request.Name); + body.Brand.ShouldBe(request.Brand); + body.Price.ShouldBe(request.Price); + } + + [Fact] + public async Task RegisterProduct_ShouldReturnNotFound_WhenClientDoesNotExist() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var token = CommonTestUtilities.Token.JwtTokenGeneratorBuilder.Build().Generate(clientId); + + _factory.ClientsToReturn = []; + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var request = new RequestProductJson + { + Name = "Product 1", + Brand = "Brand 1", + Price = 10.5m + }; + + var response = await _httpClient.PostAsync( + "/api/products", + new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json")); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + + var body = await response.Content.ReadAsStringAsync(); + body.ShouldNotBeNullOrWhiteSpace(); + } +} diff --git a/WebApi.Test/Product/Update/UpdateProductIntegrationTest.cs b/WebApi.Test/Product/Update/UpdateProductIntegrationTest.cs new file mode 100644 index 0000000..1efea41 --- /dev/null +++ b/WebApi.Test/Product/Update/UpdateProductIntegrationTest.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using Newtonsoft.Json; +using ProductClientHub.Communication.Requests; +using Shouldly; +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using ClientEntity = ProductClientHub.Domain.Entities.Client; +using ProductEntity = ProductClientHub.Domain.Entities.Product; + +namespace WebApi.Test.Product.Update; + +public class UpdateProductIntegrationTest : IClassFixture +{ + private readonly CustomWebApplicationFactory _factory; + private readonly HttpClient _httpClient; + + public UpdateProductIntegrationTest(CustomWebApplicationFactory factory) + { + _factory = factory; + _httpClient = factory.CreateClient(new WebApplicationFactoryClientOptions + { + BaseAddress = new Uri("https://localhost") + }); + } + + [Fact] + public async Task UpdateProduct_ShouldReturnOk_WhenRequestIsValid() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var productId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + var token = CommonTestUtilities.Token.JwtTokenGeneratorBuilder.Build().Generate(clientId); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _factory.ProductsToReturn = + [ + new ProductEntity + { + Id = productId, + Name = "Product 1", + Brand = "Brand 1", + Price = 10.5m, + ClientId = clientId, + Client = _factory.ClientsToReturn[0] + } + ]; + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var request = new RequestProductJson + { + Name = "Product Updated", + Brand = "Brand Updated", + Price = 20m + }; + + var response = await _httpClient.PutAsync( + $"/api/products/{productId}", + new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json")); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task UpdateProduct_ShouldReturnNotFound_WhenProductDoesNotExist() + { + var clientId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); + var productId = Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"); + var token = CommonTestUtilities.Token.JwtTokenGeneratorBuilder.Build().Generate(clientId); + + _factory.ClientsToReturn = + [ + new ClientEntity + { + Id = clientId, + Name = "Client 1", + Email = "client1@email.com", + Password = "password123", + Products = [] + } + ]; + + _factory.ProductsToReturn = []; + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var request = new RequestProductJson + { + Name = "Product Updated", + Brand = "Brand Updated", + Price = 20m + }; + + var response = await _httpClient.PutAsync( + $"/api/products/{productId}", + new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json")); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } +}