using ClosedXML.Excel; 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 AttendanceControllerTests { [Fact] public async Task TaskStatistics_UsesSubmittedSheetsAndExportsAllDetails() { 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 = "080901", Name = "计算机科学与技术", CollegeId = college.Id, DegreeType = "工学学士" }; var administrativeClass = new AdministrativeClass { Code = "CS2026-01", Name = "计科 2026-1 班", MajorId = major.Id, Grade = 2026 }; var firstStudent = new Student { StudentNumber = "202601001", Name = "周同学", AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1) }; var secondStudent = new Student { StudentNumber = "202601002", Name = "吴同学", AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1) }; var course = new Course { Code = "CS101", Name = "程序设计基础", CollegeId = college.Id, Credits = 4, TotalHours = 64, LectureHours = 48, PracticeHours = 16, Nature = CourseNature.MajorRequired, AssessmentMethod = AssessmentMethod.Examination }; var term = new AcademicTerm { Code = "2026-1", Name = "2026—2027 学年第一学期", AcademicYear = "2026-2027", Season = TermSeason.Autumn, StartDate = new DateOnly(2026, 9, 1), EndDate = new DateOnly(2027, 1, 20) }; var task = new TeachingTask { TaskNumber = "2026-1-CS101-01", Name = "程序设计基础教学班", AcademicTermId = term.Id, CourseId = course.Id, Capacity = 60, Status = TeachingTaskStatus.Published }; db.AddRange( college, major, administrativeClass, firstStudent, secondStudent, course, term, task); await db.SaveChangesAsync(); db.AttendanceSheets.AddRange( new AttendanceSheet { TeachingTaskId = task.Id, Name = "第1周点名", AttendanceDate = new DateTime(2026, 9, 3), Status = AttendanceSheetStatus.Submitted, Records = [ new AttendanceRecord { StudentId = firstStudent.Id, Status = AttendanceStatus.Present }, new AttendanceRecord { StudentId = secondStudent.Id, Status = AttendanceStatus.Absent } ] }, new AttendanceSheet { TeachingTaskId = task.Id, Name = "第2周点名", AttendanceDate = new DateTime(2026, 9, 10), Status = AttendanceSheetStatus.Submitted, Records = [ new AttendanceRecord { StudentId = firstStudent.Id, Status = AttendanceStatus.Late }, new AttendanceRecord { StudentId = secondStudent.Id, Status = AttendanceStatus.Excused } ] }, new AttendanceSheet { TeachingTaskId = task.Id, Name = "未提交点名", AttendanceDate = new DateTime(2026, 9, 17), Status = AttendanceSheetStatus.Draft, Records = [ new AttendanceRecord { StudentId = firstStudent.Id, Status = AttendanceStatus.Absent } ] }); await db.SaveChangesAsync(); var controller = new AttendanceController(db, new AllDataScope()); var result = await controller.GetTaskStatistics( task.Id, CancellationToken.None); var ok = Assert.IsType(result); var statistics = Assert.IsType(ok.Value); Assert.Equal(2, statistics.Summary.SubmittedSheetCount); Assert.Equal(4, statistics.Summary.RecordCount); Assert.Equal(3, statistics.Summary.RequiredCount); Assert.Equal(66.7m, statistics.Summary.OverallAttendanceRate); Assert.Equal(0, statistics.Summary.PerfectAttendanceCount); Assert.Equal(2, statistics.Students.Count); Assert.Equal(0m, statistics.Students[0].AttendanceRate); Assert.Equal(100m, statistics.Students[1].AttendanceRate); var exportResult = await controller.ExportTaskStatistics( task.Id, CancellationToken.None); var file = Assert.IsType(exportResult); using var workbook = new XLWorkbook(new MemoryStream(file.FileContents)); Assert.NotNull(workbook.Worksheet("课程概览")); Assert.Equal( 3, workbook.Worksheet("学生出勤明细").LastRowUsed()!.RowNumber()); Assert.Equal( 3, workbook.Worksheet("历次点名趋势").LastRowUsed()!.RowNumber()); } private sealed class AllDataScope : ICurrentUserDataScope { public CurrentUserScope Current { get; } = new( Guid.NewGuid(), "测试管理员", null, DataScope.All, new HashSet([SystemRoles.SuperAdmin])); } }