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>
This commit is contained in:
295
LittleShop.Tests/TestUtilities/TestDataBuilder.cs
Normal file
295
LittleShop.Tests/TestUtilities/TestDataBuilder.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
using LittleShop.DTOs;
|
||||
using LittleShop.Enums;
|
||||
using LittleShop.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LittleShop.Tests.TestUtilities;
|
||||
|
||||
public static class TestDataBuilder
|
||||
{
|
||||
public static Category CreateCategory(string? name = null, bool isActive = true)
|
||||
{
|
||||
return new Category
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name ?? $"Category-{Guid.NewGuid()}",
|
||||
Description = "Test category description",
|
||||
IsActive = isActive,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
public static Product CreateProduct(Guid? categoryId = null, string? name = null, decimal? price = null, bool isActive = true)
|
||||
{
|
||||
return new Product
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name ?? $"Product-{Guid.NewGuid()}",
|
||||
Description = "Test product description",
|
||||
Price = price ?? 99.99m,
|
||||
CategoryId = categoryId ?? Guid.NewGuid(),
|
||||
IsActive = isActive,
|
||||
Weight = 1.5m,
|
||||
WeightUnit = ProductWeightUnit.Kilograms,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
public static Order CreateOrder(string? identityReference = null, OrderStatus status = OrderStatus.PendingPayment)
|
||||
{
|
||||
var reference = identityReference ?? $"identity-{Guid.NewGuid()}";
|
||||
return new Order
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IdentityReference = reference,
|
||||
Status = status,
|
||||
ShippingName = "Test Customer",
|
||||
ShippingAddress = "123 Test Street",
|
||||
ShippingCity = "Test City",
|
||||
ShippingPostCode = "TE5 7CD",
|
||||
ShippingCountry = "United Kingdom",
|
||||
TotalAmount = 199.99m,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
Items = new List<OrderItem>()
|
||||
};
|
||||
}
|
||||
|
||||
public static OrderItem CreateOrderItem(Guid orderId, Guid productId, int quantity = 1, decimal price = 99.99m)
|
||||
{
|
||||
return new OrderItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orderId,
|
||||
ProductId = productId,
|
||||
Quantity = quantity,
|
||||
UnitPrice = price,
|
||||
TotalPrice = price * quantity
|
||||
};
|
||||
}
|
||||
|
||||
public static User CreateUser(string? username = null, string role = "User")
|
||||
{
|
||||
var user = username ?? $"user-{Guid.NewGuid()}";
|
||||
return new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = user,
|
||||
Email = $"{user}@test.com",
|
||||
PasswordHash = "hashed-password",
|
||||
Role = role,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
public static CryptoPayment CreateCryptoPayment(Guid orderId, CryptoCurrency currency = CryptoCurrency.BTC, PaymentStatus status = PaymentStatus.Pending)
|
||||
{
|
||||
return new CryptoPayment
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrderId = orderId,
|
||||
Currency = currency,
|
||||
Amount = 0.0025m,
|
||||
CryptoAddress = $"bc1q{Guid.NewGuid().ToString().Replace("-", "").Substring(0, 39)}",
|
||||
Status = status,
|
||||
ExchangeRate = 40000.00m,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
public static CreateOrderDto CreateOrderDto(List<Guid>? productIds = null)
|
||||
{
|
||||
var items = new List<CreateOrderItemDto>();
|
||||
|
||||
if (productIds != null)
|
||||
{
|
||||
foreach (var productId in productIds)
|
||||
{
|
||||
items.Add(new CreateOrderItemDto
|
||||
{
|
||||
ProductId = productId,
|
||||
Quantity = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Add(new CreateOrderItemDto
|
||||
{
|
||||
ProductId = Guid.NewGuid(),
|
||||
Quantity = 1
|
||||
});
|
||||
}
|
||||
|
||||
return new CreateOrderDto
|
||||
{
|
||||
IdentityReference = $"test-identity-{Guid.NewGuid()}",
|
||||
ShippingName = "Test Customer",
|
||||
ShippingAddress = "123 Test Street",
|
||||
ShippingCity = "Test City",
|
||||
ShippingPostCode = "TE5 7CD",
|
||||
ShippingCountry = "United Kingdom",
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
public static CreateProductDto CreateProductDto(Guid? categoryId = null)
|
||||
{
|
||||
return new CreateProductDto
|
||||
{
|
||||
Name = $"Product-{Guid.NewGuid()}",
|
||||
Description = "Test product description",
|
||||
Price = 99.99m,
|
||||
CategoryId = categoryId ?? Guid.NewGuid(),
|
||||
Weight = 1.5m,
|
||||
WeightUnit = ProductWeightUnit.Kilograms
|
||||
};
|
||||
}
|
||||
|
||||
public static CreateCategoryDto CreateCategoryDto()
|
||||
{
|
||||
return new CreateCategoryDto
|
||||
{
|
||||
Name = $"Category-{Guid.NewGuid()}",
|
||||
Description = "Test category description"
|
||||
};
|
||||
}
|
||||
|
||||
public static ProductPhoto CreateProductPhoto(Guid productId, int displayOrder = 1)
|
||||
{
|
||||
return new ProductPhoto
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ProductId = productId,
|
||||
PhotoUrl = $"/uploads/products/{Guid.NewGuid()}.jpg",
|
||||
AltText = "Test product photo",
|
||||
DisplayOrder = displayOrder,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
// Builder class for complex test scenarios
|
||||
public class OrderBuilder
|
||||
{
|
||||
private Order _order;
|
||||
private List<Product> _products = new();
|
||||
|
||||
public OrderBuilder()
|
||||
{
|
||||
_order = CreateOrder();
|
||||
}
|
||||
|
||||
public OrderBuilder WithStatus(OrderStatus status)
|
||||
{
|
||||
_order.Status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderBuilder WithIdentity(string identityReference)
|
||||
{
|
||||
_order.IdentityReference = identityReference;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderBuilder WithShipping(string name, string address, string city, string postCode, string country)
|
||||
{
|
||||
_order.ShippingName = name;
|
||||
_order.ShippingAddress = address;
|
||||
_order.ShippingCity = city;
|
||||
_order.ShippingPostCode = postCode;
|
||||
_order.ShippingCountry = country;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderBuilder AddItem(Product product, int quantity)
|
||||
{
|
||||
_products.Add(product);
|
||||
var item = CreateOrderItem(_order.Id, product.Id, quantity, product.Price);
|
||||
_order.Items.Add(item);
|
||||
_order.TotalAmount = _order.Items.Sum(i => i.TotalPrice);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderBuilder WithPayment(CryptoCurrency currency, PaymentStatus status)
|
||||
{
|
||||
var payment = CreateCryptoPayment(_order.Id, currency, status);
|
||||
_order.Payments ??= new List<CryptoPayment>();
|
||||
_order.Payments.Add(payment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Order Build()
|
||||
{
|
||||
return _order;
|
||||
}
|
||||
|
||||
public (Order order, List<Product> products) BuildWithProducts()
|
||||
{
|
||||
return (_order, _products);
|
||||
}
|
||||
}
|
||||
|
||||
// Bulk data generation for stress testing
|
||||
public static class BulkDataGenerator
|
||||
{
|
||||
public static List<Category> GenerateCategories(int count)
|
||||
{
|
||||
var categories = new List<Category>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
categories.Add(CreateCategory($"Category {i + 1}"));
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
|
||||
public static List<Product> GenerateProducts(List<Category> categories, int productsPerCategory)
|
||||
{
|
||||
var products = new List<Product>();
|
||||
foreach (var category in categories)
|
||||
{
|
||||
for (int i = 0; i < productsPerCategory; i++)
|
||||
{
|
||||
products.Add(CreateProduct(
|
||||
category.Id,
|
||||
$"{category.Name} - Product {i + 1}",
|
||||
Random.Shared.Next(10, 1000)));
|
||||
}
|
||||
}
|
||||
return products;
|
||||
}
|
||||
|
||||
public static List<Order> GenerateOrders(int count, List<Product> availableProducts)
|
||||
{
|
||||
var orders = new List<Order>();
|
||||
var statuses = Enum.GetValues<OrderStatus>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var builder = new OrderBuilder()
|
||||
.WithIdentity($"customer-{i}")
|
||||
.WithStatus(statuses[Random.Shared.Next(statuses.Length)]);
|
||||
|
||||
// Add random products to order
|
||||
var productCount = Random.Shared.Next(1, Math.Min(5, availableProducts.Count));
|
||||
var selectedProducts = availableProducts
|
||||
.OrderBy(x => Random.Shared.Next())
|
||||
.Take(productCount);
|
||||
|
||||
foreach (var product in selectedProducts)
|
||||
{
|
||||
builder.AddItem(product, Random.Shared.Next(1, 3));
|
||||
}
|
||||
|
||||
orders.Add(builder.Build());
|
||||
}
|
||||
|
||||
return orders;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user