Files
Academic-Affairs-System/tests/Jiaowu.Api.Tests/CourseGradeStatisticsCronSchedulerTests.cs
T
biss cd073885b5 “教学班成绩分析”页面新增“定时刷新设置”,支持启停、刷新间隔、单次处理上限、查看上次/下次扫描时间。
配置保存在独立数据库表 CourseGradeStatisticsRefreshSettings,不是修改 appsettings.json。
后台每 10 秒读取配置,仅到期扫描;成绩录入、导入、审批时不再立即创建统计任务。
扫描发现过期数据后,仍通过持久任务、Outbox 和 RabbitMQ 执行;Redis统计缓存由处理任务统一刷新。
2026-08-09 20:51:57 +08:00

177 lines
6.0 KiB
C#

using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.System;
using Jiaowu.Api.Infrastructure.Grades;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
namespace Jiaowu.Api.Tests;
public sealed class CourseGradeStatisticsRefreshSchedulerTests
{
[Fact]
public async Task EnqueueDueAsync_UsesPersistentScheduleAndHonorsInterval()
{
await using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
await using var db = new AppDbContext(options);
await db.Database.EnsureCreatedAsync();
var scheduler = new CourseGradeStatisticsRefreshScheduler(
db,
NullLogger<CourseGradeStatisticsRefreshScheduler>.Instance);
var now = new DateTime(2026, 8, 9, 12, 0, 0, DateTimeKind.Utc);
await scheduler.EnqueueDueAsync(now, CancellationToken.None);
var setting = Assert.Single(await db.CourseGradeStatisticsRefreshSettings.ToListAsync());
Assert.True(setting.IsEnabled);
Assert.Equal(now, setting.LastRunAt);
await scheduler.EnqueueDueAsync(now.AddMinutes(1), CancellationToken.None);
Assert.Equal(now, setting.LastRunAt);
}
[Fact]
public async Task EnqueueStaleAsync_GroupsByCourseTerm_AndSkipsActiveTarget()
{
await using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
await using var db = new AppDbContext(options);
await db.Database.EnsureCreatedAsync();
var college = new College { Code = "SCHEDULE", Name = "定时任务学院" };
var major = new Major
{
Code = "SCHEDULE-M",
Name = "定时任务专业",
College = college,
DegreeType = "本科"
};
var administrativeClass = new AdministrativeClass
{
Code = "SCHEDULE-C",
Name = "定时任务一班",
Major = major,
Grade = 2026
};
var student = new Student
{
StudentNumber = "SCHEDULE-001",
Name = "定时任务学生",
AdministrativeClass = administrativeClass,
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 1)
};
var course = new Course
{
Code = "SCHEDULE-COURSE",
Name = "定时任务课程",
College = college,
Credits = 2,
TotalHours = 32,
LectureHours = 32
};
var term = new AcademicTerm
{
Code = "2026-A",
Name = "2026-2027 学年第一学期",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 1),
EndDate = new DateOnly(2027, 1, 15)
};
var firstTask = new TeachingTask
{
TaskNumber = "SCHEDULE-T1",
Name = "定时任务教学班一",
Course = course,
AcademicTerm = term,
Capacity = 30
};
var secondTask = new TeachingTask
{
TaskNumber = "SCHEDULE-T2",
Name = "定时任务教学班二",
Course = course,
AcademicTerm = term,
Capacity = 30
};
var firstSheet = PublishedSheet(firstTask, student, 80m);
var secondSheet = PublishedSheet(secondTask, student, 90m);
db.GradeSheets.AddRange(firstSheet, secondSheet);
await db.SaveChangesAsync();
var scheduler = new CourseGradeStatisticsRefreshScheduler(
db,
NullLogger<CourseGradeStatisticsRefreshScheduler>.Instance);
var queued = await scheduler.EnqueueStaleAsync(100, CancellationToken.None);
var queuedAgain = await scheduler.EnqueueStaleAsync(100, CancellationToken.None);
Assert.Equal(1, queued);
Assert.Equal(0, queuedAgain);
var job = Assert.Single(await db.CourseGradeStatisticsRefreshJobs.ToListAsync());
var outbox = Assert.Single(await db.BackgroundJobOutboxMessages.ToListAsync());
Assert.Equal(BackgroundJobKind.CourseGradeStatisticsRefresh, outbox.JobKind);
job.Status = CourseGradeStatisticsRefreshJobStatus.Succeeded;
db.TeachingTaskGradeStatistics.AddRange(
FreshStatistic(firstSheet, course, term),
FreshStatistic(secondSheet, course, term));
await db.SaveChangesAsync();
var queuedAfterFreshStatistics = await scheduler.EnqueueStaleAsync(
100,
CancellationToken.None);
Assert.Equal(0, queuedAfterFreshStatistics);
Assert.Single(await db.CourseGradeStatisticsRefreshJobs.ToListAsync());
}
private static GradeSheet PublishedSheet(
TeachingTask task,
Student student,
decimal score)
{
var sheet = new GradeSheet
{
TeachingTask = task,
Status = GradeSheetStatus.Published,
PublishedAt = DateTime.UtcNow
};
sheet.Records.Add(new GradeRecord
{
GradeSheet = sheet,
Student = student,
TotalScore = score,
GradePoint = 3m
});
return sheet;
}
private static TeachingTaskGradeStatistic FreshStatistic(
GradeSheet sheet,
Course course,
AcademicTerm term) => new()
{
GradeSheetId = sheet.Id,
TeachingTaskId = sheet.TeachingTaskId,
CourseId = course.Id,
AcademicTermId = term.Id,
StudentCount = 1,
PassedCount = 1,
HighestScore = 80m,
AverageScore = 80m,
MedianScore = 80m,
LowestScore = 80m,
PassRate = 100m,
CalculatedAt = DateTime.UtcNow.AddMinutes(1)
};
}