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 ProductVariantServiceTests : IDisposable { private readonly LittleShopContext _context; private readonly IProductService _productService; private readonly Mock _mockEnvironment; public ProductVariantServiceTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _context = new LittleShopContext(options); _mockEnvironment = new Mock(); _mockEnvironment.Setup(e => e.WebRootPath).Returns("/test/wwwroot"); _productService = new ProductService(_context, _mockEnvironment.Object); } [Fact] public async Task CreateProductVariant_ShouldCreateSuccessfully() { // Arrange var product = await CreateTestProduct(); var createDto = new CreateProductVariantDto { ProductId = product.Id, Name = "Red", VariantType = "Color", SortOrder = 1, StockLevel = 50 }; // Act var result = await _productService.CreateProductVariantAsync(createDto); // Assert result.Should().NotBeNull(); result.Name.Should().Be("Red"); result.VariantType.Should().Be("Color"); result.StockLevel.Should().Be(50); result.IsActive.Should().BeTrue(); } [Fact] public async Task CreateProductVariant_MultipleTypes_ShouldCreateSuccessfully() { // Arrange var product = await CreateTestProduct(); // Create color variants var redVariant = await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Red", VariantType = "Color", SortOrder = 1 }); var blueVariant = await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Blue", VariantType = "Color", SortOrder = 2 }); // Act - Get product with variants var result = await _productService.GetProductByIdAsync(product.Id); // Assert result.Should().NotBeNull(); result!.Variants.Should().HaveCount(2); result.Variants.Should().AllSatisfy(v => v.VariantType.Should().Be("Color")); result.Variants.Should().Contain(v => v.Name == "Red"); result.Variants.Should().Contain(v => v.Name == "Blue"); } [Fact] public async Task CreateProductVariant_FlavorVariants_ShouldWork() { // Arrange var product = await CreateTestProduct(); // Create flavor variants await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Vanilla", VariantType = "Flavor", SortOrder = 1 }); await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Chocolate", VariantType = "Flavor", SortOrder = 2 }); await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Strawberry", VariantType = "Flavor", SortOrder = 3 }); // Act var result = await _productService.GetProductByIdAsync(product.Id); // Assert result.Should().NotBeNull(); result!.Variants.Should().HaveCount(3); result.Variants.Should().BeInAscendingOrder(v => v.SortOrder); result.Variants.Select(v => v.Name).Should().BeEquivalentTo( new[] { "Vanilla", "Chocolate", "Strawberry" } ); } [Fact] public async Task UpdateProductVariant_ShouldUpdateSuccessfully() { // Arrange var product = await CreateTestProduct(); var variant = await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Original", VariantType = "Size", StockLevel = 100 }); var updateDto = new UpdateProductVariantDto { Name = "Updated", SortOrder = 10, StockLevel = 50, IsActive = false }; // Act var success = await _productService.UpdateProductVariantAsync(variant.Id, updateDto); // Assert success.Should().BeTrue(); // Get the updated variant to verify changes var updated = await _productService.GetProductVariantByIdAsync(variant.Id); updated.Should().NotBeNull(); updated!.Name.Should().Be("Updated"); updated.SortOrder.Should().Be(10); updated.StockLevel.Should().Be(50); updated.IsActive.Should().BeFalse(); } [Fact] public async Task DeleteProductVariant_ShouldSoftDelete() { // Arrange var product = await CreateTestProduct(); var variant = await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "ToDelete", VariantType = "Color" }); // Act await _productService.DeleteProductVariantAsync(variant.Id); // Assert var deletedVariant = await _context.ProductVariants .IgnoreQueryFilters() .FirstOrDefaultAsync(v => v.Id == variant.Id); deletedVariant.Should().NotBeNull(); deletedVariant!.IsActive.Should().BeFalse(); } [Fact] public async Task ProductWithVariants_StockTracking_ShouldWork() { // Arrange var product = await CreateTestProduct(); // Create variants with different stock levels var variants = new[] { new CreateProductVariantDto { ProductId = product.Id, Name = "Small", VariantType = "Size", StockLevel = 10, SortOrder = 1 }, new CreateProductVariantDto { ProductId = product.Id, Name = "Medium", VariantType = "Size", StockLevel = 25, SortOrder = 2 }, new CreateProductVariantDto { ProductId = product.Id, Name = "Large", VariantType = "Size", StockLevel = 5, SortOrder = 3 } }; foreach (var variantDto in variants) { await _productService.CreateProductVariantAsync(variantDto); } // Act var result = await _productService.GetProductByIdAsync(product.Id); // Assert result.Should().NotBeNull(); result!.Variants.Should().HaveCount(3); result.Variants.Sum(v => v.StockLevel).Should().Be(40); result.Variants.First(v => v.Name == "Medium").StockLevel.Should().Be(25); result.Variants.First(v => v.Name == "Large").StockLevel.Should().Be(5); } [Fact] public async Task GetActiveVariants_ShouldOnlyReturnActive() { // Arrange var product = await CreateTestProduct(); // Create active variant await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Active", VariantType = "Status" }); // Create inactive variant var inactiveVariant = await _productService.CreateProductVariantAsync(new CreateProductVariantDto { ProductId = product.Id, Name = "Inactive", VariantType = "Status" }); await _productService.UpdateProductVariantAsync(inactiveVariant.Id, new UpdateProductVariantDto { IsActive = false }); // Act var result = await _productService.GetProductByIdAsync(product.Id); // Assert result.Should().NotBeNull(); result!.Variants.Should().HaveCount(1); result.Variants.First().Name.Should().Be("Active"); } private async Task CreateTestProduct() { var category = new Category { Id = Guid.NewGuid(), Name = "Test Category", IsActive = true }; _context.Categories.Add(category); var product = new Product { Id = Guid.NewGuid(), Name = "Test Product", Price = 10.00m, CategoryId = category.Id, IsActive = true, Weight = 1.0m, WeightUnit = ProductWeightUnit.Kilograms }; _context.Products.Add(product); await _context.SaveChangesAsync(); return product; } public void Dispose() { _context.Dispose(); } }