using System.Text.Json; using Jiaowu.Api.Controllers; using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Infrastructure.Auth; using Jiaowu.Api.Infrastructure.Persistence; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace Jiaowu.Api.Tests; public sealed class AcademicPlanningControllerTests { [Fact] public async Task Simulation_reuses_published_grade_and_checks_prerequisites() { 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 user = new ApplicationUser { Id = Guid.NewGuid(), UserName = "student", NormalizedUserName = "STUDENT", DisplayName = "规划学生" }; var college = new College { Code = "CS", Name = "计算机学院" }; var major = new Major { Code = "SE", Name = "软件工程", CollegeId = college.Id, DegreeType = "工学", SchoolingYears = 4 }; var administrativeClass = new AdministrativeClass { Code = "SE2501", Name = "软件工程2501班", MajorId = major.Id, Grade = 2025 }; var student = new Student { StudentNumber = "20250001", Name = "规划学生", AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2025, EnrollmentDate = new DateOnly(2025, 9, 1), UserId = user.Id }; var pastTerm = new AcademicTerm { Code = "2025-A", Name = "2025—2026 学年秋季学期", AcademicYear = "2025-2026", Season = TermSeason.Autumn, StartDate = new DateOnly(2025, 9, 1), EndDate = new DateOnly(2026, 1, 15) }; var currentTerm = new AcademicTerm { Code = "2025-S", Name = "2025—2026 学年春季学期", AcademicYear = "2025-2026", Season = TermSeason.Spring, StartDate = new DateOnly(2026, 2, 20), EndDate = new DateOnly(2026, 7, 10), IsCurrent = true }; var introduction = new Course { Code = "CS101", Name = "程序设计基础", CollegeId = college.Id, Credits = 4, TotalHours = 64, LectureHours = 48, PracticeHours = 16, Nature = CourseNature.MajorRequired, AssessmentMethod = AssessmentMethod.Examination }; var dataStructures = new Course { Code = "CS201", Name = "数据结构", CollegeId = college.Id, Credits = 4, TotalHours = 64, LectureHours = 48, PracticeHours = 16, Nature = CourseNature.MajorRequired, AssessmentMethod = AssessmentMethod.Examination, Prerequisites = [ new CoursePrerequisite { PrerequisiteCourseId = introduction.Id } ] }; var plan = new CurriculumPlan { MajorId = major.Id, Name = "软件工程本科培养方案", Version = "2025", EffectiveGrade = 2025, TotalCredits = 8, Status = CurriculumPlanStatus.Published, PublishedAt = DateTime.UtcNow, Modules = [ new CurriculumModule { Code = "M01", Name = "专业基础", RequiredCredits = 0, Courses = [ new CurriculumCourse { CourseId = introduction.Id, RecommendedSemester = 1, Type = CurriculumCourseType.Required }, new CurriculumCourse { CourseId = dataStructures.Id, RecommendedSemester = 3, Type = CurriculumCourseType.Required } ] } ] }; var teachingTask = new TeachingTask { TaskNumber = "2025-CS101-01", Name = "程序设计基础", AcademicTermId = pastTerm.Id, CourseId = introduction.Id, Capacity = 50, Status = TeachingTaskStatus.Closed }; var gradeSheet = new GradeSheet { TeachingTaskId = teachingTask.Id, Status = GradeSheetStatus.Published, PublishedAt = DateTime.UtcNow, Records = [ new GradeRecord { StudentId = student.Id, TotalScore = 82, GradePoint = 3.2m } ] }; db.AddRange( user, college, major, administrativeClass, student, pastTerm, currentTerm, introduction, dataStructures, plan, teachingTask, gradeSheet); await db.SaveChangesAsync(); var controller = new AcademicPlanningController( db, new StudentDataScope(user.Id)); var overview = Assert.IsType( await controller.Get(default)); using var overviewJson = ToJson(overview.Value); Assert.Equal( 4, overviewJson.RootElement .GetProperty("baseline") .GetProperty("earnedCredits") .GetDecimal()); Assert.Equal( dataStructures.Id, overviewJson.RootElement .GetProperty("nextSemesterSuggestion")[0] .GetProperty("courseId") .GetGuid()); var result = Assert.IsType( await controller.Simulate( new AcademicPlanningRequest( [ new AcademicPlanningTermRequest( 3, [dataStructures.Id]) ]), default)); using var resultJson = ToJson(result.Value); Assert.Empty(resultJson.RootElement.GetProperty("conflicts").EnumerateArray()); var projected = resultJson.RootElement.GetProperty("projected"); Assert.Equal(8, projected.GetProperty("earnedCredits").GetDecimal()); Assert.Equal( (int)GraduationAuditConclusion.Eligible, projected.GetProperty("graduationConclusion").GetInt32()); } private static JsonDocument ToJson(object? value) => JsonDocument.Parse( JsonSerializer.Serialize( value, new JsonSerializerOptions(JsonSerializerDefaults.Web))); private sealed class StudentDataScope(Guid userId) : ICurrentUserDataScope { public CurrentUserScope Current { get; } = new( userId, "规划学生", null, DataScope.Self, new HashSet([SystemRoles.Student])); } }