Товар

This commit is contained in:
2025-03-09 17:46:36 +04:00
parent 5c978beafa
commit efef7afd91
12 changed files with 970 additions and 4 deletions

View File

@@ -0,0 +1,21 @@
using CatHasPawsContratcs.AdapterContracts.OperationResponses;
using CatHasPawsContratcs.BindingModels;
namespace CatHasPawsContratcs.AdapterContracts;
public interface IProductAdapter
{
ProductOperationResponse GetList(bool includeDeleted);
ProductOperationResponse GetManufacturerList(string id, bool includeDeleted);
ProductOperationResponse GetHistory(string id);
ProductOperationResponse GetElement(string data);
ProductOperationResponse RegisterProduct(ProductBindingModel productModel);
ProductOperationResponse ChangeProductInfo(ProductBindingModel productModel);
ProductOperationResponse RemoveProduct(string id);
}

View File

@@ -0,0 +1,21 @@
using CatHasPawsContratcs.Infrastructure;
using CatHasPawsContratcs.ViewModels;
namespace CatHasPawsContratcs.AdapterContracts.OperationResponses;
public class ProductOperationResponse : OperationResponse
{
public static ProductOperationResponse OK(List<ProductViewModel> data) => OK<ProductOperationResponse, List<ProductViewModel>>(data);
public static ProductOperationResponse OK(List<ProductHistoryViewModel> data) => OK<ProductOperationResponse, List<ProductHistoryViewModel>>(data);
public static ProductOperationResponse OK(ProductViewModel data) => OK<ProductOperationResponse, ProductViewModel>(data);
public static ProductOperationResponse NoContent() => NoContent<ProductOperationResponse>();
public static ProductOperationResponse NotFound(string message) => NotFound<ProductOperationResponse>(message);
public static ProductOperationResponse BadRequest(string message) => BadRequest<ProductOperationResponse>(message);
public static ProductOperationResponse InternalServerError(string message) => InternalServerError<ProductOperationResponse>(message);
}

View File

@@ -0,0 +1,14 @@
namespace CatHasPawsContratcs.BindingModels;
public class ProductBindingModel
{
public string? Id { get; set; }
public string? ProductName { get; set; }
public string? ProductType { get; set; }
public string? ManufacturerId { get; set; }
public double Price { get; set; }
}

View File

