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(); } }