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

169 lines
6.2 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 11: 接单状态转换
/// 对任意处于待接单状态的订单,当跑腿接取后,订单状态应变为"进行中"
/// 且 RunnerId 应被设置为接单跑腿的用户 ID。
/// **Feature: login-and-homepage, Property 11: 接单状态转换**
///
/// </summary>
public class AcceptOrderPropertyTests : 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)
{
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, "Runner")
};
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>
/// 属性:接单后订单状态变为 InProgressRunnerId 被正确设置
/// </summary>
[Property(MaxTest = 20)]
public bool RunnerId正确(PositiveInt seed)
{
var dbName = $"accept_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var runnerId = 10 + (seed.Get % 90); // 跑腿用户 ID
var ownerId = runnerId + 100; // 确保单主和跑腿不同
int orderId;
// 准备数据:创建 Pending 订单、跑腿用户和认证记录
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", GenerateUserToken(runnerId));
var response = client.PostAsync($"/api/orders/{orderId}/accept", null).Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var result = response.Content.ReadFromJsonAsync<AcceptOrderResponse>().Result!;
// 验证:状态变为 InProgress
if (result.Status != "InProgress") return false;
// 验证RunnerId 被正确设置
if (result.RunnerId != runnerId) return false;
// 验证AcceptedAt 已设置
if (result.AcceptedAt == default) return false;
return true;
}
public void Dispose()
{
foreach (var f in _factories) f.Dispose();
_factories.Clear();
}
}