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>
293 lines
9.1 KiB
C#
293 lines
9.1 KiB
C#
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,
|
|
PasswordHash = "hashed-password",
|
|
IsActive = true,
|
|
CreatedAt = 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,
|
|
RequiredAmount = 0.0025m,
|
|
WalletAddress = $"bc1q{Guid.NewGuid().ToString().Replace("-", "").Substring(0, 39)}",
|
|
Status = status,
|
|
CreatedAt = DateTime.UtcNow,
|
|
ExpiresAt = DateTime.UtcNow.AddHours(1)
|
|
};
|
|
}
|
|
|
|
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 sortOrder = 1)
|
|
{
|
|
var fileName = $"{Guid.NewGuid()}.jpg";
|
|
return new ProductPhoto
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ProductId = productId,
|
|
FileName = fileName,
|
|
FilePath = $"/uploads/products/{fileName}",
|
|
AltText = "Test product photo",
|
|
SortOrder = sortOrder,
|
|
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;
|
|
}
|
|
}
|
|
} |