campus-errand/server.tests/OrderHallSortPropertyTests.cs
2026-03-12 18:12:10 +08:00

132 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net;
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 10: 订单大厅佣金排序
/// 对任意订单列表,当选择佣金优先排序时,返回的订单应按佣金金额从高到低排列。
/// **Feature: login-and-homepage, Property 10: 订单大厅佣金排序**
///
/// </summary>
public class OrderHallSortPropertyTests : 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>
[Property(MaxTest = 20)]
public bool (PositiveInt seed)
{
var dbName = $"hall_sort_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
// 插入多个 Pending 状态订单,佣金各不相同
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var rng = new Random(seed.Get);
var count = rng.Next(2, 10);
for (int i = 0; i < count; i++)
{
// 生成 1.0 ~ 50.0 之间的合法佣金1 位小数)
var commission = Math.Round(1.0m + rng.Next(0, 490) * 0.1m, 1);
db.Orders.Add(new Order
{
OrderNo = $"ORD{Guid.NewGuid():N}"[..20],
OwnerId = 99,
OrderType = OrderType.Pickup,
Status = OrderStatus.Pending,
ItemName = $"物品{i}",
DeliveryLocation = "测试地点",
Phone = "13800138000",
Commission = commission,
TotalAmount = commission,
CreatedAt = DateTime.UtcNow
});
}
db.SaveChanges();
}
// 调用订单大厅接口(默认佣金优先排序)
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken());
var response = client.GetAsync("/api/orders/hall?sort=commission").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var orders = response.Content.ReadFromJsonAsync<List<OrderHallItemResponse>>().Result!;
// 验证:按佣金从高到低排列
for (int i = 1; i < orders.Count; i++)
{
if (orders[i].Commission > orders[i - 1].Commission) return false;
}
return true;
}
public void Dispose()
{
foreach (var f in _factories) f.Dispose();
_factories.Clear();
}
}