littleshop/LittleShop.Tests/Unit/ProductServiceTests.cs
sysadmin 1f7c0af497 Add LittleShop.Client SDK library with complete API wrapper
Features:
- Complete .NET client SDK for LittleShop API
- JWT authentication with automatic token management
- Catalog service for products and categories
- Order service with payment creation
- Retry policies using Polly for resilience
- Error handling middleware
- Dependency injection support
- Comprehensive documentation and examples

SDK Components:
- Authentication service with token refresh
- Strongly-typed models for all API responses
- HTTP handlers for retry and error handling
- Extension methods for easy DI registration
- Example console application demonstrating usage

Test Updates:
- Fixed test compilation errors
- Updated test data builders for new models
- Corrected service constructor dependencies
- Fixed enum value changes (PaymentStatus, OrderStatus)

Documentation:
- Complete project README with features and usage
- Client SDK README with detailed examples
- API endpoint documentation
- Security considerations
- Deployment guidelines

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-20 18:15:35 +01:00

310 lines
9.4 KiB
C#

using FluentAssertions;
using LittleShop.Data;
using LittleShop.DTOs;
using LittleShop.Models;
using LittleShop.Services;
using LittleShop.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Moq;
using Xunit;
namespace LittleShop.Tests.Unit;
public class ProductServiceTests : IDisposable
{
private readonly LittleShopContext _context;
private readonly IProductService _productService;
private readonly Mock<IWebHostEnvironment> _mockEnvironment;
public ProductServiceTests()
{
// Set up in-memory database
var options = new DbContextOptionsBuilder<LittleShopContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new LittleShopContext(options);
// Set up mock environment
_mockEnvironment = new Mock<IWebHostEnvironment>();
_mockEnvironment.Setup(e => e.WebRootPath).Returns("/test/wwwroot");
// Create service
_productService = new ProductService(_context, _mockEnvironment.Object);
}
[Fact]
public async Task GetAllProductsAsync_ReturnsAllProducts()
{
// Arrange
var category = await CreateTestCategory();
var products = new[]
{
CreateTestProduct("Product 1", category.Id, 10.00m),
CreateTestProduct("Product 2", category.Id, 20.00m),
CreateTestProduct("Product 3", category.Id, 30.00m, isActive: false)
};
_context.Products.AddRange(products);
await _context.SaveChangesAsync();
// Act
var result = await _productService.GetAllProductsAsync();
// Assert
result.Should().HaveCount(3);
result.Should().Contain(p => p.Name == "Product 1");
result.Should().Contain(p => p.Name == "Product 2");
result.Should().Contain(p => p.Name == "Product 3");
}
[Fact]
public async Task GetProductByIdAsync_WithValidId_ReturnsProduct()
{
// Arrange
var category = await CreateTestCategory();
var productId = Guid.NewGuid();
var product = new Product
{
Id = productId,
Name = "Test Product",
Description = "Test Description",
Price = 99.99m,
CategoryId = category.Id,
IsActive = true,
Weight = 1.5m,
WeightUnit = ProductWeightUnit.Kilograms
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
// Act
var result = await _productService.GetProductByIdAsync(productId);
// Assert
result.Should().NotBeNull();
result!.Id.Should().Be(productId);
result.Name.Should().Be("Test Product");
result.Price.Should().Be(99.99m);
}
[Fact]
public async Task GetProductsByCategoryAsync_ReturnsOnlyProductsInCategory()
{
// Arrange
var category1 = await CreateTestCategory("Category 1");
var category2 = await CreateTestCategory("Category 2");
var products = new[]
{
CreateTestProduct("Product 1", category1.Id, 10.00m),
CreateTestProduct("Product 2", category1.Id, 20.00m),
CreateTestProduct("Product 3", category2.Id, 30.00m)
};
_context.Products.AddRange(products);
await _context.SaveChangesAsync();
// Act
var result = await _productService.GetProductsByCategoryAsync(category1.Id);
// Assert
result.Should().HaveCount(2);
result.Should().OnlyContain(p => p.CategoryId == category1.Id);
}
[Fact]
public async Task CreateProductAsync_WithValidData_CreatesProduct()
{
// Arrange
var category = await CreateTestCategory();
var createDto = new CreateProductDto
{
Name = "New Product",
Description = "New Description",
Price = 49.99m,
CategoryId = category.Id,
Weight = 2.5m,
WeightUnit = ProductWeightUnit.Kilograms
};
// Act
var result = await _productService.CreateProductAsync(createDto);
// Assert
result.Should().NotBeNull();
result.Name.Should().Be("New Product");
result.Price.Should().Be(49.99m);
result.IsActive.Should().BeTrue();
// Verify in database
var dbProduct = await _context.Products.FindAsync(result.Id);
dbProduct.Should().NotBeNull();
dbProduct!.Name.Should().Be("New Product");
}
[Fact]
public async Task UpdateProductAsync_WithValidData_UpdatesProduct()
{
// Arrange
var category = await CreateTestCategory();
var productId = Guid.NewGuid();
var product = new Product
{
Id = productId,
Name = "Original Name",
Description = "Original Description",
Price = 10.00m,
CategoryId = category.Id,
IsActive = true,
Weight = 1.0m,
WeightUnit = ProductWeightUnit.Kilograms
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
var updateDto = new UpdateProductDto
{
Name = "Updated Name",
Description = "Updated Description",
Price = 20.00m,
CategoryId = category.Id,
Weight = 2.0m,
WeightUnit = ProductWeightUnit.Pounds,
IsActive = false
};
// Act
var result = await _productService.UpdateProductAsync(productId, updateDto);
// Assert
result.Should().BeTrue();
// Verify in database
var dbProduct = await _context.Products.FindAsync(productId);
dbProduct!.Name.Should().Be("Updated Name");
dbProduct.Price.Should().Be(20.00m);
dbProduct.Weight.Should().Be(2.0m);
dbProduct.WeightUnit.Should().Be(ProductWeightUnit.Pounds);
dbProduct.IsActive.Should().BeFalse();
}
[Fact]
public async Task DeleteProductAsync_WithValidId_DeletesProduct()
{
// Arrange
var category = await CreateTestCategory();
var productId = Guid.NewGuid();
var product = CreateTestProduct("To Delete", category.Id, 10.00m);
product.Id = productId;
_context.Products.Add(product);
await _context.SaveChangesAsync();
// Act
var result = await _productService.DeleteProductAsync(productId);
// Assert
result.Should().BeTrue();
// Verify in database
var dbProduct = await _context.Products.FindAsync(productId);
dbProduct.Should().BeNull();
}
[Fact]
public async Task AddProductPhotoAsync_AddsPhotoToProduct()
{
// Arrange
var category = await CreateTestCategory();
var productId = Guid.NewGuid();
var product = CreateTestProduct("Product", category.Id, 10.00m);
product.Id = productId;
_context.Products.Add(product);
await _context.SaveChangesAsync();
var photoDto = new CreateProductPhotoDto
{
ProductId = productId,
PhotoUrl = "/uploads/test-photo.jpg",
AltText = "Test Photo",
DisplayOrder = 1
};
// Act
var result = await _productService.AddProductPhotoAsync(photoDto);
// Assert
result.Should().NotBeNull();
result.FilePath.Should().Contain("test-photo.jpg");
result.AltText.Should().Be("Test Photo");
// Verify in database
var dbProduct = await _context.Products
.Include(p => p.Photos)
.FirstOrDefaultAsync(p => p.Id == productId);
dbProduct!.Photos.Should().HaveCount(1);
}
[Fact]
public async Task GetProductsBySearchAsync_ReturnsMatchingProducts()
{
// Arrange
var category = await CreateTestCategory();
var products = new[]
{
CreateTestProduct("Laptop Computer", category.Id, 999.00m),
CreateTestProduct("Desktop Computer", category.Id, 799.00m),
CreateTestProduct("Mouse Pad", category.Id, 9.99m)
};
_context.Products.AddRange(products);
await _context.SaveChangesAsync();
// Act - Search for "Computer"
var result = await _productService.SearchProductsAsync("Computer");
// Assert
result.Should().HaveCount(2);
result.Should().Contain(p => p.Name.Contains("Computer"));
result.Should().NotContain(p => p.Name == "Mouse Pad");
}
private async Task<Category> CreateTestCategory(string name = "Test Category")
{
var category = new Category
{
Id = Guid.NewGuid(),
Name = name,
Description = "Test Description",
IsActive = true
};
_context.Categories.Add(category);
await _context.SaveChangesAsync();
return category;
}
private Product CreateTestProduct(string name, Guid categoryId, decimal price, bool isActive = true)
{
return new Product
{
Id = Guid.NewGuid(),
Name = name,
Description = $"Description for {name}",
Price = price,
CategoryId = categoryId,
IsActive = isActive,
Weight = 1.0m,
WeightUnit = ProductWeightUnit.Kilograms
};
}
public void Dispose()
{
_context.Dispose();
}
}