233 lines
7.5 KiB
C#
233 lines
7.5 KiB
C#
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<AppDbContext>()
|
|
.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<OkObjectResult>(
|
|
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<OkObjectResult>(
|
|
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<string>([SystemRoles.Student]));
|
|
}
|
|
}
|