Files
Academic-Affairs-System/tests/Jiaowu.Api.Tests/AutomaticScheduleJobProcessorTests.cs
T
biss d67a07f23e 主要改动:
自动排课接口立即返回 202 + jobId
后台服务独立执行,不受浏览器关闭或前端超时影响
进度、成功、失败状态持久化到数据库
服务重启后自动恢复排队中或中断的任务
同一草稿禁止重复提交后台任务
运行期间禁止编辑、发布或删除该课表
排课结果和任务成功状态在同一事务中提交
前端每秒轮询进度,显示已处理教学班和已规划安排数
2026-07-24 21:55:07 +08:00

140 lines
5.4 KiB
C#

using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.Scheduling;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Jiaowu.Api.Tests;
public sealed class AutomaticScheduleJobProcessorTests
{
[Fact]
public async Task Processor_persists_progress_result_and_schedule_entries()
{
var databasePath = Path.Combine(
Path.GetTempPath(),
$"jiaowu-auto-schedule-{Guid.NewGuid():N}.sqlite");
var services = new ServiceCollection();
services.AddLogging();
services.AddDbContext<AppDbContext>(
options => options.UseSqlite(
$"Data Source={databasePath};Pooling=False"));
services.AddScoped<AutomaticScheduleGenerator>();
services.AddScoped<AutomaticScheduleJobProcessor>();
var provider = services.BuildServiceProvider();
try
{
Guid jobId;
await using (var seedScope = provider.CreateAsyncScope())
{
var db = seedScope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.EnsureCreatedAsync();
var term = new AcademicTerm
{
Code = "2026-JOB",
Name = "后台排课学期",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 1),
EndDate = new DateOnly(2027, 1, 15)
};
var college = new College { Code = "JOB", Name = "后台任务学院" };
var course = new Course
{
Code = "JOB-01",
Name = "后台任务课程",
College = college,
Credits = 1,
TotalHours = 32,
LectureHours = 32
};
var task = new TeachingTask
{
TaskNumber = "TASK-JOB",
Name = "后台任务教学班",
AcademicTerm = term,
Course = course,
Capacity = 100,
StartWeek = 1,
EndWeek = 16,
WeeklyHours = 2,
Status = TeachingTaskStatus.Published
};
var plan = new SchedulePlan
{
AcademicTerm = term,
Name = "后台排课草稿",
Version = "V1"
};
var job = new AutomaticScheduleJob
{
SchedulePlan = plan,
ActiveSchedulePlanId = plan.Id
};
jobId = job.Id;
db.AddRange(term, college, course, task, plan, job);
db.ScheduleTimeSlots.AddRange(
new ScheduleTimeSlot
{
AcademicTerm = term,
PeriodNumber = 1,
Name = "第 1 节",
StartsAt = new TimeOnly(8, 0),
EndsAt = new TimeOnly(8, 45)
},
new ScheduleTimeSlot
{
AcademicTerm = term,
PeriodNumber = 2,
Name = "第 2 节",
StartsAt = new TimeOnly(8, 55),
EndsAt = new TimeOnly(9, 40)
});
db.TeachingTaskScheduleConstraints.Add(
new TeachingTaskScheduleConstraint
{
TeachingTask = task,
RequiresClassroom = false,
AllowedDayOfWeeks = "2",
EarliestPeriod = 1,
LatestPeriod = 2
});
await db.SaveChangesAsync();
}
await using (var workerScope = provider.CreateAsyncScope())
{
var processor = workerScope.ServiceProvider
.GetRequiredService<AutomaticScheduleJobProcessor>();
await processor.ProcessAsync(jobId, CancellationToken.None);
}
await using (var assertScope = provider.CreateAsyncScope())
{
var db = assertScope.ServiceProvider.GetRequiredService<AppDbContext>();
var job = await db.AutomaticScheduleJobs.SingleAsync();
var entry = await db.ScheduleEntries.SingleAsync();
Assert.Equal(AutomaticScheduleJobStatus.Succeeded, job.Status);
Assert.Null(job.ActiveSchedulePlanId);
Assert.Equal(1, job.TotalTasks);
Assert.Equal(1, job.ProcessedTasks);
Assert.Equal(1, job.CreatedEntries);
Assert.Equal(1, job.CompletedTasks);
Assert.Equal("[]", job.MessagesJson);
Assert.NotNull(job.StartedAt);
Assert.NotNull(job.CompletedAt);
Assert.Null(job.ErrorMessage);
Assert.Equal(2, entry.DayOfWeek);
Assert.Equal(2, entry.PeriodCount);
}
}
finally
{
await provider.DisposeAsync();
File.Delete(databasePath);
}
}
}