using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Infrastructure.Persistence; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace Jiaowu.Api.Tests; public sealed class PersistenceTests : IAsyncLifetime { private readonly SqliteConnection _connection = new("Data Source=:memory:"); private AppDbContext _db = null!; public async Task InitializeAsync() { await _connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(_connection) .Options; _db = new AppDbContext(options); await _db.Database.EnsureCreatedAsync(); } public async Task DisposeAsync() { await _db.DisposeAsync(); await _connection.DisposeAsync(); } [Fact] public async Task Catalog_codes_are_unique() { _db.Campuses.Add(new Campus { Code = "MAIN", Name = "主校区" }); await _db.SaveChangesAsync(); _db.Campuses.Add(new Campus { Code = "MAIN", Name = "另一个校区" }); await Assert.ThrowsAsync(() => _db.SaveChangesAsync()); } [Fact] public async Task Organization_relations_can_be_persisted() { var campus = new Campus { Code = "EAST", Name = "东校区" }; var college = new College { Code = "ART", Name = "艺术学院", CampusId = campus.Id }; var major = new Major { Code = "1305", Name = "设计学", CollegeId = college.Id, DegreeType = "艺术学学士" }; _db.AddRange(campus, college, major); await _db.SaveChangesAsync(); var saved = await _db.Majors .Include(x => x.College) .ThenInclude(x => x!.Campus) .SingleAsync(); Assert.Equal("东校区", saved.College!.Campus!.Name); } [Fact] public async Task People_and_course_relations_can_be_persisted() { var campus = new Campus { Code = "MAIN", Name = "主校区" }; var college = new College { Code = "CS", Name = "计算机学院", CampusId = campus.Id }; var major = new Major { Code = "080901", Name = "计算机科学与技术", CollegeId = college.Id, DegreeType = "工学学士" }; var counselor = new ApplicationUser { UserName = "counselor", NormalizedUserName = "COUNSELOR", DisplayName = "陈老师" }; var administrativeClass = new AdministrativeClass { Code = "CS2026-01", Name = "计科 2026-1 班", MajorId = major.Id, Grade = 2026, CounselorName = counselor.DisplayName, CounselorUserId = counselor.Id, CounselorUser = counselor }; var course = new Course { Code = "CS101", Name = "程序设计基础", CollegeId = college.Id, Credits = 4, TotalHours = 64, LectureHours = 40, PracticeHours = 24, Nature = CourseNature.MajorRequired, AssessmentMethod = AssessmentMethod.Examination }; var teacher = new Teacher { TeacherNumber = "T2026001", Name = "陈老师", CollegeId = college.Id, Status = TeacherStatus.Active }; var term = new AcademicTerm { Code = "2026-1", Name = "2026—2027 学年第一学期", AcademicYear = "2026-2027", Season = TermSeason.Autumn, StartDate = new DateOnly(2026, 9, 7), EndDate = new DateOnly(2027, 1, 17), IsCurrent = true }; var building = new Building { Code = "J1", Name = "第一教学楼", CampusId = campus.Id }; var classroom = new Classroom { Code = "J1-201", Name = "J1-201", BuildingId = building.Id, Capacity = 60 }; _db.AddRange( campus, college, major, counselor, administrativeClass, teacher, new Student { StudentNumber = "202601001", Name = "周同学", AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 7), Status = StudentStatus.Active }, course, term, building, classroom); await _db.SaveChangesAsync(); _db.CurriculumPlans.Add(new CurriculumPlan { MajorId = major.Id, Name = "计科培养方案", Version = "2026版", EffectiveGrade = 2026, TotalCredits = 4, Status = CurriculumPlanStatus.Draft, Modules = [ new CurriculumModule { Code = "BASIC", Name = "专业基础", RequiredCredits = 4, Courses = [ new CurriculumCourse { CourseId = course.Id, RecommendedSemester = 1, Type = CurriculumCourseType.Required } ] } ] }); var teachingTask = new TeachingTask { TaskNumber = "2026-1-CS101-01", Name = "程序设计基础教学班", AcademicTermId = term.Id, CourseId = course.Id, Capacity = 60, Teachers = [ new TeachingTaskTeacher { TeacherId = teacher.Id, IsPrimary = true } ], Classes = [ new TeachingTaskClass { AdministrativeClassId = administrativeClass.Id } ] }; _db.TeachingTasks.Add(teachingTask); _db.SchedulePlans.Add(new SchedulePlan { AcademicTermId = term.Id, Name = "第一轮课表", Version = "V1", Entries = [ new ScheduleEntry { TeachingTaskId = teachingTask.Id, ClassroomId = classroom.Id, DayOfWeek = 1, StartPeriod = 1, PeriodCount = 2, StartWeek = 1, EndWeek = 16, WeekPattern = WeekPattern.All } ] }); await _db.SaveChangesAsync(); var savedTeacher = await _db.Teachers.Include(x => x.College).SingleAsync(); var student = await _db.Students .Include(x => x.AdministrativeClass) .ThenInclude(x => x!.Major) .SingleAsync(); var savedClass = await _db.AdministrativeClasses .Include(x => x.CounselorUser) .SingleAsync(); var savedCourse = await _db.Courses.Include(x => x.College).SingleAsync(); var curriculumPlan = await _db.CurriculumPlans .Include(x => x.Modules) .ThenInclude(x => x.Courses) .SingleAsync(); var savedTeachingTask = await _db.TeachingTasks .Include(x => x.Teachers) .Include(x => x.Classes) .SingleAsync(); var schedulePlan = await _db.SchedulePlans .Include(x => x.Entries) .ThenInclude(x => x.Classroom) .SingleAsync(); Assert.Equal("计算机学院", savedTeacher.College!.Name); Assert.Equal("计算机科学与技术", student.AdministrativeClass!.Major!.Name); Assert.Equal("陈老师", savedClass.CounselorUser!.DisplayName); Assert.Equal(4m, savedCourse.Credits); Assert.Equal(savedCourse.TotalHours, savedCourse.LectureHours + savedCourse.PracticeHours); Assert.Single(curriculumPlan.Modules); Assert.Single(curriculumPlan.Modules.Single().Courses); Assert.Single(savedTeachingTask.Teachers); Assert.Single(savedTeachingTask.Classes); Assert.Equal("J1-201", schedulePlan.Entries.Single().Classroom!.Name); } }