using DocumentFormat.OpenXml.Packaging; using Jiaowu.Api.Controllers; using Jiaowu.Api.Infrastructure.Grades; using Jiaowu.Api.Infrastructure.Pdf; using SkiaSharp; namespace Jiaowu.Api.Tests; public sealed class GradeAnalysisWordReportGeneratorTests { [Fact] public void ChineseTypefaceResolver_ProvidesChineseGlyphsForReportCharts() { using var typeface = PdfTypefaceResolver.ResolveChineseTypeface(null); using var font = new SKFont(typeface, 20); Assert.True(font.ContainsGlyphs("人数平均分合格率")); } [Fact] public void Generate_CreatesReadableDocxWithAnalysisSectionsAndCharts() { var now = new DateTime(2026, 8, 9, 10, 30, 0, DateTimeKind.Local); var summary = new GradeAnalyticsController.TeachingClassMetrics( 10, 9, 2, 98m, 78.5m, 80m, 52m, 12.34m, 90m, 20m, now, [ new("0–59", 0, 60, 1), new("60–69", 60, 70, 2), new("70–79", 70, 80, 2), new("80–89", 80, 90, 3), new("90–100", 90, null, 2) ]); var report = new GradeAnalyticsController.TeachingClassAnalysisReport( false, Guid.NewGuid(), Guid.NewGuid(), "TASK-01", "计算机一班", "CS101", "程序设计", "2026-2027 学年第一学期", summary, [new(Guid.NewGuid(), Guid.NewGuid(), "TASK-01", "计算机一班", "张老师", "计科一班", 10, 98m, 78.5m, 80m, 52m, 12.34m, 90m, 20m, true)], [new("全校", "全校同课程", 100, 100m, 76m, 45m, 88m)], [new(Guid.NewGuid(), "2026-2027 学年第一学期", 100, 76m, 88m, 18m, new(10, 78.5m, 90m, 20m))], new(2.5m, 2m, 76m, 88m)); var bytes = GradeAnalysisWordReportGenerator.Generate(report, now, "测试校园综合服务平台"); Assert.True(bytes.Length > 10_000); using var stream = new MemoryStream(bytes); using var document = WordprocessingDocument.Open(stream, false); var mainDocumentPart = document.MainDocumentPart ?? throw new InvalidOperationException("Generated document has no main document part."); var text = mainDocumentPart.Document?.InnerText ?? throw new InvalidOperationException("Generated document has no document body."); Assert.Contains("成绩分析报告", text); Assert.Contains("同课程教学班对比", text); Assert.Contains("各范围基准", text); Assert.Contains("历年成绩趋势", text); Assert.Contains("行政班组成", text); Assert.Contains("计科一班", text); Assert.Equal(3, mainDocumentPart.ImageParts.Count()); Assert.Equal(2, mainDocumentPart.HeaderParts.Count()); Assert.Equal(2, mainDocumentPart.FooterParts.Count()); } [Fact] public void Generate_CourseOverallReport_CreatesReadableDocx() { var now = new DateTime(2026, 8, 21, 10, 30, 0, DateTimeKind.Local); var summary = new GradeAnalyticsController.CourseOverview( Guid.NewGuid(), "CS101", "程序设计", Guid.NewGuid(), "计算机学院", 2, 3, 30, 26, 8, 99m, 78.5m, 52m, 86.67m, 26.67m, now); var report = new GradeAnalyticsController.CourseOverallAnalysisReport( summary, [new("0–59", 0, 60, 4), new("60–69", 60, 70, 6), new("70–79", 70, 80, 8), new("80–89", 80, 90, 7), new("90–100", 90, null, 5)], [new(Guid.NewGuid(), "2025-2026 学年第二学期", 1, 12, 76m, 83.33m, 25m), new(Guid.NewGuid(), "2026-2027 学年第一学期", 2, 18, 80.2m, 88.89m, 27.78m)], [new("计算机学院", 18, 80m, 88.89m, 27.78m), new("电子信息学院", 12, 76.3m, 83.33m, 25m)], [new(Guid.NewGuid(), Guid.NewGuid(), "2026-2027 学年第一学期", "TASK-01", "程序设计一班", "张老师", 18, 99m, 80.2m, 82m, 52m, 11.2m, 88.89m, 27.78m)]); var bytes = GradeAnalysisWordReportGenerator.Generate(report, now, "测试校园综合服务平台"); Assert.True(bytes.Length > 8_000); using var stream = new MemoryStream(bytes); using var document = WordprocessingDocument.Open(stream, false); var mainDocumentPart = document.MainDocumentPart ?? throw new InvalidOperationException("Generated document has no main document part."); var text = mainDocumentPart.Document?.InnerText ?? throw new InvalidOperationException("Generated document has no document body."); Assert.Contains("科目总体成绩分析报告", text); Assert.Contains("学生来源学院对比", text); Assert.Contains("教学班横向比较", text); Assert.Single(mainDocumentPart.ImageParts); } }