270 lines
10 KiB
C#
270 lines
10 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// Property 8: 打包费计算
|
||
/// 对任意美食街订单,当门店配置为总打包费模式时,额外费用等于固定打包费金额;
|
||
/// 当门店配置为单份打包费模式时,额外费用等于菜品总份数乘以单份打包费金额。
|
||
/// **Feature: login-and-homepage, Property 8: 打包费计算**
|
||
///
|
||
/// </summary>
|
||
public class PackingFeePropertyTests : IDisposable
|
||
{
|
||
private const string JwtSecret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!";
|
||
private const string JwtIssuer = "CampusErrand";
|
||
private const string JwtAudience = "CampusErrandApp";
|
||
|
||
private readonly List<WebApplicationFactory<Program>> _factories = [];
|
||
|
||
private WebApplicationFactory<Program> CreateFactory(string dbName)
|
||
{
|
||
var factory = new WebApplicationFactory<Program>()
|
||
.WithWebHostBuilder(builder =>
|
||
{
|
||
builder.ConfigureServices(services =>
|
||
{
|
||
var efDescriptors = services
|
||
.Where(d =>
|
||
d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true
|
||
|| d.ServiceType == typeof(DbContextOptions<AppDbContext>)
|
||
|| d.ServiceType == typeof(DbContextOptions)
|
||
|| d.ImplementationType?.FullName?.Contains("EntityFrameworkCore") == true)
|
||
.ToList();
|
||
foreach (var d in efDescriptors) services.Remove(d);
|
||
|
||
services.AddDbContext<AppDbContext>(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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在数据库中创建门店和菜品,返回门店和菜品信息
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:总打包费模式下,额外费用等于固定打包费金额(不论菜品数量)
|
||
/// </summary>
|
||
[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<AppDbContext>();
|
||
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<OrderResponse>().Result;
|
||
if (order == null) return false;
|
||
|
||
// 验证:商品总金额 = 菜品总额 + 固定打包费
|
||
var expectedGoodsAmount = (dishPrice * quantity) + packingFeeAmount;
|
||
return order.GoodsAmount == expectedGoodsAmount;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:单份打包费模式下,额外费用等于菜品总份数乘以单份打包费金额
|
||
/// </summary>
|
||
[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<AppDbContext>();
|
||
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<OrderResponse>().Result;
|
||
if (order == null) return false;
|
||
|
||
// 验证:商品总金额 = 菜品总额 + (份数 × 单份打包费)
|
||
var expectedPackingFee = packingFeePerItem * quantity;
|
||
var expectedGoodsAmount = (dishPrice * quantity) + expectedPackingFee;
|
||
return order.GoodsAmount == expectedGoodsAmount;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:支付总金额 = 商品总金额(含打包费) + 跑腿佣金
|
||
/// </summary>
|
||
[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<AppDbContext>();
|
||
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<OrderResponse>().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();
|
||
}
|
||
}
|