修复实验成绩管理:

管理列表改为服务端分页,支持 10/20/50 条切换。
增加学期、成绩状态、开课学院以及项目/课程/教学班/教师关键词筛选。
数据范围分级:校级管理员可查看全校,院级管理员仅限本学院,教师仅限本人任课项目。
修复任课教师提交审核时报“无权限”:工作流查询现在会加载任课教师关系。
增加权限、学院隔离、分页和筛选回归测试。
This commit is contained in:
2026-08-09 21:48:19 +08:00 Unverified
parent cd073885b5
commit c26e082a77
8 changed files with 7162 additions and 10 deletions
@@ -11,6 +11,160 @@ namespace Jiaowu.Api.Tests;
public sealed class ExperimentGradesControllerTests
{
[Fact]
public async Task AssignedTeacher_CanSubmitExperimentGradeSheet()
{
await using var fixture = await ExperimentGradeFixture.CreateAsync();
var project = new ExperimentProject
{
TeachingTaskId = fixture.Task.Id,
Code = "LAB-TEACHER",
Name = "教师提交实验",
ArrangementMode = ExperimentArrangementMode.Centralized,
StartDate = fixture.Term.StartDate,
EndDate = fixture.Term.EndDate,
Status = ExperimentProjectStatus.Published,
PublishedAt = DateTime.UtcNow
};
fixture.Db.ExperimentProjects.Add(project);
await fixture.Db.SaveChangesAsync();
var admin = fixture.ExperimentGrades(fixture.AdminScope);
await admin.CreateSheet(
new ExperimentGradeSheetRequest(
project.Id,
1,
60,
[new ExperimentGradeItemRequest(
"操作",
ExperimentGradeItemKind.Operation,
100)]),
CancellationToken.None);
var sheet = await fixture.Db.ExperimentGradeSheets
.Include(x => x.Items)
.Include(x => x.Records)
.ThenInclude(x => x.ItemScores)
.SingleAsync();
var record = Assert.Single(sheet.Records);
var item = Assert.Single(sheet.Items);
var teacher = fixture.ExperimentGrades(fixture.TeacherScope);
Assert.IsType<NoContentResult>(await teacher.UpdateRecords(
sheet.Id,
new ExperimentGradeRecordsRequest(
[
new ExperimentGradeRecordRequest(
record.Id,
ExperimentParticipationStatus.Completed,
false,
1,
null,
null,
false,
null,
[new ExperimentGradeItemScoreRequest(item.Id, 85, null)])
]),
CancellationToken.None));
fixture.Db.ChangeTracker.Clear();
Assert.IsType<NoContentResult>(await teacher.Submit(
sheet.Id,
CancellationToken.None));
}
[Fact]
public async Task ManagementList_IsPagedFilteredAndCollegeScoped()
{
await using var fixture = await ExperimentGradeFixture.CreateAsync();
var collegeId = await fixture.Db.Courses
.Where(x => x.Id == fixture.Task.CourseId)
.Select(x => x.CollegeId)
.SingleAsync();
for (var index = 1; index <= 12; index++)
{
fixture.Db.ExperimentProjects.Add(new ExperimentProject
{
TeachingTaskId = fixture.Task.Id,
Code = $"LAB-{index:00}",
Name = $"分页实验 {index:00}",
ArrangementMode = ExperimentArrangementMode.Centralized,
StartDate = fixture.Term.StartDate,
EndDate = fixture.Term.EndDate,
Status = ExperimentProjectStatus.Published,
PublishedAt = DateTime.UtcNow
});
}
var otherCollege = new College { Code = "OTHER", Name = "其他学院" };
var otherCourse = new Course
{
CollegeId = otherCollege.Id,
Code = "OTHER-LAB",
Name = "其他学院实验",
Credits = 1,
TotalHours = 16,
PracticeHours = 16,
Nature = CourseNature.Practice,
AssessmentMethod = AssessmentMethod.Assessment
};
var otherTask = new TeachingTask
{
AcademicTermId = fixture.Term.Id,
CourseId = otherCourse.Id,
TaskNumber = "OTHER-LAB-01",
Name = "其他学院实验班",
Capacity = 20,
Status = TeachingTaskStatus.Published
};
var otherProject = new ExperimentProject
{
TeachingTaskId = otherTask.Id,
Code = "FOREIGN-LAB",
Name = "不可见实验",
ArrangementMode = ExperimentArrangementMode.Centralized,
StartDate = fixture.Term.StartDate,
EndDate = fixture.Term.EndDate,
Status = ExperimentProjectStatus.Published,
PublishedAt = DateTime.UtcNow
};
fixture.Db.AddRange(otherCollege, otherCourse, otherTask, otherProject);
await fixture.Db.SaveChangesAsync();
fixture.Db.ChangeTracker.Clear();
var collegeScope = new FixedScope(new CurrentUserScope(
Guid.NewGuid(),
"学院管理员",
collegeId,
DataScope.College,
new HashSet<string>([SystemRoles.CollegeAdmin])));
var controller = fixture.ExperimentGrades(collegeScope);
var page = Assert.IsType<OkObjectResult>(await controller.GetManagement(
null,
null,
null,
null,
1,
10,
CancellationToken.None));
Assert.Equal(12, Property<int>(page.Value, "Total"));
Assert.Equal(10, Property<System.Collections.IEnumerable>(
page.Value,
"Items").Cast<object>().Count());
var filtered = Assert.IsType<OkObjectResult>(await controller.GetManagement(
null,
null,
null,
"LAB-12",
1,
10,
CancellationToken.None));
Assert.Equal(1, Property<int>(filtered.Value, "Total"));
}
private static T Property<T>(object? value, string name) =>
Assert.IsAssignableFrom<T>(
value!.GetType().GetProperty(name)!.GetValue(value));
[Fact]
public async Task IndependentExperimentGrade_CanPublishAndImportAsCourseSnapshot()
{
@@ -260,6 +414,7 @@ public sealed class ExperimentGradesControllerTests
Student student,
Classroom classroom,
ICurrentUserDataScope adminScope,
ICurrentUserDataScope teacherScope,
ICurrentUserDataScope studentScope)
{
Connection = connection;
@@ -269,6 +424,7 @@ public sealed class ExperimentGradesControllerTests
Student = student;
Classroom = classroom;
AdminScope = adminScope;
TeacherScope = teacherScope;
StudentScope = studentScope;
}
@@ -279,6 +435,7 @@ public sealed class ExperimentGradesControllerTests
public Student Student { get; }
public Classroom Classroom { get; }
public ICurrentUserDataScope AdminScope { get; }
public ICurrentUserDataScope TeacherScope { get; }
public ICurrentUserDataScope StudentScope { get; }
public static async Task<ExperimentGradeFixture> CreateAsync()
@@ -410,6 +567,7 @@ public sealed class ExperimentGradesControllerTests
student,
classroom,
Scope(admin, SystemRoles.SuperAdmin, DataScope.All),
Scope(teacherUser, SystemRoles.Teacher, DataScope.Self),
Scope(studentUser, SystemRoles.Student, DataScope.Self));
}