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

330 lines
12 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 13: 未读消息计数
/// 对任意用户的消息集合,未读消息数应等于总消息数减去已读消息数。
/// **Feature: login-and-homepage, Property 13: 未读消息计数**
///
/// </summary>
public class UnreadMessageCountPropertyTests : 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 = "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);
}
/// <summary>
/// 属性:系统消息未读数 = 可见总消息数 - 已读消息数
/// </summary>
[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<AppDbContext>();
// 创建用户
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<UnreadCountResponse>().Result!;
// 验证:系统消息未读数 = 总消息数 - 已读消息数
var expectedUnread = totalMessages - readMessages;
return result.SystemUnread == expectedUnread;
}
/// <summary>
/// 属性:订单通知未读数 = 用户相关的状态变更订单数 - 已读订单通知数
/// </summary>
[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<AppDbContext>();
// 创建用户
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<UnreadCountResponse>().Result!;
// 验证:订单通知未读数 = 总订单通知数 - 已读数
var expectedUnread = totalOrders - readOrders;
return result.OrderNotificationUnread == expectedUnread;
}
/// <summary>
/// 属性:总未读数 = 系统消息未读数 + 订单通知未读数
/// </summary>
[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<AppDbContext>();
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<UnreadCountResponse>().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();
}
}