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

149 lines
6.0 KiB
C#
Raw Permalink 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 2: 跑腿佣金输入校验
/// 对任意数值输入,当该值小于 1.0 时,订单创建请求应被拒绝。
/// 当该值大于等于 1.0 且小数位不超过 1 位时,订单创建请求应被接受。
/// **Feature: login-and-homepage, Property 2: 跑腿佣金输入校验**
///
/// </summary>
public class CommissionValidationPropertyTests : 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);
}
private static CreateOrderRequest MakePickupOrder(decimal commission) => new()
{
OrderType = "Pickup",
ItemName = "测试物品",
DeliveryLocation = "测试地点",
Phone = "13800138000",
Commission = commission
};
/// <summary>
/// 属性:佣金小于 1.0 时,订单创建应被拒绝(返回 400
/// </summary>
[Property(MaxTest = 20)]
public bool 1(PositiveInt seed)
{
// 生成 0.0 ~ 0.9 之间的佣金值
var commission = Math.Round((seed.Get % 10) * 0.1m, 1);
if (commission >= 1.0m) return true; // 跳过不符合条件的值
var dbName = $"comm_low_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken());
var request = MakePickupOrder(commission);
var response = client.PostAsJsonAsync("/api/orders", request).Result;
return response.StatusCode == HttpStatusCode.BadRequest;
}
/// <summary>
/// 属性:佣金大于等于 1.0 且小数位不超过 1 位时,订单创建应成功(返回 201
/// </summary>
[Property(MaxTest = 20)]
public bool (PositiveInt seed)
{
// 生成 1.0 ~ 100.0 之间的合法佣金值1 位小数)
var commission = Math.Round(1.0m + (seed.Get % 990) * 0.1m, 1);
var dbName = $"comm_ok_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken());
var request = MakePickupOrder(commission);
var response = client.PostAsJsonAsync("/api/orders", request).Result;
return response.StatusCode == HttpStatusCode.Created;
}
/// <summary>
/// 属性:佣金小数位超过 1 位时,订单创建应被拒绝(返回 400
/// </summary>
[Property(MaxTest = 20)]
public bool 1(PositiveInt seed)
{
// 生成小数位为 2 位的佣金值,如 1.23, 2.56 等
var commission = 1.0m + (seed.Get % 900) * 0.01m;
// 确保确实有超过 1 位小数
if (commission == Math.Round(commission, 1)) commission += 0.01m;
var dbName = $"comm_dec_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken());
var request = MakePickupOrder(commission);
var response = client.PostAsJsonAsync("/api/orders", request).Result;
return response.StatusCode == HttpStatusCode.BadRequest;
}
public void Dispose()
{
foreach (var f in _factories) f.Dispose();
_factories.Clear();
}
}