using Jiaowu.Api.Controllers; using Jiaowu.Api.Contracts; 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 OtherExamsControllerTests { [Fact] public async Task Batches_AreFilteredAndReturnedByPage() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); await using var db = new AppDbContext( new DbContextOptionsBuilder().UseSqlite(connection).Options); await db.Database.EnsureCreatedAsync(); db.OtherExamBatches.AddRange(Enumerable.Range(1, 11).Select(number => new OtherExamBatch { ExamCode = $"CET-{number:00}", Name = $"英语等级考试 {number}", ExamDate = new DateOnly(2026, 6, number), MetricKind = OtherExamMetricKind.Score, MaxScore = 100, Status = number == 11 ? OtherExamBatchStatus.Published : OtherExamBatchStatus.Draft })); await db.SaveChangesAsync(); var controller = new OtherExamsController(db, new TestDataScope(Guid.NewGuid())); var result = Assert.IsType(await controller.GetBatches( keyword: "英语", status: OtherExamBatchStatus.Draft, page: 1, pageSize: 10, ct: CancellationToken.None)); var page = Assert.IsType>(result.Value); Assert.Equal(10, page.Total); Assert.Equal(10, page.Items.Count); } [Fact] public async Task ReplaceResults_ReplacesExistingResultsInsideTransaction() { 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 college = new College { Code = "CS", Name = "计算机学院" }; var major = new Major { Code = "SE", Name = "软件工程", CollegeId = college.Id, DegreeType = "工学" }; var administrativeClass = new AdministrativeClass { Code = "SE202601", Name = "软件工程 2026 级 1 班", MajorId = major.Id, Grade = 2026 }; var student = new Student { StudentNumber = "202600001", Name = "测试学生", AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1), Status = StudentStatus.Active }; var batch = new OtherExamBatch { ExamCode = "CET4", Name = "大学英语四级", ExamDate = new DateOnly(2026, 6, 1), MetricKind = OtherExamMetricKind.Score, MaxScore = 710 }; var oldResult = new OtherExamResult { OtherExamBatchId = batch.Id, StudentId = student.Id, Score = 500, AttemptNumber = 1 }; db.AddRange(college, major, administrativeClass, student, batch, oldResult); await db.SaveChangesAsync(); var controller = new OtherExamsController(db, new TestDataScope(college.Id)); var result = await controller.ReplaceResults(batch.Id, new ReplaceOtherExamResultsRequest([ new OtherExamResultRequest(student.StudentNumber, 610.5m, null, null, "已复核") ]), CancellationToken.None); Assert.IsType(result); db.ChangeTracker.Clear(); var saved = await db.OtherExamResults.SingleAsync(x => x.OtherExamBatchId == batch.Id); Assert.Equal(610.5m, saved.Score); Assert.Equal("已复核", saved.Notes); Assert.Equal(1, saved.AttemptNumber); } [Fact] public async Task ReplaceResults_RejectsOverlongNotesBeforeDatabaseSave() { 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 college = new College { Code = "CS", Name = "计算机学院" }; var major = new Major { Code = "SE", Name = "软件工程", CollegeId = college.Id, DegreeType = "工学" }; var administrativeClass = new AdministrativeClass { Code = "SE202601", Name = "软件工程 2026 级 1 班", MajorId = major.Id, Grade = 2026 }; var student = new Student { StudentNumber = "202600001", Name = "测试学生", AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1), Status = StudentStatus.Active }; var batch = new OtherExamBatch { ExamCode = "CET4", Name = "大学英语四级", ExamDate = new DateOnly(2026, 6, 1), MetricKind = OtherExamMetricKind.Score, MaxScore = 710 }; db.AddRange(college, major, administrativeClass, student, batch); await db.SaveChangesAsync(); var controller = new OtherExamsController(db, new TestDataScope(college.Id)); var result = await controller.ReplaceResults(batch.Id, new ReplaceOtherExamResultsRequest([ new OtherExamResultRequest(student.StudentNumber, 610, null, null, new string('注', 501)) ]), CancellationToken.None); var validation = Assert.IsType(result); Assert.Contains("备注不能超过 500 个字符", Assert.IsType(validation.Value).Detail); Assert.Empty(await db.OtherExamResults.ToListAsync()); } private sealed class TestDataScope(Guid collegeId) : ICurrentUserDataScope { public CurrentUserScope Current { get; } = new( Guid.NewGuid(), "测试管理员", collegeId, DataScope.College, new HashSet([SystemRoles.CollegeAdmin])); } }