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

212 lines
8.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 4: 手机号隐藏规则
/// 对任意未被接取的订单非单主用户查询订单详情时API 返回的数据中手机号字段应为空或被遮蔽。
/// 仅当订单已被接取且查询者为接单跑腿时,手机号才可见。
/// **Feature: login-and-homepage, Property 4: 手机号隐藏规则**
///
/// </summary>
public class PhoneHidingPropertyTests : 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, "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>
/// 在数据库中直接创建一个订单,返回订单 ID
/// </summary>
private static int SeedOrder(WebApplicationFactory<Program> factory, int ownerId, int? runnerId, string phone)
{
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var order = new Order
{
OrderNo = $"TEST{Guid.NewGuid():N}"[..20],
OwnerId = ownerId,
RunnerId = runnerId,
OrderType = OrderType.Pickup,
Status = runnerId == null ? OrderStatus.Pending : OrderStatus.InProgress,
ItemName = "测试物品",
DeliveryLocation = "测试地点",
Phone = phone,
Commission = 5.0m,
TotalAmount = 5.0m,
CreatedAt = DateTime.UtcNow,
AcceptedAt = runnerId == null ? null : DateTime.UtcNow
};
db.Orders.Add(order);
db.SaveChanges();
return order.Id;
}
/// <summary>
/// 属性:未接单订单,非单主用户查询时手机号应为空
/// </summary>
[Property(MaxTest = 10)]
public bool (PositiveInt seed)
{
var ownerId = 100 + (seed.Get % 100);
var otherUserId = ownerId + 1; // 非单主用户
var phone = "13800138000";
var dbName = $"phone_hide_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
// 创建未接单订单RunnerId = null
var orderId = SeedOrder(factory, ownerId, null, phone);
var client = factory.CreateClient();
// 以非单主身份查询
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken(otherUserId));
var response = client.GetAsync($"/api/orders/{orderId}").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var order = response.Content.ReadFromJsonAsync<OrderResponse>().Result!;
// 手机号应为空
return order.Phone == null;
}
/// <summary>
/// 属性:单主自己查询时手机号始终可见
/// </summary>
[Property(MaxTest = 10)]
public bool (PositiveInt seed)
{
var ownerId = 200 + (seed.Get % 100);
var phone = "13900139000";
var dbName = $"phone_owner_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var orderId = SeedOrder(factory, ownerId, null, phone);
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken(ownerId));
var response = client.GetAsync($"/api/orders/{orderId}").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var order = response.Content.ReadFromJsonAsync<OrderResponse>().Result!;
return order.Phone == phone;
}
/// <summary>
/// 属性:已接单订单,接单跑腿查询时手机号可见
/// </summary>
[Property(MaxTest = 10)]
public bool (PositiveInt seed)
{
var ownerId = 300 + (seed.Get % 100);
var runnerId = ownerId + 1;
var phone = "13700137000";
var dbName = $"phone_runner_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var orderId = SeedOrder(factory, ownerId, runnerId, phone);
var client = factory.CreateClient();
// 以接单跑腿身份查询
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken(runnerId));
var response = client.GetAsync($"/api/orders/{orderId}").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var order = response.Content.ReadFromJsonAsync<OrderResponse>().Result!;
return order.Phone == phone;
}
/// <summary>
/// 属性:已接单订单,非单主非跑腿的第三方用户查询时手机号为空
/// </summary>
[Property(MaxTest = 10)]
public bool (PositiveInt seed)
{
var ownerId = 400 + (seed.Get % 100);
var runnerId = ownerId + 1;
var thirdPartyId = ownerId + 2;
var phone = "13600136000";
var dbName = $"phone_third_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
var orderId = SeedOrder(factory, ownerId, runnerId, phone);
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateUserToken(thirdPartyId));
var response = client.GetAsync($"/api/orders/{orderId}").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var order = response.Content.ReadFromJsonAsync<OrderResponse>().Result!;
return order.Phone == null;
}
public void Dispose()
{
foreach (var f in _factories) f.Dispose();
_factories.Clear();
}
}