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

244 lines
8.7 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 24: 封禁跑腿接单拒绝
/// 对任意被封禁的跑腿用户,接单请求应被拒绝并返回错误信息。
/// **Feature: login-and-homepage, Property 24: 封禁跑腿接单拒绝**
///
/// </summary>
public class BannedRunnerPropertyTests : 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 GenerateToken(int userId, string role = "Runner")
{
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, role)
};
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>
/// 属性:被封禁的跑腿接单请求应被拒绝(返回 400
/// </summary>
[Property(MaxTest = 20)]
public bool (PositiveInt seed)
{
var dbName = $"banned_runner_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var runnerId = 10 + (seed.Get % 90);
var ownerId = runnerId + 100;
int orderId;
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 创建单主
db.Users.Add(new User
{
Id = ownerId,
OpenId = $"owner_{ownerId}",
Phone = "13900000001",
Nickname = "单主",
CreatedAt = DateTime.UtcNow
});
// 创建被封禁的跑腿用户
db.Users.Add(new User
{
Id = runnerId,
OpenId = $"runner_{runnerId}",
Phone = "13900000002",
Nickname = "跑腿",
Role = UserRole.Runner,
IsBanned = true, // 已封禁
CreatedAt = DateTime.UtcNow
});
// 创建跑腿认证记录(已通过)
db.RunnerCertifications.Add(new RunnerCertification
{
UserId = runnerId,
RealName = "测试跑腿",
Phone = "13900000002",
Status = CertificationStatus.Approved,
CreatedAt = DateTime.UtcNow
});
// 创建待接单订单
var commission = Math.Round(1.0m + (seed.Get % 490) * 0.1m, 1);
var order = new Order
{
OrderNo = $"ORD{Guid.NewGuid():N}"[..20],
OwnerId = ownerId,
OrderType = OrderType.Pickup,
Status = OrderStatus.Pending,
ItemName = "测试物品",
DeliveryLocation = "测试地点",
Phone = "13800138000",
Commission = commission,
TotalAmount = commission,
CreatedAt = DateTime.UtcNow
};
db.Orders.Add(order);
db.SaveChanges();
orderId = order.Id;
}
// 被封禁的跑腿尝试接单
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateToken(runnerId));
var response = client.PostAsync($"/api/orders/{orderId}/accept", null).Result;
// 验证请求应被拒绝400
if (response.StatusCode != HttpStatusCode.BadRequest) return false;
// 验证:订单状态仍为 Pending
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var order = db.Orders.Find(orderId);
if (order == null || order.Status != OrderStatus.Pending) return false;
if (order.RunnerId != null) return false;
}
return true;
}
/// <summary>
/// 属性:未封禁的跑腿可以正常接单
/// </summary>
[Property(MaxTest = 20)]
public bool (PositiveInt seed)
{
var dbName = $"unbanned_runner_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var runnerId = 10 + (seed.Get % 90);
var ownerId = runnerId + 100;
int orderId;
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Users.Add(new User
{
Id = ownerId,
OpenId = $"owner_{ownerId}",
Phone = "13900000001",
Nickname = "单主",
CreatedAt = DateTime.UtcNow
});
// 创建未封禁的跑腿用户
db.Users.Add(new User
{
Id = runnerId,
OpenId = $"runner_{runnerId}",
Phone = "13900000002",
Nickname = "跑腿",
Role = UserRole.Runner,
IsBanned = false, // 未封禁
CreatedAt = DateTime.UtcNow
});
db.RunnerCertifications.Add(new RunnerCertification
{
UserId = runnerId,
RealName = "测试跑腿",
Phone = "13900000002",
Status = CertificationStatus.Approved,
CreatedAt = DateTime.UtcNow
});
var commission = Math.Round(1.0m + (seed.Get % 490) * 0.1m, 1);
var order = new Order
{
OrderNo = $"ORD{Guid.NewGuid():N}"[..20],
OwnerId = ownerId,
OrderType = OrderType.Pickup,
Status = OrderStatus.Pending,
ItemName = "测试物品",
DeliveryLocation = "测试地点",
Phone = "13800138000",
Commission = commission,
TotalAmount = commission,
CreatedAt = DateTime.UtcNow
};
db.Orders.Add(order);
db.SaveChanges();
orderId = order.Id;
}
// 未封禁的跑腿接单
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateToken(runnerId));
var response = client.PostAsync($"/api/orders/{orderId}/accept", null).Result;
// 验证接单成功200
return response.StatusCode == HttpStatusCode.OK;
}
public void Dispose()
{
foreach (var f in _factories) f.Dispose();
_factories.Clear();
}
}