xiangning 3b13099378 refactor(domain): 重构领域实体结构并更新枚举命名
- 将通用 Entities 命名空间拆分为具体业务实体子目录
- 更新所有实体类型引用为具体的 Entity 后缀类型
- 将 ModelType、ModelProvider、GroupMemberRole 等枚举重命名为 Enum 后缀
- 重构 AiModel 相关配置和映射逻辑适配新的实体结构
- 更新测试代码中的实体类型引用和枚举使用
- 调整应用层处理器中的实体创建和查询逻辑
- 修改 appsettings.json 中的基础设施主机配置为动态占位符模式
2026-07-19 14:02:32 +08:00

177 lines
6.5 KiB
C#

using BuildFlow.Platform.Domain.Entities.Notification;
using BuildFlow.Platform.Application.Notifications.Commands;
using BuildFlow.Platform.Application.Notifications.Handlers;
using BuildFlow.Platform.Application.Notifications.Queries;
using BuildFlow.Platform.Domain.Entities.Menu;
using BuildFlow.Platform.Domain.Entities.Notification;
using BuildFlow.Platform.Domain.Entities.Permission;
using BuildFlow.Platform.Domain.Entities.RefreshToken;
using BuildFlow.Platform.Domain.Entities.Role;
using BuildFlow.Platform.Domain.Entities.RolePermission;
using BuildFlow.Platform.Domain.Entities.User;
using BuildFlow.Platform.Domain.Entities.UserRole;
using BuildFlow.Platform.Infrastructure.Persistence;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using Xunit;
using INotificationDispatcher = BuildFlow.Platform.Application.Notifications.INotificationDispatcher;
namespace BuildFlow.Platform.Tests.Notifications;
/// <summary>
/// [SOP-PLATFORM-02] 通知系统全 Handler 覆盖测试。
/// 覆盖:Publish(Fanout) / GetNotifications(分页) / GetUnreadCount / MarkRead / MarkAllRead。
/// 飞书矩阵: platform/Notifications 子域(5 Handler 零覆盖→全补)
/// </summary>
public class NotificationHandlerTests
{
private static PlatformDbContext CreateDb([System.Runtime.CompilerServices.CallerMemberName] string name = "")
=> PlatformTestDb.CreateDbContext(name);
private static NotificationEntity NewNotification(Guid recipient, bool read = false, string type = "system")
=> PlatformTestDb.WithAudit(new NotificationEntity
{
RecipientUserId = recipient,
Type = type,
Title = "测试通知",
Content = "内容",
Source = "system",
IsRead = read,
ReadAt = read ? DateTime.UtcNow : null
});
[Fact]
public async Task Publish_WithRecipientUserIds_FanoutAndDispatch()
{
var db = CreateDb();
var dispatcher = Substitute.For<INotificationDispatcher>();
var u1 = Guid.NewGuid();
var u2 = Guid.NewGuid();
var handler = new PublishNotificationCommandHandler(db, dispatcher);
var count = await handler.Handle(new PublishNotificationCommand(
"task-arrived", "标题", "内容", "workflow", null, "workflow-instance",
new[] { u1, u2 }, null), CancellationToken.None);
count.Should().Be(2);
(await db.Notifications.IgnoreQueryFilters().ToListAsync())
.Should().HaveCount(2);
await dispatcher.Received(1).DispatchAsync(
Arg.Is<IReadOnlyList<NotificationEntity>>(l => l.Count == 2),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task Publish_NoRecipients_ReturnsZeroAndNoDispatch()
{
var db = CreateDb();
var dispatcher = Substitute.For<INotificationDispatcher>();
var handler = new PublishNotificationCommandHandler(db, dispatcher);
var count = await handler.Handle(new PublishNotificationCommand(
"system", "t", "c", "system", null, null, null, null), CancellationToken.None);
count.Should().Be(0);
await dispatcher.DidNotReceiveWithAnyArgs().DispatchAsync(default!, default);
}
[Fact]
public async Task GetNotifications_Paginated_ReturnsUserOnly()
{
var db = CreateDb();
var me = Guid.NewGuid();
var other = Guid.NewGuid();
db.Notifications.AddRange(NewNotification(me), NewNotification(me), NewNotification(other));
await db.SaveChangesAsync();
var handler = new GetNotificationsQueryHandler(db);
var result = await handler.Handle(
new GetNotificationsQuery(me, false, 1, 10), CancellationToken.None);
result.Items.Should().HaveCount(2);
result.Items.Should().OnlyContain(n => n.RecipientUserId == me);
result.Total.Should().Be(2);
}
[Fact]
public async Task GetNotifications_UnreadOnly_FiltersRead()
{
var db = CreateDb();
var me = Guid.NewGuid();
db.Notifications.AddRange(NewNotification(me, read: true), NewNotification(me, read: false));
await db.SaveChangesAsync();
var handler = new GetNotificationsQueryHandler(db);
var result = await handler.Handle(
new GetNotificationsQuery(me, true, 1, 10), CancellationToken.None);
result.Items.Should().HaveCount(1);
result.Items[0].IsRead.Should().BeFalse();
}
[Fact]
public async Task GetUnreadCount_ReturnsOnlyUnread()
{
var db = CreateDb();
var me = Guid.NewGuid();
db.Notifications.AddRange(
NewNotification(me, read: false),
NewNotification(me, read: false),
NewNotification(me, read: true));
await db.SaveChangesAsync();
var handler = new GetUnreadNotificationCountQueryHandler(db);
var count = await handler.Handle(new GetUnreadNotificationCountQuery(me), CancellationToken.None);
count.Should().Be(2);
}
[Fact]
public async Task MarkRead_ExistingUnread_MarksAndReturnsTrue()
{
var db = CreateDb();
var me = Guid.NewGuid();
var n = NewNotification(me, read: false);
db.Notifications.Add(n);
await db.SaveChangesAsync();
var handler = new MarkNotificationReadCommandHandler(db);
var ok = await handler.Handle(new MarkNotificationReadCommand(n.Id, me), CancellationToken.None);
ok.Should().BeTrue();
(await db.Notifications.FindAsync(n.Id))!.IsRead.Should().BeTrue();
}
[Fact]
public async Task MarkRead_NonExistent_ReturnsFalse()
{
var db = CreateDb();
var handler = new MarkNotificationReadCommandHandler(db);
var ok = await handler.Handle(
new MarkNotificationReadCommand(Guid.NewGuid(), Guid.NewGuid()), CancellationToken.None);
ok.Should().BeFalse();
}
[Fact]
public async Task MarkAllRead_MarksAllForUser()
{
var db = CreateDb();
var me = Guid.NewGuid();
var other = Guid.NewGuid();
db.Notifications.AddRange(
NewNotification(me, read: false),
NewNotification(me, read: false),
NewNotification(other, read: false));
await db.SaveChangesAsync();
var handler = new MarkAllNotificationsReadCommandHandler(db);
var affected = await handler.Handle(new MarkAllNotificationsReadCommand(me), CancellationToken.None);
affected.Should().Be(2);
(await db.Notifications.CountAsync(n => n.RecipientUserId == me && !n.IsRead)).Should().Be(0);
}
}