实验课表
This commit is contained in:
@@ -138,6 +138,107 @@ public sealed class ExperimentsControllerTests
|
||||
Assert.Empty(fixture.Db.ExperimentBookings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StudentTimetable_IncludesCentralizedAndBookedSelfScheduledSessions()
|
||||
{
|
||||
await using var fixture = await ExperimentFixture.CreateAsync();
|
||||
var manager = fixture.Controller(fixture.ManagerScope);
|
||||
|
||||
await manager.CreateProject(
|
||||
fixture.ProjectRequest(ExperimentArrangementMode.Centralized),
|
||||
CancellationToken.None);
|
||||
var centralized = await fixture.Db.ExperimentProjects.SingleAsync();
|
||||
await manager.CreateSession(
|
||||
centralized.Id,
|
||||
fixture.SessionRequest(1, 2),
|
||||
CancellationToken.None);
|
||||
await manager.PublishProject(centralized.Id, CancellationToken.None);
|
||||
|
||||
await manager.CreateProject(
|
||||
fixture.ProjectRequest(ExperimentArrangementMode.SelfScheduled),
|
||||
CancellationToken.None);
|
||||
var selfScheduled = await fixture.Db.ExperimentProjects.SingleAsync(
|
||||
x => x.ArrangementMode == ExperimentArrangementMode.SelfScheduled);
|
||||
await manager.CreateSession(
|
||||
selfScheduled.Id,
|
||||
fixture.SessionRequest(4, 2),
|
||||
CancellationToken.None);
|
||||
await manager.PublishProject(selfScheduled.Id, CancellationToken.None);
|
||||
|
||||
var selfSession = await fixture.Db.ExperimentSessions.SingleAsync(
|
||||
x => x.ExperimentProjectId == selfScheduled.Id);
|
||||
var studentController = fixture.Controller(fixture.StudentScope);
|
||||
Assert.IsType<NoContentResult>(
|
||||
await studentController.Book(
|
||||
selfSession.Id,
|
||||
CancellationToken.None));
|
||||
|
||||
var student = await fixture.Db.Students.AsNoTracking().SingleAsync();
|
||||
var service = new TimetableDataService(fixture.Db);
|
||||
var timetable = await service.BuildAsync(
|
||||
TimetableResourceType.Class,
|
||||
student.AdministrativeClassId,
|
||||
fixture.Term.Id,
|
||||
null,
|
||||
false,
|
||||
student.Id,
|
||||
new TimetableStudentDto(student.StudentNumber, student.Name),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.NotNull(timetable);
|
||||
Assert.Equal(2, timetable.ExperimentEntries.Count);
|
||||
Assert.Contains(
|
||||
timetable.ExperimentEntries,
|
||||
x => x.ExperimentArrangementMode ==
|
||||
ExperimentArrangementMode.Centralized);
|
||||
Assert.Contains(
|
||||
timetable.ExperimentEntries,
|
||||
x => x.Id == selfSession.Id &&
|
||||
x.ExperimentArrangementMode ==
|
||||
ExperimentArrangementMode.SelfScheduled);
|
||||
Assert.All(
|
||||
timetable.ExperimentEntries,
|
||||
x =>
|
||||
{
|
||||
Assert.True(x.IsExperiment);
|
||||
Assert.Equal(fixture.Term.StartDate, x.ExperimentDate);
|
||||
Assert.Equal(1, x.StartWeek);
|
||||
});
|
||||
|
||||
var publicClassTimetable = await service.BuildAsync(
|
||||
TimetableResourceType.Class,
|
||||
student.AdministrativeClassId,
|
||||
fixture.Term.Id,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
CancellationToken.None);
|
||||
Assert.NotNull(publicClassTimetable);
|
||||
Assert.Empty(publicClassTimetable.ExperimentEntries);
|
||||
|
||||
var booking = await fixture.Db.ExperimentBookings.SingleAsync();
|
||||
Assert.IsType<NoContentResult>(
|
||||
await studentController.CancelBooking(
|
||||
booking.Id,
|
||||
CancellationToken.None));
|
||||
timetable = await service.BuildAsync(
|
||||
TimetableResourceType.Class,
|
||||
student.AdministrativeClassId,
|
||||
fixture.Term.Id,
|
||||
null,
|
||||
false,
|
||||
student.Id,
|
||||
new TimetableStudentDto(student.StudentNumber, student.Name),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.NotNull(timetable);
|
||||
Assert.Single(timetable.ExperimentEntries);
|
||||
Assert.Equal(
|
||||
ExperimentArrangementMode.Centralized,
|
||||
timetable.ExperimentEntries[0].ExperimentArrangementMode);
|
||||
}
|
||||
|
||||
private sealed class ExperimentFixture : IAsyncDisposable
|
||||
{
|
||||
private ExperimentFixture(
|
||||
|
||||
@@ -251,6 +251,198 @@ public sealed class PersonalCalendarServiceTests : IAsyncLifetime
|
||||
$"ICS line exceeds 75 octets: {line}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Build_creates_student_feed_with_booked_experiment()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var mondayOffset = today.DayOfWeek == DayOfWeek.Sunday
|
||||
? 6
|
||||
: (int)today.DayOfWeek - 1;
|
||||
var termStart = today.AddDays(-mondayOffset);
|
||||
var user = new ApplicationUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserName = "calendar-student",
|
||||
NormalizedUserName = "CALENDAR-STUDENT",
|
||||
DisplayName = "实验学生",
|
||||
CalendarSubscriptionStamp = PersonalCalendarService.CreateStamp(),
|
||||
CalendarSubscriptionCreatedAt = DateTime.UtcNow
|
||||
};
|
||||
var campus = new Campus { Code = "LAB-MAIN", Name = "主校区" };
|
||||
var college = new College
|
||||
{
|
||||
Code = "LAB-CS",
|
||||
Name = "计算机学院",
|
||||
CampusId = campus.Id
|
||||
};
|
||||
var major = new Major
|
||||
{
|
||||
Code = "LAB-SE",
|
||||
Name = "软件工程",
|
||||
CollegeId = college.Id,
|
||||
DegreeType = "工学"
|
||||
};
|
||||
var administrativeClass = new AdministrativeClass
|
||||
{
|
||||
Code = "LAB-CLASS",
|
||||
Name = "软件工程实验班",
|
||||
MajorId = major.Id,
|
||||
Grade = today.Year
|
||||
};
|
||||
var student = new Student
|
||||
{
|
||||
StudentNumber = "LAB-STUDENT-01",
|
||||
Name = "实验学生",
|
||||
AdministrativeClassId = administrativeClass.Id,
|
||||
EnrollmentYear = today.Year,
|
||||
EnrollmentDate = termStart,
|
||||
UserId = user.Id,
|
||||
Status = StudentStatus.Active
|
||||
};
|
||||
var teacher = new Teacher
|
||||
{
|
||||
TeacherNumber = "LAB-TEACHER-01",
|
||||
Name = "实验教师",
|
||||
CollegeId = college.Id,
|
||||
Status = TeacherStatus.Active
|
||||
};
|
||||
var course = new Course
|
||||
{
|
||||
Code = "LAB-CALENDAR",
|
||||
Name = "嵌入式系统实验",
|
||||
CollegeId = college.Id,
|
||||
Credits = 2,
|
||||
TotalHours = 32,
|
||||
LectureHours = 16,
|
||||
PracticeHours = 16,
|
||||
Nature = CourseNature.Practice,
|
||||
AssessmentMethod = AssessmentMethod.Assessment
|
||||
};
|
||||
var term = new AcademicTerm
|
||||
{
|
||||
Code = "LAB-CALENDAR-TERM",
|
||||
Name = "实验日历测试学期",
|
||||
AcademicYear = $"{today.Year}-{today.Year + 1}",
|
||||
Season = TermSeason.Autumn,
|
||||
StartDate = termStart,
|
||||
EndDate = termStart.AddDays(40),
|
||||
IsCurrent = true
|
||||
};
|
||||
var building = new Building
|
||||
{
|
||||
Code = "LAB-CENTER",
|
||||
Name = "实验中心",
|
||||
CampusId = campus.Id
|
||||
};
|
||||
var classroom = new Classroom
|
||||
{
|
||||
Code = "LAB-CENTER-301",
|
||||
Name = "301",
|
||||
BuildingId = building.Id,
|
||||
Capacity = 30
|
||||
};
|
||||
_db.AddRange(
|
||||
user,
|
||||
campus,
|
||||
college,
|
||||
major,
|
||||
administrativeClass,
|
||||
student,
|
||||
teacher,
|
||||
course,
|
||||
term,
|
||||
building,
|
||||
classroom);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
var task = new TeachingTask
|
||||
{
|
||||
TaskNumber = "LAB-CALENDAR-TASK",
|
||||
Name = "嵌入式系统实验教学班",
|
||||
AcademicTermId = term.Id,
|
||||
CourseId = course.Id,
|
||||
Capacity = 30,
|
||||
Status = TeachingTaskStatus.Published,
|
||||
Teachers =
|
||||
[
|
||||
new TeachingTaskTeacher
|
||||
{
|
||||
TeacherId = teacher.Id,
|
||||
IsPrimary = true
|
||||
}
|
||||
],
|
||||
Classes =
|
||||
[
|
||||
new TeachingTaskClass
|
||||
{
|
||||
AdministrativeClassId = administrativeClass.Id
|
||||
}
|
||||
]
|
||||
};
|
||||
_db.AddRange(
|
||||
task,
|
||||
new ScheduleTimeSlot
|
||||
{
|
||||
AcademicTermId = term.Id,
|
||||
PeriodNumber = 3,
|
||||
Name = "第三节",
|
||||
StartsAt = new TimeOnly(10, 0),
|
||||
EndsAt = new TimeOnly(10, 45)
|
||||
},
|
||||
new ScheduleTimeSlot
|
||||
{
|
||||
AcademicTermId = term.Id,
|
||||
PeriodNumber = 4,
|
||||
Name = "第四节",
|
||||
StartsAt = new TimeOnly(10, 55),
|
||||
EndsAt = new TimeOnly(11, 40)
|
||||
});
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
var project = new ExperimentProject
|
||||
{
|
||||
TeachingTaskId = task.Id,
|
||||
Code = "LAB-SELF-01",
|
||||
Name = "自主硬件实验",
|
||||
ArrangementMode = ExperimentArrangementMode.SelfScheduled,
|
||||
StartDate = termStart,
|
||||
EndDate = termStart.AddDays(14),
|
||||
Status = ExperimentProjectStatus.Published,
|
||||
PublishedAt = DateTime.UtcNow
|
||||
};
|
||||
var session = new ExperimentSession
|
||||
{
|
||||
ExperimentProject = project,
|
||||
ClassroomId = classroom.Id,
|
||||
SessionDate = termStart.AddDays(2),
|
||||
StartPeriod = 3,
|
||||
PeriodCount = 2,
|
||||
Capacity = 30,
|
||||
ReservedCount = 1
|
||||
};
|
||||
session.Bookings.Add(new ExperimentBooking
|
||||
{
|
||||
ExperimentProject = project,
|
||||
StudentId = student.Id,
|
||||
Status = ExperimentBookingStatus.Booked,
|
||||
BookedAt = DateTime.UtcNow
|
||||
});
|
||||
_db.Add(session);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
var result = await CreateService().BuildAsync(
|
||||
user,
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(1, result.EventCount);
|
||||
var ics = Encoding.UTF8.GetString(result.Content);
|
||||
Assert.Contains("SUMMARY:[实验] 自主硬件实验", ics);
|
||||
Assert.Contains("CATEGORIES:实验", ics);
|
||||
Assert.Contains("LOCATION:主校区 · 实验中心 · 301", ics);
|
||||
Assert.Contains("LAB-SELF-01 · 自主预约", ics);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Access_token_is_bound_to_user_stamp_and_can_be_rotated()
|
||||
{
|
||||
|
||||
@@ -108,7 +108,41 @@ public sealed class TimetableExcelExporterTests
|
||||
"2026-2027 学年第一学期期末考试",
|
||||
new DateOnly(2027, 1, 5),
|
||||
DateTime.UtcNow)
|
||||
]);
|
||||
])
|
||||
{
|
||||
ExperimentEntries =
|
||||
[
|
||||
new TimetableEntryDto(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid(),
|
||||
"TASK-LAB",
|
||||
"程序设计实验教学班",
|
||||
"CSLAB101",
|
||||
"程序设计实验",
|
||||
["陈老师"],
|
||||
["计算机科学 2601 班"],
|
||||
"301",
|
||||
"实验中心",
|
||||
"主校区",
|
||||
4,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
WeekPattern.All,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
DateTime.UtcNow,
|
||||
true,
|
||||
Guid.NewGuid(),
|
||||
"LAB-01",
|
||||
"基础控制实验",
|
||||
new DateOnly(2026, 9, 10),
|
||||
ExperimentArrangementMode.SelfScheduled)
|
||||
]
|
||||
};
|
||||
|
||||
var bytes = TimetableExcelExporter.Create(timetable);
|
||||
|
||||
@@ -117,8 +151,11 @@ public sealed class TimetableExcelExporterTests
|
||||
var sheet = workbook.Worksheet("课表");
|
||||
Assert.Contains("计算机科学 2601 班", sheet.Cell("A1").GetString());
|
||||
Assert.Contains("已融入 1 场考试", sheet.Cell("A2").GetString());
|
||||
Assert.Contains("已融入 1 场实验", sheet.Cell("A2").GetString());
|
||||
Assert.Contains("高等数学", sheet.Cell(5, 2).GetString());
|
||||
Assert.Contains("【考试】高等数学", sheet.Cell(5, 3).GetString());
|
||||
Assert.Contains("【实验】基础控制实验", sheet.Cell(5, 5).GetString());
|
||||
Assert.Contains("自主预约", sheet.Cell(5, 5).GetString());
|
||||
Assert.Contains("2027-01-05", sheet.Cell(5, 3).GetString());
|
||||
Assert.Contains(
|
||||
sheet.CellsUsed(),
|
||||
|
||||
Reference in New Issue
Block a user