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 _mockEnvironment; public ProductServiceTests() { // Set up in-memory database var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _context = new LittleShopContext(options); // Set up mock environment _mockEnvironment = new Mock(); _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 - only active products should be returned result.Should().HaveCount(2); result.Should().Contain(p => p.Name == "Product 1"); result.Should().Contain(p => p.Name == "Product 2"); result.Should().NotContain(p => p.Name == "Product 3"); // Inactive product should not be returned } [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 - soft delete means IsActive = false var dbProduct = await _context.Products.FindAsync(productId); dbProduct.Should().NotBeNull(); dbProduct!.IsActive.Should().BeFalse(); } [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 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(); } }