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;
///
/// Property 13: 未读消息计数
/// 对任意用户的消息集合,未读消息数应等于总消息数减去已读消息数。
/// **Feature: login-and-homepage, Property 13: 未读消息计数**
///
///
public class UnreadMessageCountPropertyTests : IDisposable
{
private const string JwtSecret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!";
private const string JwtIssuer = "CampusErrand";
private const string JwtAudience = "CampusErrandApp";
private readonly List> _factories = [];
private WebApplicationFactory CreateFactory(string dbName)
{
var factory = new WebApplicationFactory()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
var efDescriptors = services
.Where(d =>
d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true
|| d.ServiceType == typeof(DbContextOptions)
|| d.ServiceType == typeof(DbContextOptions)
|| d.ImplementationType?.FullName?.Contains("EntityFrameworkCore") == true)
.ToList();
foreach (var d in efDescriptors) services.Remove(d);
services.AddDbContext(options =>
options.UseInMemoryDatabase(dbName));
});
});
_factories.Add(factory);
return factory;
}
private static string GenerateToken(int userId, string role = "User")
{
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);
}
///
/// 属性:系统消息未读数 = 可见总消息数 - 已读消息数
///
[Property(MaxTest = 20)]
public bool 系统消息未读数等于总消息数减已读数(PositiveInt totalMsgCount, NonNegativeInt readCount)
{
var totalMessages = Math.Min(totalMsgCount.Get, 20); // 限制数量避免测试过慢
var readMessages = Math.Min(readCount.Get, totalMessages);
var dbName = $"unread_sys_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
int userId;
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
// 创建用户
var user = new User
{
OpenId = $"openid_{Guid.NewGuid()}",
Phone = "13800000001",
Nickname = "测试用户",
Role = UserRole.User,
CreatedAt = DateTime.UtcNow
};
db.Users.Add(user);
db.SaveChanges();
userId = user.Id;
// 创建系统消息(目标为全部用户)
for (int i = 0; i < totalMessages; i++)
{
db.SystemMessages.Add(new SystemMessage
{
Title = $"通知{i}",
Content = $"内容{i}",
TargetType = MessageTargetType.All,
CreatedAt = DateTime.UtcNow
});
}
db.SaveChanges();
// 标记部分消息为已读
var messageIds = db.SystemMessages.Select(m => m.Id).Take(readMessages).ToList();
foreach (var msgId in messageIds)
{
db.MessageReads.Add(new MessageRead
{
UserId = userId,
MessageType = MessageType.System,
MessageId = msgId,
ReadAt = DateTime.UtcNow
});
}
db.SaveChanges();
}
// 调用未读消息数接口
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateToken(userId));
var response = client.GetAsync("/api/messages/unread-count").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var result = response.Content.ReadFromJsonAsync().Result!;
// 验证:系统消息未读数 = 总消息数 - 已读消息数
var expectedUnread = totalMessages - readMessages;
return result.SystemUnread == expectedUnread;
}
///
/// 属性:订单通知未读数 = 用户相关的状态变更订单数 - 已读订单通知数
///
[Property(MaxTest = 20)]
public bool 订单通知未读数等于总通知数减已读数(PositiveInt orderCount, NonNegativeInt readCount)
{
var totalOrders = Math.Min(orderCount.Get, 15);
var readOrders = Math.Min(readCount.Get, totalOrders);
var dbName = $"unread_order_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
int userId;
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
// 创建用户
var user = new User
{
OpenId = $"openid_{Guid.NewGuid()}",
Phone = "13800000002",
Nickname = "测试用户",
Role = UserRole.User,
CreatedAt = DateTime.UtcNow
};
db.Users.Add(user);
var runner = new User
{
OpenId = $"openid_{Guid.NewGuid()}",
Phone = "13800000003",
Nickname = "跑腿",
Role = UserRole.Runner,
CreatedAt = DateTime.UtcNow
};
db.Users.Add(runner);
db.SaveChanges();
userId = user.Id;
// 创建已接单的订单(状态非 Pending,会产生订单通知)
for (int i = 0; i < totalOrders; i++)
{
db.Orders.Add(new Order
{
OrderNo = $"ORD{Guid.NewGuid():N}"[..20],
OwnerId = userId,
RunnerId = runner.Id,
OrderType = OrderType.Pickup,
Status = OrderStatus.InProgress,
ItemName = $"物品{i}",
DeliveryLocation = "地点A",
Phone = "13800000002",
Commission = 5.0m,
TotalAmount = 5.0m,
CreatedAt = DateTime.UtcNow,
AcceptedAt = DateTime.UtcNow
});
}
db.SaveChanges();
// 标记部分订单通知为已读
var orderIds = db.Orders.Where(o => o.OwnerId == userId).Select(o => o.Id).Take(readOrders).ToList();
foreach (var orderId in orderIds)
{
db.MessageReads.Add(new MessageRead
{
UserId = userId,
MessageType = MessageType.OrderNotification,
MessageId = orderId,
ReadAt = DateTime.UtcNow
});
}
db.SaveChanges();
}
// 调用未读消息数接口
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateToken(userId));
var response = client.GetAsync("/api/messages/unread-count").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var result = response.Content.ReadFromJsonAsync().Result!;
// 验证:订单通知未读数 = 总订单通知数 - 已读数
var expectedUnread = totalOrders - readOrders;
return result.OrderNotificationUnread == expectedUnread;
}
///
/// 属性:总未读数 = 系统消息未读数 + 订单通知未读数
///
[Property(MaxTest = 10)]
public bool 总未读数等于系统未读加订单通知未读(PositiveInt sysMsgCount, PositiveInt orderCount)
{
var totalSysMsg = Math.Min(sysMsgCount.Get, 10);
var totalOrderNotif = Math.Min(orderCount.Get, 10);
var dbName = $"unread_total_{Guid.NewGuid()}";
using var factory = CreateFactory(dbName);
int userId;
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
var user = new User
{
OpenId = $"openid_{Guid.NewGuid()}",
Phone = "13800000004",
Nickname = "测试用户",
Role = UserRole.User,
CreatedAt = DateTime.UtcNow
};
db.Users.Add(user);
var runner = new User
{
OpenId = $"openid_{Guid.NewGuid()}",
Phone = "13800000005",
Nickname = "跑腿",
Role = UserRole.Runner,
CreatedAt = DateTime.UtcNow
};
db.Users.Add(runner);
db.SaveChanges();
userId = user.Id;
// 创建系统消息
for (int i = 0; i < totalSysMsg; i++)
{
db.SystemMessages.Add(new SystemMessage
{
Title = $"通知{i}",
Content = $"内容{i}",
TargetType = MessageTargetType.All,
CreatedAt = DateTime.UtcNow
});
}
// 创建订单通知
for (int i = 0; i < totalOrderNotif; i++)
{
db.Orders.Add(new Order
{
OrderNo = $"ORD{Guid.NewGuid():N}"[..20],
OwnerId = userId,
RunnerId = runner.Id,
OrderType = OrderType.Delivery,
Status = OrderStatus.Completed,
ItemName = $"物品{i}",
DeliveryLocation = "地点B",
Phone = "13800000004",
Commission = 3.0m,
TotalAmount = 3.0m,
CreatedAt = DateTime.UtcNow,
AcceptedAt = DateTime.UtcNow,
CompletedAt = DateTime.UtcNow
});
}
db.SaveChanges();
}
// 调用未读消息数接口(全部未读)
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", GenerateToken(userId));
var response = client.GetAsync("/api/messages/unread-count").Result;
if (response.StatusCode != HttpStatusCode.OK) return false;
var result = response.Content.ReadFromJsonAsync().Result!;
// 验证:总未读 = 系统未读 + 订单通知未读
return result.TotalUnread == result.SystemUnread + result.OrderNotificationUnread
&& result.SystemUnread == totalSysMsg
&& result.OrderNotificationUnread == totalOrderNotif;
}
public void Dispose()
{
foreach (var f in _factories) f.Dispose();
_factories.Clear();
}
}