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( options => options.UseSqlite( $"Data Source={databasePath};Pooling=False")); services.AddScoped(); services.AddScoped(); var provider = services.BuildServiceProvider(); try { Guid jobId; await using (var seedScope = provider.CreateAsyncScope()) { var db = seedScope.ServiceProvider.GetRequiredService(); 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(); await processor.ProcessAsync(jobId, CancellationToken.None); } await using (var assertScope = provider.CreateAsyncScope()) { var db = assertScope.ServiceProvider.GetRequiredService(); 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); } } }