using System.Net.Http.Headers; using System.Net.Http.Json; using CampusErrand.Data; using CampusErrand.Models; using CampusErrand.Models.Dtos; using FsCheck; using FsCheck.Xunit; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace CampusErrand.Tests; /// /// Property 8: 打包费计算 /// 对任意美食街订单,当门店配置为总打包费模式时,额外费用等于固定打包费金额; /// 当门店配置为单份打包费模式时,额外费用等于菜品总份数乘以单份打包费金额。 /// **Feature: login-and-homepage, Property 8: 打包费计算** /// **Validates: Requirements 10.1, 10.2, 10.3** /// public class PackingFeePropertyTests : IDisposable { private const string JwtSecret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!"; private const string JwtIssuer = "CampusErrand"; private const string JwtAudience = "CampusErrandApp"; private readonly List> _factories = []; private WebApplicationFactory CreateFactory(string dbName) { var factory = new WebApplicationFactory() .WithWebHostBuilder(builder => { builder.ConfigureServices(services => { var efDescriptors = services .Where(d => d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true || d.ServiceType == typeof(DbContextOptions) || d.ServiceType == typeof(DbContextOptions) || d.ImplementationType?.FullName?.Contains("EntityFrameworkCore") == true) .ToList(); foreach (var d in efDescriptors) services.Remove(d); services.AddDbContext(options => options.UseInMemoryDatabase(dbName)); }); }); _factories.Add(factory); return factory; } private static string GenerateUserToken(int userId = 1) { var key = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey( System.Text.Encoding.UTF8.GetBytes(JwtSecret)); var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials( key, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256); var claims = new[] { new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, userId.ToString()), new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, "User") }; var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken( issuer: JwtIssuer, audience: JwtAudience, claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials); return new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler().WriteToken(token); } /// /// 在数据库中创建门店和菜品,返回门店和菜品信息 /// private static (Shop shop, Dish dish) SeedShopAndDish( AppDbContext db, PackingFeeType feeType, decimal feeAmount, decimal dishPrice) { var shop = new Shop { Name = "测试门店", Photo = "https://example.com/photo.jpg", Location = "测试位置", PackingFeeType = feeType, PackingFeeAmount = feeAmount, IsEnabled = true }; db.Shops.Add(shop); db.SaveChanges(); var dish = new Dish { ShopId = shop.Id, Name = "测试菜品", Photo = "https://example.com/dish.jpg", Price = dishPrice, IsEnabled = true }; db.Dishes.Add(dish); db.SaveChanges(); return (shop, dish); } /// /// 属性:总打包费模式下,额外费用等于固定打包费金额(不论菜品数量) /// [Property(MaxTest = 20)] public bool 总打包费模式下额外费用等于固定金额(PositiveInt quantitySeed, PositiveInt feeSeed) { // 生成测试数据:数量 1~10,打包费 0.5~5.0 var quantity = (quantitySeed.Get % 10) + 1; var packingFeeAmount = Math.Round(0.5m + (feeSeed.Get % 10) * 0.5m, 2); var dishPrice = 10.0m; var dbName = $"packing_fixed_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); // 准备数据 using var scope = factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var (shop, dish) = SeedShopAndDish(db, PackingFeeType.Fixed, packingFeeAmount, dishPrice); // 创建美食街订单 var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateUserToken()); var request = new CreateOrderRequest { OrderType = "Food", ItemName = "美食街订单", DeliveryLocation = "测试送达地点", Phone = "13800138000", Commission = 3.0m, FoodItems = [ new FoodOrderItemRequest { ShopId = shop.Id, DishId = dish.Id, Quantity = quantity, UnitPrice = dishPrice } ] }; var response = client.PostAsJsonAsync("/api/orders", request).Result; if (!response.IsSuccessStatusCode) return false; var order = response.Content.ReadFromJsonAsync().Result; if (order == null) return false; // 验证:商品总金额 = 菜品总额 + 固定打包费 var expectedGoodsAmount = (dishPrice * quantity) + packingFeeAmount; return order.GoodsAmount == expectedGoodsAmount; } /// /// 属性:单份打包费模式下,额外费用等于菜品总份数乘以单份打包费金额 /// [Property(MaxTest = 20)] public bool 单份打包费模式下额外费用等于份数乘以单价(PositiveInt quantitySeed, PositiveInt feeSeed) { // 生成测试数据:数量 1~10,单份打包费 0.5~5.0 var quantity = (quantitySeed.Get % 10) + 1; var packingFeePerItem = Math.Round(0.5m + (feeSeed.Get % 10) * 0.5m, 2); var dishPrice = 10.0m; var dbName = $"packing_peritem_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); // 准备数据 using var scope = factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var (shop, dish) = SeedShopAndDish(db, PackingFeeType.PerItem, packingFeePerItem, dishPrice); // 创建美食街订单 var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateUserToken()); var request = new CreateOrderRequest { OrderType = "Food", ItemName = "美食街订单", DeliveryLocation = "测试送达地点", Phone = "13800138000", Commission = 3.0m, FoodItems = [ new FoodOrderItemRequest { ShopId = shop.Id, DishId = dish.Id, Quantity = quantity, UnitPrice = dishPrice } ] }; var response = client.PostAsJsonAsync("/api/orders", request).Result; if (!response.IsSuccessStatusCode) return false; var order = response.Content.ReadFromJsonAsync().Result; if (order == null) return false; // 验证:商品总金额 = 菜品总额 + (份数 × 单份打包费) var expectedPackingFee = packingFeePerItem * quantity; var expectedGoodsAmount = (dishPrice * quantity) + expectedPackingFee; return order.GoodsAmount == expectedGoodsAmount; } /// /// 属性:支付总金额 = 商品总金额(含打包费) + 跑腿佣金 /// [Property(MaxTest = 20)] public bool 支付总金额等于商品总金额加佣金(PositiveInt quantitySeed, PositiveInt feeSeed, PositiveInt commSeed) { var quantity = (quantitySeed.Get % 5) + 1; var packingFeeAmount = Math.Round(1.0m + (feeSeed.Get % 5) * 0.5m, 2); var commission = Math.Round(1.0m + (commSeed.Get % 50) * 0.1m, 1); var dishPrice = 15.0m; var dbName = $"packing_total_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); using var scope = factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var (shop, dish) = SeedShopAndDish(db, PackingFeeType.Fixed, packingFeeAmount, dishPrice); var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateUserToken()); var request = new CreateOrderRequest { OrderType = "Food", ItemName = "美食街订单", DeliveryLocation = "测试送达地点", Phone = "13800138000", Commission = commission, FoodItems = [ new FoodOrderItemRequest { ShopId = shop.Id, DishId = dish.Id, Quantity = quantity, UnitPrice = dishPrice } ] }; var response = client.PostAsJsonAsync("/api/orders", request).Result; if (!response.IsSuccessStatusCode) return false; var order = response.Content.ReadFromJsonAsync().Result; if (order == null) return false; // 验证:支付总金额 = 商品总金额 + 佣金 var expectedGoodsAmount = (dishPrice * quantity) + packingFeeAmount; var expectedTotal = expectedGoodsAmount + commission; return order.TotalAmount == expectedTotal && order.GoodsAmount == expectedGoodsAmount; } public void Dispose() { foreach (var f in _factories) f.Dispose(); _factories.Clear(); } }