发信改为事务内每 300 人分批写入,避免全校群发一次插入过大;数据库异常现在返回明确的 503,不再只显示模糊 500。
校级、学院级管理员支持按学院、身份、行政班、教学班、姓名/学号/工号筛选,也可指定最多 500 名收件人。所有结果均由服务端重新校验权限。 接入 CKEditor 5,支持标题、列表、引用和链接;正文扩展为 MySQL longtext。 富文本在收件箱、详情弹窗和已发送记录中统一经 DOMPurify 净化后展示。 页面改用系统现有的冷白、靛蓝、青绿色令牌,新增“选择收件人 → 编辑内容”工作台和移动端适配。
This commit is contained in:
@@ -119,6 +119,85 @@ public sealed class NotificationsControllerTests
|
||||
Assert.Contains(fixture.ClassStudentUser.Id, recipientIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task School_administrator_can_filter_recipients_by_college()
|
||||
{
|
||||
await using var fixture = await NotificationFixture.CreateAsync();
|
||||
var controller = new NotificationsController(
|
||||
fixture.Db,
|
||||
new TestDataScope(
|
||||
fixture.Sender.Id,
|
||||
fixture.FirstCollege.Id,
|
||||
SystemRoles.AcademicAdmin));
|
||||
|
||||
var result = await controller.Send(
|
||||
new SendMessageRequest(
|
||||
"第二学院通知",
|
||||
"<p><strong>仅发送</strong>给第二学院。</p>",
|
||||
RecipientMode: MessageRecipientMode.Filtered,
|
||||
RecipientFilter: new MessageRecipientFilter(
|
||||
CollegeId: fixture.SecondCollege.Id)),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.IsType<OkObjectResult>(result);
|
||||
var notification = Assert.Single(
|
||||
await fixture.Db.Notifications.ToListAsync());
|
||||
Assert.Equal(fixture.OutsideRecipient.Id, notification.UserId);
|
||||
var dispatch = await fixture.Db.MessageDispatches.SingleAsync();
|
||||
Assert.Equal(MessageAudienceType.Custom, dispatch.AudienceType);
|
||||
Assert.Equal("指定学院", dispatch.AudienceName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task College_administrator_cannot_select_recipient_outside_college()
|
||||
{
|
||||
await using var fixture = await NotificationFixture.CreateAsync();
|
||||
var controller = new NotificationsController(
|
||||
fixture.Db,
|
||||
new TestDataScope(
|
||||
fixture.Sender.Id,
|
||||
fixture.FirstCollege.Id,
|
||||
SystemRoles.CollegeAdmin));
|
||||
|
||||
var result = await controller.Send(
|
||||
new SendMessageRequest(
|
||||
"越权消息",
|
||||
"<p>不应发送。</p>",
|
||||
RecipientMode: MessageRecipientMode.Selected,
|
||||
RecipientUserIds: [fixture.OutsideRecipient.Id]),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.IsType<BadRequestObjectResult>(result);
|
||||
Assert.Empty(await fixture.Db.Notifications.ToListAsync());
|
||||
Assert.Empty(await fixture.Db.MessageDispatches.ToListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Administrator_can_send_rich_content_larger_than_legacy_limit()
|
||||
{
|
||||
await using var fixture = await NotificationFixture.CreateAsync();
|
||||
var controller = new NotificationsController(
|
||||
fixture.Db,
|
||||
new TestDataScope(
|
||||
fixture.Sender.Id,
|
||||
fixture.FirstCollege.Id,
|
||||
SystemRoles.AcademicAdmin));
|
||||
var content = $"<h2>教学安排</h2><p>{new string('内', 1500)}</p>";
|
||||
|
||||
var result = await controller.Send(
|
||||
new SendMessageRequest(
|
||||
"富文本通知",
|
||||
content,
|
||||
RecipientMode: MessageRecipientMode.Selected,
|
||||
RecipientUserIds: [fixture.ClassStudentUser.Id]),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.IsType<OkObjectResult>(result);
|
||||
var notification = await fixture.Db.Notifications.SingleAsync();
|
||||
Assert.Equal(content, notification.Content);
|
||||
Assert.Equal(fixture.ClassStudentUser.Id, notification.UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Publishing_course_grades_automatically_notifies_roster_students()
|
||||
{
|
||||
@@ -189,6 +268,7 @@ public sealed class NotificationsControllerTests
|
||||
SqliteConnection connection,
|
||||
AppDbContext db,
|
||||
College firstCollege,
|
||||
College secondCollege,
|
||||
ApplicationUser sender,
|
||||
ApplicationUser collegeRecipient,
|
||||
ApplicationUser outsideRecipient,
|
||||
@@ -201,6 +281,7 @@ public sealed class NotificationsControllerTests
|
||||
this.connection = connection;
|
||||
Db = db;
|
||||
FirstCollege = firstCollege;
|
||||
SecondCollege = secondCollege;
|
||||
Sender = sender;
|
||||
CollegeRecipient = collegeRecipient;
|
||||
OutsideRecipient = outsideRecipient;
|
||||
@@ -213,6 +294,7 @@ public sealed class NotificationsControllerTests
|
||||
|
||||
public AppDbContext Db { get; }
|
||||
public College FirstCollege { get; }
|
||||
public College SecondCollege { get; }
|
||||
public ApplicationUser Sender { get; }
|
||||
public ApplicationUser CollegeRecipient { get; }
|
||||
public ApplicationUser OutsideRecipient { get; }
|
||||
@@ -362,6 +444,7 @@ public sealed class NotificationsControllerTests
|
||||
connection,
|
||||
db,
|
||||
firstCollege,
|
||||
secondCollege,
|
||||
sender,
|
||||
collegeRecipient,
|
||||
outsideRecipient,
|
||||
|
||||
Reference in New Issue
Block a user