littleshop/LittleShop.Tests/Unit/CategoryServiceTests.cs
sysadmin a281bb2896 Implement complete e-commerce functionality with shipping and order management
Features Added:
- Standard e-commerce properties (Price, Weight, shipping fields)
- Order management with Create/Edit views and shipping information
- ShippingRates system for weight-based shipping calculations
- Comprehensive test coverage with JWT authentication tests
- Sample data seeder with 5 orders demonstrating full workflow
- Photo upload functionality for products
- Multi-cryptocurrency payment support (BTC, XMR, USDT, etc.)

Database Changes:
- Added ShippingRates table
- Added shipping fields to Orders (Name, Address, City, PostCode, Country)
- Renamed properties to standard names (BasePrice to Price, ProductWeight to Weight)
- Added UpdatedAt timestamps to models

UI Improvements:
- Added Create/Edit views for Orders
- Added ShippingRates management UI
- Updated navigation menu with Shipping option
- Enhanced Order Details view with shipping information

Sample Data:
- 3 Categories (Electronics, Clothing, Books)
- 5 Products with various prices
- 5 Shipping rates (Royal Mail options)
- 5 Orders in different statuses (Pending to Delivered)
- 3 Crypto payments demonstrating payment flow

Security:
- All API endpoints secured with JWT authentication
- No public endpoints - client apps must authenticate
- Privacy-focused design with minimal data collection

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-20 17:37:24 +01:00

262 lines
7.3 KiB
C#

using FluentAssertions;
using LittleShop.Data;
using LittleShop.DTOs;
using LittleShop.Models;
using LittleShop.Services;
using Microsoft.EntityFrameworkCore;
using Moq;
using AutoMapper;
using Xunit;
using LittleShop.Mapping;
namespace LittleShop.Tests.Unit;
public class CategoryServiceTests : IDisposable
{
private readonly LittleShopContext _context;
private readonly ICategoryService _categoryService;
private readonly IMapper _mapper;
public CategoryServiceTests()
{
// Set up in-memory database
var options = new DbContextOptionsBuilder<LittleShopContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_context = new LittleShopContext(options);
// Set up AutoMapper
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
});
_mapper = mappingConfig.CreateMapper();
// Create service
_categoryService = new CategoryService(_context, _mapper);
}
[Fact]
public async Task GetAllCategoriesAsync_ReturnsAllCategories()
{
// Arrange
var categories = new[]
{
new Category { Id = Guid.NewGuid(), Name = "Category 1", Description = "Desc 1", IsActive = true },
new Category { Id = Guid.NewGuid(), Name = "Category 2", Description = "Desc 2", IsActive = true },
new Category { Id = Guid.NewGuid(), Name = "Category 3", Description = "Desc 3", IsActive = false }
};
_context.Categories.AddRange(categories);
await _context.SaveChangesAsync();
// Act
var result = await _categoryService.GetAllCategoriesAsync();
// Assert
result.Should().HaveCount(3);
result.Should().Contain(c => c.Name == "Category 1");
result.Should().Contain(c => c.Name == "Category 2");
result.Should().Contain(c => c.Name == "Category 3");
}
[Fact]
public async Task GetCategoryByIdAsync_WithValidId_ReturnsCategory()
{
// Arrange
var categoryId = Guid.NewGuid();
var category = new Category
{
Id = categoryId,
Name = "Test Category",
Description = "Test Description",
IsActive = true
};
_context.Categories.Add(category);
await _context.SaveChangesAsync();
// Act
var result = await _categoryService.GetCategoryByIdAsync(categoryId);
// Assert
result.Should().NotBeNull();
result!.Id.Should().Be(categoryId);
result.Name.Should().Be("Test Category");
result.Description.Should().Be("Test Description");
}
[Fact]
public async Task GetCategoryByIdAsync_WithInvalidId_ReturnsNull()
{
// Arrange
var invalidId = Guid.NewGuid();
// Act
var result = await _categoryService.GetCategoryByIdAsync(invalidId);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task CreateCategoryAsync_WithValidData_CreatesCategory()
{
// Arrange
var createDto = new CreateCategoryDto
{
Name = "New Category",
Description = "New Description"
};
// Act
var result = await _categoryService.CreateCategoryAsync(createDto);
// Assert
result.Should().NotBeNull();
result.Name.Should().Be("New Category");
result.Description.Should().Be("New Description");
result.IsActive.Should().BeTrue();
// Verify in database
var dbCategory = await _context.Categories.FindAsync(result.Id);
dbCategory.Should().NotBeNull();
dbCategory!.Name.Should().Be("New Category");
}
[Fact]
public async Task UpdateCategoryAsync_WithValidData_UpdatesCategory()
{
// Arrange
var categoryId = Guid.NewGuid();
var category = new Category
{
Id = categoryId,
Name = "Original Name",
Description = "Original Description",
IsActive = true
};
_context.Categories.Add(category);
await _context.SaveChangesAsync();
var updateDto = new UpdateCategoryDto
{
Name = "Updated Name",
Description = "Updated Description",
IsActive = false
};
// Act
var result = await _categoryService.UpdateCategoryAsync(categoryId, updateDto);
// Assert
result.Should().BeTrue();
// Verify in database
var dbCategory = await _context.Categories.FindAsync(categoryId);
dbCategory!.Name.Should().Be("Updated Name");
dbCategory.Description.Should().Be("Updated Description");
dbCategory.IsActive.Should().BeFalse();
}
[Fact]
public async Task UpdateCategoryAsync_WithInvalidId_ReturnsFalse()
{
// Arrange
var invalidId = Guid.NewGuid();
var updateDto = new UpdateCategoryDto
{
Name = "Updated Name",
Description = "Updated Description",
IsActive = true
};
// Act
var result = await _categoryService.UpdateCategoryAsync(invalidId, updateDto);
// Assert
result.Should().BeFalse();
}
[Fact]
public async Task DeleteCategoryAsync_WithValidId_DeletesCategory()
{
// Arrange
var categoryId = Guid.NewGuid();
var category = new Category
{
Id = categoryId,
Name = "To Delete",
Description = "Will be deleted",
IsActive = true
};
_context.Categories.Add(category);
await _context.SaveChangesAsync();
// Act
var result = await _categoryService.DeleteCategoryAsync(categoryId);
// Assert
result.Should().BeTrue();
// Verify in database
var dbCategory = await _context.Categories.FindAsync(categoryId);
dbCategory.Should().BeNull();
}
[Fact]
public async Task DeleteCategoryAsync_WithInvalidId_ReturnsFalse()
{
// Arrange
var invalidId = Guid.NewGuid();
// Act
var result = await _categoryService.DeleteCategoryAsync(invalidId);
// Assert
result.Should().BeFalse();
}
[Fact]
public async Task DeleteCategoryAsync_WithProductsAttached_ThrowsException()
{
// Arrange
var categoryId = Guid.NewGuid();
var category = new Category
{
Id = categoryId,
Name = "Category with Products",
Description = "Has products",
IsActive = true
};
var product = new Product
{
Id = Guid.NewGuid(),
Name = "Product",
Description = "Product in category",
Price = 10.00m,
CategoryId = categoryId,
IsActive = true,
Weight = 1,
WeightUnit = Enums.ProductWeightUnit.Kilograms
};
_context.Categories.Add(category);
_context.Products.Add(product);
await _context.SaveChangesAsync();
// Act & Assert
await Assert.ThrowsAsync<DbUpdateException>(async () =>
{
await _categoryService.DeleteCategoryAsync(categoryId);
});
}
public void Dispose()
{
_context.Dispose();
}
}