@@ -7,6 +7,8 @@ namespace CatHasPawsContratcs.DataModels;
public class ProductDataModel(string id, string productName, ProductType productType, string manufacturerId, double price, bool isDeleted) : IValidation
{
private readonly ManufacturerDataModel? _manufacturer;
public string Id { get; private set; } = id;
public string ProductName { get; private set; } = productName;
@@ -19,6 +21,15 @@ public class ProductDataModel(string id, string productName, ProductType product
public bool IsDeleted { get; private set; } = isDeleted;
public string ManufacturerName => _manufacturer?.ManufacturerName ?? string.Empty;
public ProductDataModel(string id, string productName, ProductType productType, string manufacturerId, double price, bool isDeleted, ManufacturerDataModel manufacturer) : this(id, productName, productType, manufacturerId, price, isDeleted)
{
_manufacturer = manufacturer;
}
public ProductDataModel(string id, string productName, ProductType productType, string manufacturerId, double price) : this(id, productName, productType, manufacturerId, price, false) { }
public void Validate()
{
if (Id.IsEmpty())

View File

@@ -6,12 +6,22 @@ namespace CatHasPawsContratcs.DataModels;
public class ProductHistoryDataModel(string productId, double oldPrice) : IValidation
{
private readonly ProductDataModel? _product;
public string ProductId { get; private set; } = productId;
public double OldPrice { get; private set; } = oldPrice;
public DateTime ChangeDate { get; private set; } = DateTime.UtcNow;
public string ProductName => _product?.ProductName ?? string.Empty;
public ProductHistoryDataModel(string productId, double oldPrice, DateTime changeDate, ProductDataModel product) : this(productId, oldPrice)
{
ChangeDate = changeDate;
_product = product;
}
public void Validate()
{
if (ProductId.IsEmpty())

View File

@@ -0,0 +1,10 @@
namespace CatHasPawsContratcs.ViewModels;
public class ProductHistoryViewModel
{
public required string ProductName { get; set; }
public double OldPrice { get; set; }
public DateTime ChangeDate { get; set; }
}

View File

@@ -0,0 +1,18 @@
namespace CatHasPawsContratcs.ViewModels;
public class ProductViewModel
{
public required string Id { get; set; }
public required string ProductName { get; set; }
public required string ManufacturerId { get; set; }
public required string ManufacturerName { get; set; }
public required string ProductType { get; set; }
public double Price { get; set; }
public bool IsDeleted { get; set; }
}

View File

@@ -18,6 +18,7 @@ internal class ProductStorageContract : IProductStorageContract
_dbContext = dbContext;
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Manufacturer, ManufacturerDataModel>();
cfg.CreateMap<Product, ProductDataModel>();
cfg.CreateMap<ProductDataModel, Product>()
.ForMember(x => x.IsDeleted, x => x.MapFrom(src => false));
@@ -30,7 +31,7 @@ internal class ProductStorageContract : IProductStorageContract
{
try
{
var query = _dbContext.Products.AsQueryable();
var query = _dbContext.Products.Include(x => x.Manufacturer).AsQueryable();
if (onlyActive)
{
query = query.Where(x => !x.IsDeleted);
@@ -52,7 +53,7 @@ internal class ProductStorageContract : IProductStorageContract
{
try
{
return [.. _dbContext.ProductHistories.Where(x => x.ProductId == productId).OrderByDescending(x => x.ChangeDate).Select(x => _mapper.Map<ProductHistoryDataModel>(x))];
return [.. _dbContext.ProductHistories.Include(x => x.Product).Where(x => x.ProductId == productId).OrderByDescending(x => x.ChangeDate).Select(x => _mapper.Map<ProductHistoryDataModel>(x))];
}
catch (Exception ex)
{
@@ -78,7 +79,7 @@ internal class ProductStorageContract : IProductStorageContract
{
try
{
return _mapper.Map<ProductDataModel>(_dbContext.Products.FirstOrDefault(x => x.ProductName == name && !x.IsDeleted));
return _mapper.Map<ProductDataModel>(_dbContext.Products.Include(x => x.Manufacturer).FirstOrDefault(x => x.ProductName == name && !x.IsDeleted));
}
catch (Exception ex)
{
@@ -171,5 +172,5 @@ internal class ProductStorageContract : IProductStorageContract
}
}
private Product? GetProductById(string id) => _dbContext.Products.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
private Product? GetProductById(string id) => _dbContext.Products.Include(x => x.Manufacturer).FirstOrDefault(x => x.Id == id && !x.IsDeleted);
}

View File

@@ -0,0 +1,536 @@
using CatHasPawsContratcs.BindingModels;
using CatHasPawsContratcs.Enums;
using CatHasPawsContratcs.ViewModels;
using CatHasPawsDatabase;
using CatHasPawsDatabase.Models;
using CatHasPawsTests.Infrastructure;
using System.Net;
namespace CatHasPawsTests.WebApiControllersTests;
[TestFixture]
internal class ProductControllerTests : BaseWebApiControllerTest
{
private string _manufacturerId;
[SetUp]
public void SetUp()
{
_manufacturerId = CatHasPawsDbContext.InsertManufacturerToDatabaseAndReturn().Id;
}
[TearDown]
public void TearDown()
{
CatHasPawsDbContext.RemoveProductsFromDatabase();
CatHasPawsDbContext.RemoveManufacturersFromDatabase();
}
[Test]
public async Task GetList_WhenHaveRecords_ShouldSuccess_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 1");
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 2");
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 3");
//Act
var response = await HttpClient.GetAsync("/api/products/getrecords?includeDeleted=false");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.Multiple(() =>
{
Assert.That(data, Is.Not.Null);
Assert.That(data, Has.Count.EqualTo(3));
});
AssertElement(data.First(x => x.Id == product.Id), product);
}
[Test]
public async Task GetList_WhenNoRecords_ShouldSuccess_Test()
{
//Act
var response = await HttpClient.GetAsync("/api/products/getrecords?includeDeleted=false");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.Multiple(() =>
{
Assert.That(data, Is.Not.Null);
Assert.That(data, Has.Count.EqualTo(0));
});
}
[Test]
public async Task GetList_OnlyActual_ShouldSuccess_Test()
{
//Arrange
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 1", isDeleted: true);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 2", isDeleted: false);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 3", isDeleted: false);
//Act
var response = await HttpClient.GetAsync("/api/products/getrecords?includeDeleted=false");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.That(data, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(data, Has.Count.EqualTo(2));
Assert.That(data.All(x => !x.IsDeleted));
});
}
[Test]
public async Task GetList_IncludeNoActual_ShouldSuccess_Test()
{
//Arrange
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 1", isDeleted: true);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 2", isDeleted: true);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 3", isDeleted: false);
//Act
var response = await HttpClient.GetAsync("/api/products/getrecords?includeDeleted=true");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.That(data, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(data, Has.Count.EqualTo(3));
Assert.That(data.Any(x => x.IsDeleted));
Assert.That(data.Any(x => !x.IsDeleted));
});
}
[Test]
public async Task GetList_ByManufacturer_ShouldSuccess_Test()
{
//Arrange
var manufacruer = CatHasPawsDbContext.InsertManufacturerToDatabaseAndReturn(manufacturerName: "name 2");
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 1", isDeleted: true);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 2", isDeleted: false);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacruer.Id, productName: "name 3", isDeleted: false);
//Act
var response = await HttpClient.GetAsync($"/api/products/getmanufacturerrecords?id={_manufacturerId}&includeDeleted=true");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.That(data, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(data, Has.Count.EqualTo(2));
Assert.That(data.All(x => x.ManufacturerId == _manufacturerId));
});
AssertElement(data.First(x => x.Id == product.Id), product);
}
[Test]
public async Task GetList_ByManufacturer_WhenOnlyActual_ShouldSuccess_Test()
{
//Arrange
var manufacruer = CatHasPawsDbContext.InsertManufacturerToDatabaseAndReturn(manufacturerName: "name 2");
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 1", isDeleted: true);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: "name 2", isDeleted: false);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(manufacruer.Id, productName: "name 3", isDeleted: false);
//Act
var response = await HttpClient.GetAsync($"/api/products/getmanufacturerrecords?id={_manufacturerId}&includeDeleted=false");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.That(data, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(data, Has.Count.EqualTo(1));
Assert.That(data.All(x => x.ManufacturerId == _manufacturerId && !x.IsDeleted));
});
}
[Test]
public async Task GetList_ByManufacturer_WhenIdIsNotGuid_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.GetAsync($"/api/products/getmanufacturerrecords?id=id&includeDeleted=false");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task GetHistoryByProductId_WhenHaveRecords_ShouldSuccess_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
CatHasPawsDbContext.InsertProductHistoryToDatabaseAndReturn(product.Id, 20, DateTime.UtcNow.AddDays(-1));
CatHasPawsDbContext.InsertProductHistoryToDatabaseAndReturn(product.Id, 30, DateTime.UtcNow.AddMinutes(-10));
var history = CatHasPawsDbContext.InsertProductHistoryToDatabaseAndReturn(product.Id, 40, DateTime.UtcNow.AddDays(1));
//Act
var response = await HttpClient.GetAsync($"/api/products/gethistory?id={product.Id}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductHistoryViewModel>>(response);
Assert.That(data, Is.Not.Null);
Assert.That(data, Has.Count.EqualTo(3));
AssertElement(data[0], history);
}
[Test]
public async Task GetHistoryByProductId_WhenNoRecords_ShouldSuccess_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
CatHasPawsDbContext.InsertProductHistoryToDatabaseAndReturn(product.Id, 20, DateTime.UtcNow.AddDays(-1));
CatHasPawsDbContext.InsertProductHistoryToDatabaseAndReturn(product.Id, 30, DateTime.UtcNow.AddMinutes(-10));
CatHasPawsDbContext.InsertProductHistoryToDatabaseAndReturn(product.Id, 40, DateTime.UtcNow.AddDays(1));
//Act
var response = await HttpClient.GetAsync($"/api/products/gethistory?id={Guid.NewGuid()}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var data = await GetModelFromResponseAsync<List<ProductViewModel>>(response);
Assert.That(data, Is.Not.Null);
Assert.That(data, Has.Count.EqualTo(0));
}
[Test]
public async Task GetHistoryByProductId_WhenProductIdIsNotGuid_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.GetAsync($"/api/products/gethistory?id=id");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task GetElement_ById_WhenHaveRecord_ShouldSuccess_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
//Act
var response = await HttpClient.GetAsync($"/api/products/getrecord/{product.Id}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
AssertElement(await GetModelFromResponseAsync<ProductViewModel>(response), product);
}
[Test]
public async Task GetElement_ById_WhenNoRecord_ShouldNotFound_Test()
{
//Arrange
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
//Act
var response = await HttpClient.GetAsync($"/api/products/getrecord/{Guid.NewGuid()}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
[Test]
public async Task GetElement_ById_WhenRecordWasDeleted_ShouldNotFound_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, isDeleted: true);
//Act
var response = await HttpClient.GetAsync($"/api/products/getrecord/{product.Id}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
[Test]
public async Task GetElement_ByName_WhenHaveRecord_ShouldSuccess_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
//Act
var response = await HttpClient.GetAsync($"/api/products/getrecord/{product.ProductName}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
AssertElement(await GetModelFromResponseAsync<ProductViewModel>(response), product);
}
[Test]
public async Task GetElement_ByName_WhenNoRecord_ShouldNotFound_Test()
{
//Arrange
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
//Act
var response = await HttpClient.GetAsync($"/api/products/getrecord/New%20Name");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
[Test]
public async Task GetElement_ByName_WhenRecordWasDeleted_ShouldNotFound_Test()
{
//Arrange
var product = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, isDeleted: true);
//Act
var response = await HttpClient.GetAsync($"/api/products/getrecord/{product.ProductName}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
[Test]
public async Task Post_ShouldSuccess_Test()
{
//Arrange
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
var productModel = CreateModel(_manufacturerId);
//Act
var response = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
AssertElement(CatHasPawsDbContext.GetProductFromDatabaseById(productModel.Id!), productModel);
}
[Test]
public async Task Post_WhenHaveRecordWithSameId_ShouldBadRequest_Test()
{
//Arrange
var productModel = CreateModel(_manufacturerId);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productModel.Id);
//Act
var response = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Post_WhenHaveRecordWithSameName_ShouldBadRequest_Test()
{
//Arrange
var productModel = CreateModel(_manufacturerId, productName: "unique name");
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: productModel.ProductName!);
//Act
var response = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Post_WhenDataIsIncorrect_ShouldBadRequest_Test()
{
//Arrange
var productModelWithIdIncorrect = new ProductBindingModel { Id = "Id", ManufacturerId = _manufacturerId, ProductName = "name", Price = 100, ProductType = ProductType.Toy.ToString() };
var productModelWithNameIncorrect = new ProductBindingModel { Id = Guid.NewGuid().ToString(), ManufacturerId = _manufacturerId, ProductName = string.Empty, Price = 100, ProductType = ProductType.Toy.ToString() };
var productModelWithProductTypeIncorrect = new ProductBindingModel { Id = Guid.NewGuid().ToString(), ManufacturerId = _manufacturerId, ProductName = "name", Price = 100, ProductType = string.Empty };
var productModelWithPriceIncorrect = new ProductBindingModel { Id = Guid.NewGuid().ToString(), ManufacturerId = _manufacturerId, ProductName = "name", Price = 0, ProductType = ProductType.Toy.ToString() };
//Act
var responseWithIdIncorrect = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModelWithIdIncorrect));
var responseWithNameIncorrect = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModelWithNameIncorrect));
var responseWithProductTypeIncorrect = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModelWithProductTypeIncorrect));
var responseWithPriceIncorrect = await HttpClient.PostAsync($"/api/products/register", MakeContent(productModelWithPriceIncorrect));
//Assert
Assert.Multiple(() =>
{
Assert.That(responseWithIdIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Id is incorrect");
Assert.That(responseWithNameIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Name is incorrect");
Assert.That(responseWithProductTypeIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Name is incorrect");
Assert.That(responseWithPriceIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Name is incorrect");
});
}
[Test]
public async Task Post_WhenSendEmptyData_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.PostAsync($"/api/products/register", MakeContent(string.Empty));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Post_WhenSendWrongFormatData_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.PostAsync($"/api/products/register", MakeContent(new { Data = "test", Position = 10 }));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Put_ShouldSuccess_Test()
{
//Arrange
var productModel = CreateModel(_manufacturerId);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productModel.Id);
//Act
var response = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
CatHasPawsDbContext.ChangeTracker.Clear();
AssertElement(CatHasPawsDbContext.GetProductFromDatabaseById(productModel.Id!), productModel);
}
[Test]
public async Task Put_WhenNoFoundRecord_ShouldBadRequest_Test()
{
//Arrange
var productModel = CreateModel(_manufacturerId);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
//Act
var response = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Put_WhenHaveRecordWithSameName_ShouldBadRequest_Test()
{
//Arrange
var productModel = CreateModel(_manufacturerId, productName: "unique name");
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productModel.Id);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productName: productModel.ProductName!);
//Act
var response = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Put_WhenRecordWasDeleted_ShouldBadRequest_Test()
{
//Arrange
var productModel = CreateModel(_manufacturerId);
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productModel.Id, isDeleted: true);
//Act
var response = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModel));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Put_WhenDataIsIncorrect_ShouldBadRequest_Test()
{
//Arrange
var productModelWithIdIncorrect = new ProductBindingModel { Id = "Id", ManufacturerId = _manufacturerId, ProductName = "name", Price = 100, ProductType = ProductType.Toy.ToString() };
var productModelWithNameIncorrect = new ProductBindingModel { Id = Guid.NewGuid().ToString(), ManufacturerId = _manufacturerId, ProductName = string.Empty, Price = 100, ProductType = ProductType.Toy.ToString() };
var productModelWithProductTypeIncorrect = new ProductBindingModel { Id = Guid.NewGuid().ToString(), ManufacturerId = _manufacturerId, ProductName = "name", Price = 100, ProductType = string.Empty };
var productModelWithPriceIncorrect = new ProductBindingModel { Id = Guid.NewGuid().ToString(), ManufacturerId = _manufacturerId, ProductName = "name", Price = 0, ProductType = ProductType.Toy.ToString() };
//Act
var responseWithIdIncorrect = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModelWithIdIncorrect));
var responseWithNameIncorrect = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModelWithNameIncorrect));
var responseWithProductTypeIncorrect = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModelWithProductTypeIncorrect));
var responseWithPriceIncorrect = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(productModelWithPriceIncorrect));
//Assert
Assert.Multiple(() =>
{
Assert.That(responseWithIdIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Id is incorrect");
Assert.That(responseWithNameIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Name is incorrect");
Assert.That(responseWithProductTypeIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Name is incorrect");
Assert.That(responseWithPriceIncorrect.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), "Name is incorrect");
});
}
[Test]
public async Task Put_WhenSendEmptyData_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(string.Empty));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Put_WhenSendWrongFormatData_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.PutAsync($"/api/products/changeinfo", MakeContent(new { Data = "test", Position = 10 }));
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Delete_ShouldSuccess_Test()
{
//Arrange
var productId = Guid.NewGuid().ToString();
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, productId);
//Act
var response = await HttpClient.DeleteAsync($"/api/products/delete/{productId}");
CatHasPawsDbContext.ChangeTracker.Clear();
//Assert
Assert.Multiple(() =>
{
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
Assert.That(CatHasPawsDbContext.GetProductFromDatabaseById(productId)!.IsDeleted);
});
}
[Test]
public async Task Delete_WhenNoFoundRecord_ShouldBadRequest_Test()
{
//Arrange
CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId);
//Act
var response = await HttpClient.DeleteAsync($"/api/products/delete/{Guid.NewGuid()}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Delete_WhenRecordWasDeleted_ShouldBadRequest_Test()
{
//Arrange
var productId = CatHasPawsDbContext.InsertProductToDatabaseAndReturn(_manufacturerId, isDeleted: true).Id;
//Act
var response = await HttpClient.DeleteAsync($"/api/products/delete/{productId}");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
[Test]
public async Task Delete_WhenSendWrongFormatData_ShouldBadRequest_Test()
{
//Act
var response = await HttpClient.DeleteAsync($"/api/products/delete/id");
//Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
}
private static void AssertElement(ProductViewModel? actual, Product expected)
{
Assert.That(actual, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(actual.Id, Is.EqualTo(expected.Id));
Assert.That(actual.ManufacturerId, Is.EqualTo(expected.ManufacturerId));
Assert.That(actual.ProductName, Is.EqualTo(expected.ProductName));
Assert.That(actual.ProductType, Is.EqualTo(expected.ProductType.ToString()));
Assert.That(actual.Price, Is.EqualTo(expected.Price));
Assert.That(actual.IsDeleted, Is.EqualTo(expected.IsDeleted));
Assert.That(actual.ManufacturerName, Is.EqualTo(expected.Manufacturer!.ManufacturerName));
});
}
private static void AssertElement(ProductHistoryViewModel? actual, ProductHistory expected)
{
Assert.That(actual, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(actual.ProductName, Is.EqualTo(expected.Product!.ProductName));
Assert.That(actual.OldPrice, Is.EqualTo(expected.OldPrice));
Assert.That(actual.ChangeDate.ToString(), Is.EqualTo(expected.ChangeDate.ToString()));
});
}
private static ProductBindingModel CreateModel(string manufacturerId, string? id = null, string productName = "name", ProductType productType = ProductType.Feed, double price = 1)
=> new()
{
Id = id ?? Guid.NewGuid().ToString(),
ManufacturerId = manufacturerId,
ProductName = productName,
ProductType = productType.ToString(),
Price = price
};
private static void AssertElement(Product? actual, ProductBindingModel expected)
{
Assert.That(actual, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(actual.Id, Is.EqualTo(expected.Id));
Assert.That(actual.ManufacturerId, Is.EqualTo(expected.ManufacturerId));
Assert.That(actual.ProductName, Is.EqualTo(expected.ProductName));
Assert.That(actual.ProductType.ToString(), Is.EqualTo(expected.ProductType));
Assert.That(actual.Price, Is.EqualTo(expected.Price));
Assert.That(!actual.IsDeleted);
});
}
}

