Add initial implementation of product management API

This commit is contained in:
2026-02-21 23:51:01 +01:00
commit 8e1b023303
31 changed files with 969 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EndavaTask\EndavaTask.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
using EndavaTask.Contracts;
using EndavaTask.Dtos;
using EndavaTask.Mappings;
using EndavaTask.Models;
using Xunit;
namespace EndavaTask.Tests;
public class EntityDtoMappingsTests
{
[Fact]
public void Product_ToDto_And_ToEntity_Roundtrip_Works()
{
var entity = new Product
{
Id = Guid.NewGuid(),
Name = "Keyboard",
Price = 90m,
CategoryId = Guid.NewGuid()
};
var dto = entity.ToDto();
var mappedBack = dto.ToEntity();
Assert.Equal(entity.Id, dto.Id);
Assert.Equal(entity.Name, dto.Name);
Assert.Equal(entity.Price, dto.Price);
Assert.Equal(entity.CategoryId, dto.CategoryId);
Assert.Equal(entity.Id, mappedBack.Id);
Assert.Equal(entity.Name, mappedBack.Name);
Assert.Equal(entity.Price, mappedBack.Price);
Assert.Equal(entity.CategoryId, mappedBack.CategoryId);
}
[Fact]
public void Category_ToDto_And_ToEntity_Roundtrip_Works()
{
var entity = new Category
{
Id = Guid.NewGuid(),
Name = "Home",
Description = "Home essentials"
};
var dto = entity.ToDto();
var mappedBack = dto.ToEntity();
Assert.Equal(entity.Id, dto.Id);
Assert.Equal(entity.Name, dto.Name);
Assert.Equal(entity.Description, dto.Description);
Assert.Equal(entity.Id, mappedBack.Id);
Assert.Equal(entity.Name, mappedBack.Name);
Assert.Equal(entity.Description, mappedBack.Description);
}
[Fact]
public void PagedProducts_ToDto_MapsMetadataAndItems()
{
var paged = new PagedResult<Product>
{
Items =
[
new Product { Id = Guid.NewGuid(), Name = "Laptop", Price = 1200m, CategoryId = Guid.NewGuid() },
new Product { Id = Guid.NewGuid(), Name = "Monitor", Price = 300m, CategoryId = Guid.NewGuid() }
],
PageNumber = 2,
PageSize = 2,
TotalCount = 5,
TotalPages = 3
};
var dtoPaged = paged.ToDto();
Assert.Equal(2, dtoPaged.Items.Count);
Assert.Equal(2, dtoPaged.PageNumber);
Assert.Equal(2, dtoPaged.PageSize);
Assert.Equal(5, dtoPaged.TotalCount);
Assert.Equal(3, dtoPaged.TotalPages);
var first = Assert.IsType<ProductDto>(dtoPaged.Items.First());
Assert.Equal("Laptop", first.Name);
}
}

View File

@@ -0,0 +1,44 @@
using EndavaTask.Contracts;
using EndavaTask.Data;
using Xunit;
namespace EndavaTask.Tests;
public class InMemoryProductRepositoryTests
{
private readonly InMemoryProductRepository _repository = new();
[Fact]
public void GetFilteredProducts_FiltersByNameCategoryAndPriceRange_WithPagination()
{
var filter = new ProductFilterQuery
{
Name = "mon",
CategoryId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
MinPrice = 200m,
MaxPrice = 350m
};
var result = _repository.GetFilteredProducts(filter, new PaginationQuery { PageNumber = 1, PageSize = 10 });
Assert.Single(result.Items);
var product = Assert.Single(result.Items);
Assert.Equal("Monitor", product.Name);
Assert.Equal(1, result.TotalCount);
Assert.Equal(1, result.TotalPages);
}
[Fact]
public void GetFilteredProducts_ReturnsPagedData_WhenNoFilters()
{
var result = _repository.GetFilteredProducts(new ProductFilterQuery(), new PaginationQuery { PageNumber = 2, PageSize = 2 });
Assert.Equal(2, result.Items.Count);
Assert.Equal(5, result.TotalCount);
Assert.Equal(3, result.TotalPages);
Assert.Equal(2, result.PageNumber);
Assert.Equal(2, result.PageSize);
}
}

View File

@@ -0,0 +1,90 @@
using EndavaTask.Contracts;
using EndavaTask.Data;
using EndavaTask.Dtos;
using EndavaTask.Exceptions;
using EndavaTask.Models;
using EndavaTask.Services;
using Moq;
using Xunit;
namespace EndavaTask.Tests;
public class ProductServiceTests
{
[Fact]
public void GetProducts_WithRepositoryResult_MapsAndReturnsPagedResult()
{
var categoryId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
var repositoryMock = new Mock<IProductRepository>();
repositoryMock
.Setup(r => r.CategoryExists(categoryId))
.Returns(true);
repositoryMock
.Setup(r => r.GetFilteredProducts(It.IsAny<ProductFilterQuery>(), It.IsAny<PaginationQuery>()))
.Returns(new PagedResult<Product>
{
Items =
[
new Product { Id = Guid.NewGuid(), Name = "Laptop", Price = 1200m, CategoryId = categoryId },
new Product { Id = Guid.NewGuid(), Name = "Headphones", Price = 180m, CategoryId = categoryId }
],
PageNumber = 1,
PageSize = 2,
TotalCount = 3,
TotalPages = 2
});
var service = new ProductService(repositoryMock.Object);
var result = service.GetProducts(new ProductFilterQuery { CategoryId = categoryId }, new PaginationQuery { PageNumber = 1, PageSize = 2 });
var value = Assert.IsType<PagedResult<ProductDto>>(result);
Assert.Equal(2, value.Items.Count);
Assert.Equal(3, value.TotalCount);
Assert.Equal(2, value.TotalPages);
}
[Fact]
public void UpdateProduct_WithRepositoryEntity_ReturnsMappedDto()
{
var productId = Guid.Parse("55555555-5555-5555-5555-555555555555");
var categoryId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
var repositoryMock = new Mock<IProductRepository>();
repositoryMock
.Setup(r => r.UpdateProduct(productId, It.IsAny<UpdateProductRequest>()))
.Returns(new Product
{
Id = productId,
Name = "Updated Monitor",
Price = 350m,
CategoryId = categoryId
});
var service = new ProductService(repositoryMock.Object);
var result = service.UpdateProduct(productId, new UpdateProductRequest { Name = "Updated Monitor", Price = 350m });
var value = Assert.IsType<ProductDto>(result);
Assert.Equal("Updated Monitor", value.Name);
Assert.Equal(350m, value.Price);
}
[Fact]
public void GetProducts_WithUnknownCategory_ThrowsValidationException()
{
var repositoryMock = new Mock<IProductRepository>();
repositoryMock
.Setup(r => r.CategoryExists(It.IsAny<Guid>()))
.Returns(false);
var service = new ProductService(repositoryMock.Object);
var exception = Assert.Throws<ApiValidationException>(() =>
service.GetProducts(new ProductFilterQuery { CategoryId = Guid.NewGuid() }, new PaginationQuery()));
Assert.True(exception.Errors.ContainsKey(nameof(ProductFilterQuery.CategoryId)));
}
}