diff --git a/TheCatHasPawsProject/CatHasPawsBusinessLogic/Implementations/ReportContract.cs b/TheCatHasPawsProject/CatHasPawsBusinessLogic/Implementations/ReportContract.cs index a83a5ec..ad20951 100644 --- a/TheCatHasPawsProject/CatHasPawsBusinessLogic/Implementations/ReportContract.cs +++ b/TheCatHasPawsProject/CatHasPawsBusinessLogic/Implementations/ReportContract.cs @@ -1,7 +1,21 @@ using CatHasPawsContratcs.BusinessLogicsContracts; +using CatHasPawsContratcs.DataModels; +using CatHasPawsContratcs.StoragesContracts; +using Microsoft.Extensions.Logging; namespace CatHasPawsBusinessLogic.Implementations; -internal class ReportContract : IReportContract +internal class ReportContract(IProductStorageContract productStorageContract, ILogger logger) : IReportContract { + private readonly IProductStorageContract _productStorageContract = productStorageContract; + + private readonly ILogger _logger = logger; + + public Task> GetDataProductsByManufacturerAsync(CancellationToken ct) + { + _logger.LogInformation("Get data ProductsByManufacturer"); + return GetDataByProductsAsync(ct); + } + + private async Task> GetDataByProductsAsync(CancellationToken ct) => [.. (await _productStorageContract.GetListAsync(ct)).GroupBy(x => x.ManufacturerName).Select(x => new ManufacturerProductDataModel { ManufacturerName = x.Key, Products = [.. x.Select(y => y.ProductName)] })]; } \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/IReportAdapter.cs b/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/IReportAdapter.cs index e89957a..9f60f26 100644 --- a/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/IReportAdapter.cs +++ b/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/IReportAdapter.cs @@ -1,5 +1,9 @@ -namespace CatHasPawsContratcs.AdapterContracts; +using CatHasPawsContratcs.AdapterContracts.OperationResponses; +using CatHasPawsContratcs.DataModels; + +namespace CatHasPawsContratcs.AdapterContracts; public interface IReportAdapter { + Task GetDataProductsByManufacturerAsync(CancellationToken ct); } \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/OperationResponses/ReportOperationResponse.cs b/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/OperationResponses/ReportOperationResponse.cs index 4fafaff..4f5f929 100644 --- a/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/OperationResponses/ReportOperationResponse.cs +++ b/TheCatHasPawsProject/CatHasPawsContratcs/AdapterContracts/OperationResponses/ReportOperationResponse.cs @@ -1,9 +1,11 @@ using CatHasPawsContratcs.Infrastructure; +using CatHasPawsContratcs.ViewModels; namespace CatHasPawsContratcs.AdapterContracts.OperationResponses; public class ReportOperationResponse : OperationResponse { + public static ReportOperationResponse OK(List data) => OK>(data); public static ReportOperationResponse BadRequest(string message) => BadRequest(message); diff --git a/TheCatHasPawsProject/CatHasPawsContratcs/BusinessLogicsContracts/IReportContract.cs b/TheCatHasPawsProject/CatHasPawsContratcs/BusinessLogicsContracts/IReportContract.cs index 60df821..6e35376 100644 --- a/TheCatHasPawsProject/CatHasPawsContratcs/BusinessLogicsContracts/IReportContract.cs +++ b/TheCatHasPawsProject/CatHasPawsContratcs/BusinessLogicsContracts/IReportContract.cs @@ -1,5 +1,8 @@ -namespace CatHasPawsContratcs.BusinessLogicsContracts; +using CatHasPawsContratcs.DataModels; + +namespace CatHasPawsContratcs.BusinessLogicsContracts; public interface IReportContract { + Task> GetDataProductsByManufacturerAsync(CancellationToken ct); } \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsContratcs/DataModels/ManufacturerProductDataModel.cs b/TheCatHasPawsProject/CatHasPawsContratcs/DataModels/ManufacturerProductDataModel.cs new file mode 100644 index 0000000..43662e5 --- /dev/null +++ b/TheCatHasPawsProject/CatHasPawsContratcs/DataModels/ManufacturerProductDataModel.cs @@ -0,0 +1,8 @@ +namespace CatHasPawsContratcs.DataModels; + +public class ManufacturerProductDataModel +{ + public required string ManufacturerName { get; set; } + + public required List Products { get; set; } +} \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsContratcs/StoragesContracts/IProductStorageContract.cs b/TheCatHasPawsProject/CatHasPawsContratcs/StoragesContracts/IProductStorageContract.cs index 2df78a5..92a91b1 100644 --- a/TheCatHasPawsProject/CatHasPawsContratcs/StoragesContracts/IProductStorageContract.cs +++ b/TheCatHasPawsProject/CatHasPawsContratcs/StoragesContracts/IProductStorageContract.cs @@ -6,6 +6,8 @@ public interface IProductStorageContract { List GetList(bool onlyActive = true, string? manufacturerId = null); + Task> GetListAsync(CancellationToken ct); + List GetHistoryByProductId(string productId); ProductDataModel? GetElementById(string id); diff --git a/TheCatHasPawsProject/CatHasPawsContratcs/ViewModels/ManufacturerProductViewModel.cs b/TheCatHasPawsProject/CatHasPawsContratcs/ViewModels/ManufacturerProductViewModel.cs new file mode 100644 index 0000000..f49fcb7 --- /dev/null +++ b/TheCatHasPawsProject/CatHasPawsContratcs/ViewModels/ManufacturerProductViewModel.cs @@ -0,0 +1,8 @@ +namespace CatHasPawsContratcs.ViewModels; + +public class ManufacturerProductViewModel +{ + public required string ManufacturerName { get; set; } + + public required List Products { get; set; } +} \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsDatabase/Implementations/ProductStorageContract.cs b/TheCatHasPawsProject/CatHasPawsDatabase/Implementations/ProductStorageContract.cs index b38e66a..181e122 100644 --- a/TheCatHasPawsProject/CatHasPawsDatabase/Implementations/ProductStorageContract.cs +++ b/TheCatHasPawsProject/CatHasPawsDatabase/Implementations/ProductStorageContract.cs @@ -49,6 +49,19 @@ internal class ProductStorageContract : IProductStorageContract } } + public async Task> GetListAsync(CancellationToken ct) + { + try + { + return [.. await _dbContext.Products.Include(x => x.Manufacturer).Select(x => _mapper.Map(x)).ToListAsync(ct)]; + } + catch (Exception ex) + { + _dbContext.ChangeTracker.Clear(); + throw new StorageException(ex); + } + } + public List GetHistoryByProductId(string productId) { try diff --git a/TheCatHasPawsProject/CatHasPawsTests/BusinessLogicsContractsTests/ReportContractTests.cs b/TheCatHasPawsProject/CatHasPawsTests/BusinessLogicsContractsTests/ReportContractTests.cs new file mode 100644 index 0000000..6c6d208 --- /dev/null +++ b/TheCatHasPawsProject/CatHasPawsTests/BusinessLogicsContractsTests/ReportContractTests.cs @@ -0,0 +1,79 @@ +using CatHasPawsBusinessLogic.Implementations; +using CatHasPawsContratcs.DataModels; +using CatHasPawsContratcs.Enums; +using CatHasPawsContratcs.Exceptions; +using CatHasPawsContratcs.StoragesContracts; +using Microsoft.Extensions.Logging; +using Moq; + +namespace CatHasPawsTests.BusinessLogicsContractsTests; + +[TestFixture] +internal class ReportContractTests +{ + private ReportContract _reportContract; + private Mock _productStorageContract; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + _productStorageContract = new Mock(); + _reportContract = new ReportContract(_productStorageContract.Object, new Mock().Object); + } + + [SetUp] + public void SetUp() + { + _productStorageContract.Reset(); + } + + [Test] + public async Task GetDataProductsByManufacturer_ShouldSuccess_Test() + { + //Arrange + var manufacturer1 = new ManufacturerDataModel(Guid.NewGuid().ToString(), "name1"); + var manufacturer2 = new ManufacturerDataModel(Guid.NewGuid().ToString(), "name2"); + _productStorageContract.Setup(x => x.GetListAsync(It.IsAny())).Returns(Task.FromResult(new List() + { + new(Guid.NewGuid().ToString(), "name 1.1", ProductType.Accessory, manufacturer1.Id, 10, false, manufacturer1), + new(Guid.NewGuid().ToString(), "name 2.1", ProductType.Accessory, manufacturer2.Id, 10, false, manufacturer2), + new(Guid.NewGuid().ToString(), "name 1.2", ProductType.Accessory, manufacturer1.Id, 10, false, manufacturer1), + new(Guid.NewGuid().ToString(), "name 1.3", ProductType.Accessory, manufacturer1.Id, 10, false, manufacturer1), + new(Guid.NewGuid().ToString(), "name 2.2", ProductType.Accessory, manufacturer2.Id, 10, false, manufacturer2) + })); + //Act + var data = await _reportContract.GetDataProductsByManufacturerAsync(CancellationToken.None); + //Assert + Assert.That(data, Is.Not.Null); + Assert.That(data, Has.Count.EqualTo(2)); + Assert.Multiple(() => + { + Assert.That(data.First(x => x.ManufacturerName == manufacturer1.ManufacturerName).Products, Has.Count.EqualTo(3)); + Assert.That(data.First(x => x.ManufacturerName == manufacturer2.ManufacturerName).Products, Has.Count.EqualTo(2)); + }); + _productStorageContract.Verify(x => x.GetListAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task GetDataProductsByManufacturer_WhenNoRecords_ShouldSuccess_Test() + { + //Arrange + _productStorageContract.Setup(x => x.GetListAsync(It.IsAny())).Returns(Task.FromResult(new List())); + //Act + var data = await _reportContract.GetDataProductsByManufacturerAsync(CancellationToken.None); + //Assert + Assert.That(data, Is.Not.Null); + Assert.That(data, Has.Count.EqualTo(0)); + _productStorageContract.Verify(x => x.GetListAsync(It.IsAny()), Times.Once); + } + + [Test] + public void GetDataProductsByManufacturer_WhenStorageThrowError_ShouldFail_Test() + { + //Arrange + _productStorageContract.Setup(x => x.GetListAsync(It.IsAny())).Throws(new StorageException(new InvalidOperationException())); + //Act&Assert + Assert.That(async () => await _reportContract.GetDataProductsByManufacturerAsync(CancellationToken.None), Throws.TypeOf()); + _productStorageContract.Verify(x => x.GetListAsync(It.IsAny()), Times.Once); + } +} \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsTests/StoragesContracts/ProductStorageContractTests.cs b/TheCatHasPawsProject/CatHasPawsTests/StoragesContracts/ProductStorageContractTests.cs index 7561b3d..b9b0b45 100644 --- a/TheCatHasPawsProject/CatHasPawsTests/StoragesContracts/ProductStorageContractTests.cs +++ b/TheCatHasPawsProject/CatHasPawsTests/StoragesContracts/ProductStorageContractTests.cs @@ -47,6 +47,26 @@ internal class ProductStorageContractTests : BaseStorageContractTest Assert.That(list, Is.Empty); } + [Test] + public async Task Try_GetListAsync_WhenHaveRecords_Test() + { + var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturer.Id, productName: "name 1"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturer.Id, productName: "name 2"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturer.Id, productName: "name 3"); + var list = await _productStorageContract.GetListAsync(CancellationToken.None); + Assert.That(list, Is.Not.Null); + Assert.That(list, Has.Count.EqualTo(3)); + AssertElement(list.First(x => x.Id == product.Id), product); + } + + [Test] + public async Task Try_GetListAsync_WhenNoRecords_Test() + { + var list = await _productStorageContract.GetListAsync(CancellationToken.None); + Assert.That(list, Is.Not.Null); + Assert.That(list, Is.Empty); + } + [Test] public void Try_GetList_OnlyActual_Test() { diff --git a/TheCatHasPawsProject/CatHasPawsTests/WebApiControllersTests/ReportControllerTests.cs b/TheCatHasPawsProject/CatHasPawsTests/WebApiControllersTests/ReportControllerTests.cs new file mode 100644 index 0000000..dceb9b6 --- /dev/null +++ b/TheCatHasPawsProject/CatHasPawsTests/WebApiControllersTests/ReportControllerTests.cs @@ -0,0 +1,41 @@ +using CatHasPawsContratcs.ViewModels; +using CatHasPawsTests.Infrastructure; +using System.Net; + +namespace CatHasPawsTests.WebApiControllersTests; + +[TestFixture] +internal class ReportControllerTests : BaseWebApiControllerTest +{ + [TearDown] + public void TearDown() + { + CatHasPawsDbContext.RemoveProductsFromDatabase(); + CatHasPawsDbContext.RemoveManufacturersFromDatabase(); + } + + [Test] + public async Task GetProducts_WhenHaveRecords_ShouldSuccess_Test() + { + //Arrange + var manufacturer1 = CatHasPawsDbContext.InsertManufacturerToDatabaseAndReturn(manufacturerName: "name 1"); + var manufacturer2 = CatHasPawsDbContext.InsertManufacturerToDatabaseAndReturn(manufacturerName: "name 2"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacturer1.Id, productName: "name 1.1"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacturer1.Id, productName: "name 1.2"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacturer1.Id, productName: "name 1.3"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacturer2.Id, productName: "name 2.1"); + CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacturer2.Id, productName: "name 2.2"); + //Act + var response = await HttpClient.GetAsync("/api/report/getproducts"); + //Assert + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + var data = await GetModelFromResponseAsync>(response); + Assert.That(data, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(data, Has.Count.EqualTo(2)); + Assert.That(data.First(x => x.ManufacturerName == manufacturer1.ManufacturerName).Products, Has.Count.EqualTo(3)); + Assert.That(data.First(x => x.ManufacturerName == manufacturer2.ManufacturerName).Products, Has.Count.EqualTo(2)); + }); + } +} \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsWebApi/Adapters/ReportAdapter.cs b/TheCatHasPawsProject/CatHasPawsWebApi/Adapters/ReportAdapter.cs index 3ca07b9..0584d56 100644 --- a/TheCatHasPawsProject/CatHasPawsWebApi/Adapters/ReportAdapter.cs +++ b/TheCatHasPawsProject/CatHasPawsWebApi/Adapters/ReportAdapter.cs @@ -1,7 +1,52 @@ -using CatHasPawsContratcs.AdapterContracts; +using AutoMapper; +using CatHasPawsContratcs.AdapterContracts; +using CatHasPawsContratcs.AdapterContracts.OperationResponses; +using CatHasPawsContratcs.BusinessLogicsContracts; +using CatHasPawsContratcs.DataModels; +using CatHasPawsContratcs.Exceptions; +using CatHasPawsContratcs.ViewModels; namespace CatHasPawsWebApi.Adapters; public class ReportAdapter : IReportAdapter { + private IReportContract _reportContract; + + private readonly ILogger _logger; + + private readonly Mapper _mapper; + + public ReportAdapter(IReportContract reportContract, ILogger logger) + { + _reportContract = reportContract; + _logger = logger; + var config = new MapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + _mapper = new Mapper(config); + } + + public async Task GetDataProductsByManufacturerAsync(CancellationToken ct) + { + try + { + return ReportOperationResponse.OK([.. (await _reportContract.GetDataProductsByManufacturerAsync(ct)).Select(x => _mapper.Map(x))]); + } + catch (InvalidOperationException ex) + { + _logger.LogError(ex, "InvalidOperationException"); + return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}"); + } + catch (StorageException ex) + { + _logger.LogError(ex, "StorageException"); + return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception"); + return ReportOperationResponse.InternalServerError(ex.Message); + } + } } \ No newline at end of file diff --git a/TheCatHasPawsProject/CatHasPawsWebApi/Controllers/ReportController.cs b/TheCatHasPawsProject/CatHasPawsWebApi/Controllers/ReportController.cs index e3751c4..85bb515 100644 --- a/TheCatHasPawsProject/CatHasPawsWebApi/Controllers/ReportController.cs +++ b/TheCatHasPawsProject/CatHasPawsWebApi/Controllers/ReportController.cs @@ -1,11 +1,20 @@ using CatHasPawsContratcs.AdapterContracts; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace CatHasPawsWebApi.Controllers; -[Route("api/[controller]")] +[Authorize] +[Route("api/[controller]/[action]")] [ApiController] -public class ReportController(IReportAdapter reportAdapter) : ControllerBase +public class ReportController(IReportAdapter adapter) : ControllerBase { - private IReportAdapter _reportAdapter = reportAdapter; + private readonly IReportAdapter _adapter = adapter; + + [HttpGet] + [Consumes("application/json")] + public async Task GetProducts(CancellationToken ct) + { + return (await _adapter.GetDataProductsByManufacturerAsync(ct)).GetResponse(Request, Response); + } } \ No newline at end of file