View File

@@ -0,0 +1,266 @@
using AutoMapper;
using CatHasPawsContratcs.AdapterContracts.OperationResponses;
using CatHasPawsContratcs.AdapterContracts;
using CatHasPawsContratcs.BindingModels;
using CatHasPawsContratcs.BusinessLogicsContracts;
using CatHasPawsContratcs.DataModels;
using CatHasPawsContratcs.Exceptions;
using CatHasPawsContratcs.ViewModels;
namespace CatHasPawsWebApi.Adapters;
public class ProductAdapter : IProductAdapter
{
private readonly IProductBusinessLogicContract _productBusinessLogicContract;
private readonly ILogger _logger;
private readonly Mapper _mapper;
public ProductAdapter(IProductBusinessLogicContract productBusinessLogicContract, ILogger<ProductAdapter> logger)
{
_productBusinessLogicContract = productBusinessLogicContract;
_logger = logger;
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ProductBindingModel, ProductDataModel>();
cfg.CreateMap<ProductDataModel, ProductViewModel>();
cfg.CreateMap<ProductHistoryDataModel, ProductHistoryViewModel>();
});
_mapper = new Mapper(config);
}
public ProductOperationResponse GetList(bool includeDeleted)
{
try
{
return ProductOperationResponse.OK([.. _productBusinessLogicContract.GetAllProducts(!includeDeleted).Select(x => _mapper.Map<ProductViewModel>(x))]);
}
catch (NullListException)
{
_logger.LogError("NullListException");
return ProductOperationResponse.NotFound("The list is not initialized");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
public ProductOperationResponse GetManufacturerList(string id, bool includeDeleted)
{
try
{
return ProductOperationResponse.OK([.. _productBusinessLogicContract.GetAllProductsByManufacturer(id, !includeDeleted).Select(x => _mapper.Map<ProductViewModel>(x))]);
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ProductOperationResponse.BadRequest("Data is empty");
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ProductOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (NullListException)
{
_logger.LogError("NullListException");
return ProductOperationResponse.NotFound("The list is not initialized");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
public ProductOperationResponse GetHistory(string id)
{
try
{
return ProductOperationResponse.OK([.. _productBusinessLogicContract.GetProductHistoryByProduct(id).Select(x => _mapper.Map<ProductHistoryViewModel>(x))]);
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ProductOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (NullListException)
{
_logger.LogError("NullListException");
return ProductOperationResponse.NotFound("The list is not initialized");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
public ProductOperationResponse GetElement(string data)
{
try
{
return ProductOperationResponse.OK(_mapper.Map<ProductViewModel>(_productBusinessLogicContract.GetProductByData(data)));
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ProductOperationResponse.BadRequest("Data is empty");
}
catch (ElementNotFoundException ex)
{
_logger.LogError(ex, "ElementNotFoundException");
return ProductOperationResponse.NotFound($"Not found element by data {data}");
}
catch (ElementDeletedException ex)
{
_logger.LogError(ex, "ElementDeletedException");
return ProductOperationResponse.BadRequest($"Element by data: {data} was deleted");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
public ProductOperationResponse RegisterProduct(ProductBindingModel productModel)
{
try
{
_productBusinessLogicContract.InsertProduct(_mapper.Map<ProductDataModel>(productModel));
return ProductOperationResponse.NoContent();
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ProductOperationResponse.BadRequest("Data is empty");
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ProductOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (ElementExistsException ex)
{
_logger.LogError(ex, "ElementExistsException");
return ProductOperationResponse.BadRequest(ex.Message);
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.BadRequest($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
public ProductOperationResponse ChangeProductInfo(ProductBindingModel productModel)
{
try
{
_productBusinessLogicContract.UpdateProduct(_mapper.Map<ProductDataModel>(productModel));
return ProductOperationResponse.NoContent();
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ProductOperationResponse.BadRequest("Data is empty");
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ProductOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (ElementNotFoundException ex)
{
_logger.LogError(ex, "ElementNotFoundException");
return ProductOperationResponse.BadRequest($"Not found element by Id {productModel.Id}");
}
catch (ElementExistsException ex)
{
_logger.LogError(ex, "ElementExistsException");
return ProductOperationResponse.BadRequest(ex.Message);
}
catch (ElementDeletedException ex)
{
_logger.LogError(ex, "ElementDeletedException");
return ProductOperationResponse.BadRequest($"Element by id: {productModel.Id} was deleted");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.BadRequest($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
public ProductOperationResponse RemoveProduct(string id)
{
try
{
_productBusinessLogicContract.DeleteProduct(id);
return ProductOperationResponse.NoContent();
}
catch (ArgumentNullException ex)
{
_logger.LogError(ex, "ArgumentNullException");
return ProductOperationResponse.BadRequest("Id is empty");
}
catch (ValidationException ex)
{
_logger.LogError(ex, "ValidationException");
return ProductOperationResponse.BadRequest($"Incorrect data transmitted: {ex.Message}");
}
catch (ElementNotFoundException ex)
{
_logger.LogError(ex, "ElementNotFoundException");
return ProductOperationResponse.BadRequest($"Not found element by id: {id}");
}
catch (ElementDeletedException ex)
{
_logger.LogError(ex, "ElementDeletedException");
return ProductOperationResponse.BadRequest($"Element by id: {id} was deleted");
}
catch (StorageException ex)
{
_logger.LogError(ex, "StorageException");
return ProductOperationResponse.BadRequest($"Error while working with data storage: {ex.InnerException!.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception");
return ProductOperationResponse.InternalServerError(ex.Message);
}
}
}

View File

@@ -0,0 +1,57 @@
using CatHasPawsContratcs.AdapterContracts;
using CatHasPawsContratcs.BindingModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace CatHasPawsWebApi.Controllers;
[Authorize]
[Route("api/[controller]/[action]")]
[ApiController]
[Produces("application/json")]
public class ProductsController(IProductAdapter adapter) : ControllerBase
{
private readonly IProductAdapter _adapter = adapter;
[HttpGet]
public IActionResult GetRecords(bool includeDeleted)
{
return _adapter.GetList(includeDeleted).GetResponse(Request, Response);
}
[HttpGet]
public IActionResult GetManufacturerRecords(string id, bool includeDeleted)
{
return _adapter.GetManufacturerList(id, includeDeleted).GetResponse(Request, Response);
}
[HttpGet]
public IActionResult GetHistory(string id)
{
return _adapter.GetHistory(id).GetResponse(Request, Response);
}
[HttpGet("{data}")]
public IActionResult GetRecord(string data)
{
return _adapter.GetElement(data).GetResponse(Request, Response);
}
[HttpPost]
public IActionResult Register([FromBody] ProductBindingModel model)
{
return _adapter.RegisterProduct(model).GetResponse(Request, Response);
}
[HttpPut]
public IActionResult ChangeInfo([FromBody] ProductBindingModel model)
{
return _adapter.ChangeProductInfo(model).GetResponse(Request, Response);
}
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
return _adapter.RemoveProduct(id).GetResponse(Request, Response);
}
}

View File

@@ -70,6 +70,7 @@ builder.Services.AddTransient<IWorkerStorageContract, WorkerStorageContract>();
builder.Services.AddTransient<IBuyerAdapter, BuyerAdapter>();
builder.Services.AddTransient<IManufacturerAdapter, ManufacturerAdapter>();
builder.Services.AddTransient<IPostAdapter, PostAdapter>();
builder.Services.AddTransient<IProductAdapter, ProductAdapter>();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();