修复500

TimeOnly ↔ TimeSpan
This commit is contained in:
2026-07-26 12:56:59 +08:00 Unverified
parent 0c3745d91a
commit 32eb723c02
3 changed files with 215 additions and 0 deletions
@@ -4,6 +4,9 @@ using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Collections;
using System.Text.Encodings.Web;
using System.Text.Json;
namespace Jiaowu.Api.Tests;
@@ -102,6 +105,206 @@ public sealed class ScheduleSettingsControllerTests
});
}
[Fact]
public async Task Get_constraints_materializes_teachers_and_allowed_classrooms()
{
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 college = new College { Code = "CS", Name = "计算机学院" };
var course = new Course
{
Code = "CS101",
Name = "程序设计基础",
College = college,
Credits = 4,
TotalHours = 64,
LectureHours = 48,
PracticeHours = 16
};
var term = new AcademicTerm
{
Code = "2026-F",
Name = "2026 秋季",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 7),
EndDate = new DateOnly(2027, 1, 17)
};
var teacher = new Teacher
{
TeacherNumber = "T001",
Name = "测试教师",
College = college
};
var classroom = new Classroom
{
Code = "J1-201",
Name = "J1-201",
Capacity = 80,
Building = new Building
{
Code = "J1",
Name = "第一教学楼",
Campus = new Campus { Code = "MAIN", Name = "主校区" }
}
};
var task = NewTask("TASK-01", course, term);
task.Teachers.Add(new TeachingTaskTeacher
{
Teacher = teacher,
IsPrimary = true
});
var constraint = new TeachingTaskScheduleConstraint
{
TeachingTask = task,
AllowedDayOfWeeks = "1,3,5",
EarliestPeriod = 1,
LatestPeriod = 8,
AllowedClassrooms =
[
new TeachingTaskAllowedClassroom { Classroom = classroom }
]
};
db.AddRange(college, course, term, teacher, classroom, task, constraint);
await db.SaveChangesAsync();
var result = await new ScheduleSettingsController(db)
.GetConstraints(term.Id, CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(result);
var payload = Assert.IsAssignableFrom<IEnumerable>(ok.Value);
Assert.Single(payload.Cast<object>());
var json = JsonSerializer.Serialize(
ok.Value,
new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
Assert.Contains("测试教师", json);
Assert.Contains(classroom.Id.ToString(), json);
}
[Fact]
public async Task MySql_schedule_settings_materialize_time_slots_and_constraints()
{
var connectionString =
Environment.GetEnvironmentVariable("JIAOWU_TEST_MYSQL_CONNECTION");
if (string.IsNullOrWhiteSpace(connectionString))
return;
var previousConnectionString =
Environment.GetEnvironmentVariable("ConnectionStrings__MySql");
AppDbContext db;
try
{
Environment.SetEnvironmentVariable(
"ConnectionStrings__MySql",
connectionString);
db = new AppDbContextFactory().CreateDbContext([]);
}
finally
{
Environment.SetEnvironmentVariable(
"ConnectionStrings__MySql",
previousConnectionString);
}
await using var ownedDb = db;
var term = await db.AcademicTerms.FirstAsync();
var course = await db.Courses.FirstAsync();
var teacher = await db.Teachers.FirstAsync(
item => item.CollegeId == course.CollegeId);
var classroom = await db.Classrooms.FirstAsync();
await using var transaction = await db.Database.BeginTransactionAsync();
var periodNumber = await db.ScheduleTimeSlots
.Where(item => item.AcademicTermId == term.Id)
.Select(item => (int?)item.PeriodNumber)
.MaxAsync() ?? 0;
var task = new TeachingTask
{
TaskNumber = $"MYSQL-{Guid.NewGuid():N}"[..22],
Name = "MySQL 排课约束回归",
AcademicTermId = term.Id,
CourseId = course.Id,
Capacity = 60,
WeeklyHours = 2,
StartWeek = 1,
EndWeek = 16,
Status = TeachingTaskStatus.Published,
Teachers =
[
new TeachingTaskTeacher
{
TeacherId = teacher.Id,
IsPrimary = true
}
]
};
db.Add(task);
db.Add(new ScheduleTimeSlot
{
AcademicTermId = term.Id,
PeriodNumber = periodNumber + 1,
Name = "MySQL 回归节次",
StartsAt = new TimeOnly(8, 0),
EndsAt = new TimeOnly(8, 45)
});
db.Add(new TeachingTaskScheduleConstraint
{
TeachingTask = task,
AllowedDayOfWeeks = "1,3,5",
EarliestPeriod = 1,
LatestPeriod = 8,
AllowedClassrooms =
[
new TeachingTaskAllowedClassroom { ClassroomId = classroom.Id }
]
});
await db.SaveChangesAsync();
db.ChangeTracker.Clear();
var result = await new ScheduleSettingsController(db)
.GetConstraints(term.Id, CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(result);
var payload = Assert.IsAssignableFrom<IEnumerable>(ok.Value);
Assert.Contains(payload.Cast<object>(), item =>
JsonSerializer.Serialize(item).Contains(task.TaskNumber));
var timeSlotsResult = await new ScheduleSettingsController(db)
.GetTimeSlots(term.Id, CancellationToken.None);
var timeSlotsOk = Assert.IsType<OkObjectResult>(timeSlotsResult);
var timeSlotsJson = JsonSerializer.Serialize(timeSlotsOk.Value);
Assert.Contains("08:00", timeSlotsJson);
var examTimeSlotsResult = await new ExamsController(db, null!, null!)
.GetTimeSlotsForTerm(term.Id, CancellationToken.None);
var examTimeSlotsOk =
Assert.IsType<OkObjectResult>(examTimeSlotsResult);
Assert.Contains(
"08:00",
JsonSerializer.Serialize(examTimeSlotsOk.Value));
var makeupTimeSlotsResult = await new MakeupExamsController(
db,
null!,
null!,
null!,
null!)
.GetTimeSlotsForTerm(term.Id, CancellationToken.None);
var makeupTimeSlotsOk =
Assert.IsType<OkObjectResult>(makeupTimeSlotsResult);
Assert.Contains(
"08:00",
JsonSerializer.Serialize(makeupTimeSlotsOk.Value));
await transaction.RollbackAsync();
}
private static TeachingTask NewTask(
string taskNumber,
Course course,