using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Infrastructure.Persistence; using Jiaowu.Api.Infrastructure.Scheduling; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; namespace Jiaowu.Api.Tests; public sealed class AutomaticScheduleGeneratorTests { [Fact] public async Task Development_migration_chain_accepts_current_sqlite_schema() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var db = new AppDbContext(options); await db.Database.EnsureCreatedAsync(); var migrator = new DevelopmentSqliteMigrator( db, NullLogger.Instance); await migrator.MigrateAsync(); Assert.True(await db.ScheduleTimeSlots.CountAsync() == 0); Assert.True(await db.Database .SqlQueryRaw( """ SELECT COUNT(*) AS "Value" FROM pragma_table_info('ScheduleEntries') WHERE name = 'ClassroomId' AND "notnull" = 0 """) .AnyAsync(value => value > 0)); } [Fact] public async Task Generator_schedules_roomless_course_within_allowed_time() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var db = new AppDbContext(options); await db.Database.EnsureCreatedAsync(); var term = new AcademicTerm { Code = "2026-A", Name = "2026 秋季", AcademicYear = "2026-2027", Season = TermSeason.Autumn, StartDate = new DateOnly(2026, 9, 1), EndDate = new DateOnly(2027, 1, 15) }; var college = new College { Code = "CS", Name = "计算机学院" }; var course = new Course { Code = "MOOC-01", Name = "在线专题", College = college, Credits = 1, TotalHours = 32, LectureHours = 32 }; var task = new TeachingTask { TaskNumber = "TASK-MOOC", Name = "在线专题教学班", AcademicTerm = term, Course = course, Capacity = 200, StartWeek = 1, EndWeek = 16, WeeklyHours = 2, Status = TeachingTaskStatus.Published }; var plan = new SchedulePlan { AcademicTerm = term, Name = "自动排课测试", Version = "V1" }; db.AddRange(term, college, course, task, plan); 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 = "3", EarliestPeriod = 1, LatestPeriod = 2 }); await db.SaveChangesAsync(); var result = await new AutomaticScheduleGenerator(db) .GenerateAsync(plan, CancellationToken.None); var entry = await db.ScheduleEntries.SingleAsync(); Assert.Equal(1, result.CreatedEntries); Assert.Empty(result.Messages); Assert.Equal(3, entry.DayOfWeek); Assert.Equal(1, entry.StartPeriod); Assert.Equal(2, entry.PeriodCount); Assert.Null(entry.ClassroomId); } }