mi-assessment/.kiro/specs/report-web-pages/design.md
2026-02-25 17:32:05 +08:00

26 KiB
Raw Blame History

设计文档测评报告网页版PDF报告生成用

概述

测评报告网页版是学业邑规划测评系统的服务端渲染模块,集成在 MiAssessment.Api 项目中,使用 ASP.NET Core Razor Pages 渲染报告 HTML 页面。该模块为后端截图服务提供固定尺寸1309×926px的报告网页截图后与静态图片按配置顺序拼装为 PDF 报告文件。

核心职责

  1. 结论数据管理:报告生成时从 report_conclusions 模板表复制结论到 assessment_record_conclusions 记录级别表,支持管理员针对单条记录手动调整
  2. 报告数据服务:提供内部服务方法,一次性查询指定测评记录的完整报告数据
  3. 服务端渲染:通过 Razor Pages 将报告数据嵌入 HTML 输出,每个报告板块对应独立 URL
  4. 页面配置:通过 report_page_configs 表定义 PDF 报告的页面组成和顺序

设计决策

  • 集成到 MiAssessment.Api:报告页面面向外部访问(截图服务通过 HTTP 访问),与小程序 API 共用同一个 Web 项目,复用已有的数据库连接和 DI 配置
  • 使用 Razor Pages 而非 MVC ViewsRazor Pages 是 ASP.NET Core 推荐的页面渲染方式,每个页面自包含(.cshtml + PageModel适合报告这种独立页面场景
  • ECharts CDN 引入:图表库通过 CDN <script> 标签引入,避免增加后端项目的静态资源体积
  • 结论数据独立副本:每条测评记录拥有独立的结论数据,管理员调整不影响模板和其他记录,支持"重新生成"恢复到模板数据
  • 新增实体放在 MiAssessment.ModelAssessmentRecordConclusionReportPageConfig 实体在 Model 项目中定义,供 Api 和 Admin.Business 共用
  • 先搭框架后做页面:本设计聚焦框架层面(数据表、服务、路由、公共模板),具体每个页面的样式布局后续逐个实现

架构

整体数据流

sequenceDiagram
    participant RGS as ReportGenerationService
    participant DB as Business 数据库
    participant RDS as ReportDataService
    participant RP as Razor Pages
    participant SS as 截图服务(外部)

    Note over RGS,DB: 报告生成阶段
    RGS->>DB: 计算得分/排名/星级 → 写入 assessment_results
    RGS->>DB: 从 report_conclusions 复制 → 写入 assessment_record_conclusions
    RGS->>DB: 更新 assessment_records.Status = 4

    Note over SS,RP: 截图阶段
    SS->>DB: 读取 report_page_configs按 SortOrder 排序)
    loop 每个 PageType=2 的页面
        SS->>RP: GET /report/{page-name}?recordId={id}
        RP->>RDS: GetReportDataAsync(recordId)
        RDS->>DB: 查询 assessment_records + assessment_results + assessment_record_conclusions
        RDS-->>RP: ReportDataDto
        RP-->>SS: 渲染完成的 HTML含 data-render-complete 标记)
        SS->>SS: 截图 1309×926px
    end
    SS->>SS: 按 SortOrder 拼装静态图片 + 截图 → PDF

项目集成方案

MiAssessment.Model/
├── Entities/
│   ├── AssessmentRecordConclusion.cs   ← 新增实体
│   └── ReportPageConfig.cs             ← 新增实体
└── Data/
    └── MiAssessmentDbContext.cs         ← 新增 DbSet

MiAssessment.Core/
└── Services/
    ├── ReportGenerationService.cs       ← 修改:生成报告时复制结论数据
    └── ReportDataService.cs             ← 新增:报告数据查询服务

MiAssessment.Api/
├── Pages/                               ← 新增 Razor Pages 目录
│   └── Report/
│       ├── _ReportLayout.cshtml         ← 报告公共布局
│       ├── Cover.cshtml                 ← 封面页
│       ├── IntelligenceOverview.cshtml  ← 八大智能分析
│       ├── StrongestIntelligence.cshtml ← 最强智能详情
│       ├── WeakestIntelligence.cshtml   ← 较弱智能详情
│       ├── PersonalityTraits.cshtml     ← 个人特质分析
│       ├── SubAbilities.cshtml          ← 40项细分能力
│       ├── LearningTypes.cshtml         ← 先天学习类型
│       ├── LearningAbilities.cshtml     ← 学习关键能力
│       ├── BrainTypes.cshtml            ← 科学大脑类型
│       ├── CharacterTypes.cshtml        ← 性格类型
│       └── FutureAbilities.cshtml       ← 未来关键发展能力
├── wwwroot/
│   └── css/
│       └── report.css                   ← 报告公共样式
└── Program.cs                           ← 添加 Razor Pages 服务注册

组件与接口

1. ReportDataService报告数据服务

位于 MiAssessment.Core/Services/,注册为 Scoped 服务。

/// <summary>
/// 报告数据服务接口
/// </summary>
public interface IReportDataService
{
    /// <summary>
    /// 获取指定测评记录的完整报告数据
    /// </summary>
    /// <param name="recordId">测评记录ID</param>
    /// <returns>报告数据传输对象</returns>
    /// <exception cref="BusinessException">记录不存在或状态不正确时抛出</exception>
    Task<ReportDataDto> GetReportDataAsync(long recordId);

    /// <summary>
    /// 确保指定测评记录的结论数据存在,不存在则自动生成
    /// </summary>
    /// <param name="recordId">测评记录ID</param>
    Task EnsureConclusionsExistAsync(long recordId);
}

2. ReportGenerationService 修改

在现有 PersistResultsAsync 方法的事务中,增加结论数据复制逻辑:

// 在写入 AssessmentResult 之后,复制结论数据
// 1. 删除该 RecordId 已有的 AssessmentRecordConclusion 记录
// 2. 根据每个 FinalCategoryResult 的 CategoryId 和 ConclusionType
//    从 report_conclusions 查询模板,复制 Title 和 Content 到 assessment_record_conclusions

3. Razor Pages PageModel 基类

/// <summary>
/// 报告页面基类,提供公共的 recordId 解析和数据加载逻辑
/// </summary>
public abstract class ReportPageModelBase : PageModel
{
    protected readonly IReportDataService ReportDataService;

    /// <summary>
    /// 报告数据(所有页面共用)
    /// </summary>
    public ReportDataDto? ReportData { get; set; }

    /// <summary>
    /// 错误信息(数据异常时设置)
    /// </summary>
    public string? ErrorMessage { get; set; }

    /// <summary>
    /// 是否渲染成功
    /// </summary>
    public bool IsSuccess => ErrorMessage == null && ReportData != null;

    protected ReportPageModelBase(IReportDataService reportDataService)
    {
        ReportDataService = reportDataService;
    }

    /// <summary>
    /// 加载报告数据,处理公共的参数验证和异常捕获
    /// </summary>
    public async Task<IActionResult> OnGetAsync(long? recordId)
    {
        if (recordId == null || recordId <= 0)
        {
            ErrorMessage = "缺少测评记录参数";
            return Page();
        }

        try
        {
            ReportData = await ReportDataService.GetReportDataAsync(recordId.Value);
            await OnDataLoadedAsync(); // 子类可覆写,做页面特定的数据处理
            return Page();
        }
        catch (Exception ex)
        {
            ErrorMessage = ex.Message;
            return Page();
        }
    }

    /// <summary>
    /// 数据加载完成后的回调,子类可覆写
    /// </summary>
    protected virtual Task OnDataLoadedAsync() => Task.CompletedTask;
}

4. 报告页面路由映射

路由路径 Razor Page 文件 说明
/report/cover Pages/Report/Cover.cshtml 封面页
/report/intelligence-overview Pages/Report/IntelligenceOverview.cshtml 八大智能分析
/report/strongest-intelligence Pages/Report/StrongestIntelligence.cshtml 最强智能详情
/report/weakest-intelligence Pages/Report/WeakestIntelligence.cshtml 较弱智能详情
/report/personality-traits Pages/Report/PersonalityTraits.cshtml 个人特质分析
/report/sub-abilities Pages/Report/SubAbilities.cshtml 40项细分能力
/report/learning-types Pages/Report/LearningTypes.cshtml 先天学习类型
/report/learning-abilities Pages/Report/LearningAbilities.cshtml 学习关键能力
/report/brain-types Pages/Report/BrainTypes.cshtml 科学大脑类型
/report/character-types Pages/Report/CharacterTypes.cshtml 性格类型
/report/future-abilities Pages/Report/FutureAbilities.cshtml 未来关键发展能力

Razor Pages 路由通过 @page "/report/cover" 指令配置,查询参数 recordId 通过 [BindProperty(SupportsGet = true)]OnGetAsync 参数绑定。

5. 公共布局模板 _ReportLayout.cshtml

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=1309" />
    <title>@ViewData["Title"] - 测评报告</title>
    <link rel="stylesheet" href="/css/report.css" />
    @RenderSection("Styles", required: false)
</head>
<body>
    <div class="report-page">
        <!-- 页面头部:板块标题 -->
        @if (ViewData["PageTitle"] != null)
        {
            <div class="report-header">
                <h1 class="report-title">@ViewData["PageTitle"]</h1>
            </div>
        }

        <!-- 页面主体内容 -->
        <div class="report-body">
            @RenderBody()
        </div>

        <!-- 页面底部:页码 -->
        @if (ViewData["PageNumber"] != null)
        {
            <div class="report-footer">
                <span class="page-number">@ViewData["PageNumber"]</span>
            </div>
        }
    </div>

    @RenderSection("Scripts", required: false)

    <!-- 渲染完成标记脚本 -->
    <script>
        // 默认标记渲染完成(无异步内容的页面)
        // 有图表的页面在 Scripts section 中覆盖此逻辑
        if (!window.__deferRenderComplete) {
            document.body.setAttribute('data-render-complete', 'true');
        }
    </script>
</body>
</html>

6. 公共样式 report.css 核心规则

/* 页面容器:固定 1309×926px */
.report-page {
    width: 1309px;
    height: 926px;
    margin: 0;
    padding: 40px 50px;
    box-sizing: border-box;
    background-color: #FFFFFF;
    font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
    position: relative;
    overflow: hidden;
}

/* 页面头部 */
.report-header {
    margin-bottom: 20px;
}

.report-title {
    font-size: 28px;
    font-weight: 600;
    color: #333333;
}

/* 页面底部页码 */
.report-footer {
    position: absolute;
    bottom: 20px;
    left: 0;
    right: 0;
    text-align: center;
}

/* 星级显示 */
.star-rating .star-filled { color: #FAAD14; }
.star-rating .star-empty { color: #E8E8E8; }

/* 错误页面 */
.report-error {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: #FF4D4F;
    font-size: 20px;
}

/* 主色调 */
:root {
    --primary-color: #4A90E2;
    --text-color: #333333;
    --text-secondary: #666666;
    --border-color: #E8E8E8;
    --bg-gray: #F8F8F8;
}

数据模型

1. assessment_record_conclusions 表

存储每条测评记录的独立结论数据副本,由报告生成时从 report_conclusions 模板表复制而来。

字段名 类型 约束 说明
Id bigint PK, 自增 主键ID
RecordId bigint NOT NULL, FK → assessment_records.Id 测评记录ID
CategoryId bigint NOT NULL, FK → report_categories.Id 分类ID
ConclusionType int NOT NULL 结论类型1最强 2较强 3较弱 4最弱
StarLevel int NOT NULL 星级1-5用于记录级别的星级覆盖
Title nvarchar(100) NULL 结论标题
Content nvarchar(max) NOT NULL 结论内容(富文本)
CreateTime datetime NOT NULL, DEFAULT getdate() 创建时间
UpdateTime datetime NOT NULL, DEFAULT getdate() 更新时间
IsDeleted bit NOT NULL, DEFAULT 0 软删除标记

索引:

  • ix_arc_record_id ON (RecordId) — 按测评记录查询
  • ix_arc_record_category ON (RecordId, CategoryId) — 按记录+分类查询

对应实体类 AssessmentRecordConclusion

[Table("assessment_record_conclusions")]
public class AssessmentRecordConclusion
{
    [Key]
    public long Id { get; set; }

    public long RecordId { get; set; }

    public long CategoryId { get; set; }

    /// <summary>
    /// 结论类型1最强 2较强 3较弱 4最弱
    /// </summary>
    public int ConclusionType { get; set; }

    /// <summary>
    /// 星级1-5记录级别的星级可由管理员覆盖
    /// </summary>
    public int StarLevel { get; set; }

    [MaxLength(100)]
    public string? Title { get; set; }

    [Required]
    [Column(TypeName = "nvarchar(max)")]
    public string Content { get; set; } = null!;

    public DateTime CreateTime { get; set; }
    public DateTime UpdateTime { get; set; }
    public bool IsDeleted { get; set; }

    [ForeignKey(nameof(RecordId))]
    public virtual AssessmentRecord? Record { get; set; }

    [ForeignKey(nameof(CategoryId))]
    public virtual ReportCategory? Category { get; set; }
}

2. report_page_configs 表

定义 PDF 报告中每一页的类型、顺序和关联资源。

字段名 类型 约束 说明
Id bigint PK, 自增 主键ID
PageType int NOT NULL 页面类型1静态图片 2网页截图
PageName nvarchar(50) NOT NULL 页面标识名称(如 cover、intelligence-overview
Title nvarchar(100) NOT NULL 页面显示标题
SortOrder int NOT NULL 排序序号,从 1 开始
ImageUrl nvarchar(500) NULL 静态图片路径PageType=1 时必填)
RouteUrl nvarchar(200) NULL 网页路由路径PageType=2 时必填)
Status int NOT NULL, DEFAULT 1 状态0禁用 1启用
CreateTime datetime NOT NULL, DEFAULT getdate() 创建时间
UpdateTime datetime NOT NULL, DEFAULT getdate() 更新时间

索引:

  • ix_rpc_sort_order ON (SortOrder) — 按排序查询
  • ix_rpc_status ON (Status) — 按状态筛选

对应实体类 ReportPageConfig

[Table("report_page_configs")]
public class ReportPageConfig
{
    [Key]
    public long Id { get; set; }

    /// <summary>
    /// 页面类型1静态图片 2网页截图
    /// </summary>
    public int PageType { get; set; }

    /// <summary>
    /// 页面标识名称
    /// </summary>
    [Required]
    [MaxLength(50)]
    public string PageName { get; set; } = null!;

    /// <summary>
    /// 页面显示标题
    /// </summary>
    [Required]
    [MaxLength(100)]
    public string Title { get; set; } = null!;

    /// <summary>
    /// 排序序号
    /// </summary>
    public int SortOrder { get; set; }

    /// <summary>
    /// 静态图片路径PageType=1 时使用)
    /// </summary>
    [MaxLength(500)]
    public string? ImageUrl { get; set; }

    /// <summary>
    /// 网页路由路径PageType=2 时使用)
    /// </summary>
    [MaxLength(200)]
    public string? RouteUrl { get; set; }

    /// <summary>
    /// 状态0禁用 1启用
    /// </summary>
    public int Status { get; set; } = 1;

    public DateTime CreateTime { get; set; }
    public DateTime UpdateTime { get; set; }
}

3. ReportDataDto报告数据传输对象

/// <summary>
/// 报告完整数据 DTO
/// </summary>
public class ReportDataDto
{
    /// <summary>
    /// 测评记录基本信息
    /// </summary>
    public RecordInfoDto RecordInfo { get; set; } = null!;

    /// <summary>
    /// 按 CategoryType 分组的测评结果
    /// Key: CategoryType (1-8), Value: 该类型下所有分类的结果列表
    /// </summary>
    public Dictionary<int, List<CategoryResultDataDto>> ResultsByType { get; set; } = new();

    /// <summary>
    /// 按 CategoryId 索引的结论数据
    /// Key: CategoryId, Value: 该分类的结论数据
    /// </summary>
    public Dictionary<long, ConclusionDataDto> ConclusionsByCategory { get; set; } = new();

    /// <summary>
    /// 报告分类层级结构
    /// Key: CategoryType, Value: 该类型的分类树
    /// </summary>
    public Dictionary<int, List<CategoryTreeDto>> CategoryTrees { get; set; } = new();
}

/// <summary>
/// 测评记录基本信息
/// </summary>
public class RecordInfoDto
{
    public long RecordId { get; set; }
    public string Name { get; set; } = null!;
    public int Gender { get; set; }
    public int Age { get; set; }
    public int EducationStage { get; set; }
    public string Province { get; set; } = null!;
    public string City { get; set; } = null!;
    public string District { get; set; } = null!;
    public DateTime? CompleteTime { get; set; }
}

/// <summary>
/// 分类测评结果数据
/// </summary>
public class CategoryResultDataDto
{
    public long CategoryId { get; set; }
    public string CategoryName { get; set; } = null!;
    public string CategoryCode { get; set; } = null!;
    public int CategoryType { get; set; }
    public long ParentId { get; set; }
    public decimal Score { get; set; }
    public decimal MaxScore { get; set; }
    public decimal Percentage { get; set; }
    public int Rank { get; set; }
    public int StarLevel { get; set; }
}

/// <summary>
/// 结论数据
/// </summary>
public class ConclusionDataDto
{
    public long CategoryId { get; set; }
    public int ConclusionType { get; set; }
    public int StarLevel { get; set; }
    public string? Title { get; set; }
    public string Content { get; set; } = null!;
}

/// <summary>
/// 分类树节点
/// </summary>
public class CategoryTreeDto
{
    public long Id { get; set; }
    public string Name { get; set; } = null!;
    public string Code { get; set; } = null!;
    public int CategoryType { get; set; }
    public long ParentId { get; set; }
    public List<CategoryTreeDto> Children { get; set; } = new();
}

4. 结论数据复制逻辑

ReportGenerationService.PersistResultsAsync 事务中增加:

对于每个 FinalCategoryResult:
  1. conclusionType = MapStarToConclusionType(starLevel)
  2. 从 report_conclusions 查询 (CategoryId, conclusionType) 对应的模板
  3. 创建 AssessmentRecordConclusion:
     - RecordId = 当前记录ID
     - CategoryId = result.CategoryId
     - ConclusionType = conclusionType
     - StarLevel = result.StarLevel
     - Title = 模板.Title
     - Content = 模板.Content
  4. 批量写入 assessment_record_conclusions

重新生成逻辑:

1. 删除该 RecordId 的所有 AssessmentRecordConclusion硬删除
2. 删除该 RecordId 的所有 AssessmentResult硬删除
3. 重新执行完整的报告生成流水线(计算得分 → 排名 → 星级 → 匹配结论 → 复制结论 → 写入结果)

5. Program.cs 集成变更

// 添加 Razor Pages 支持
builder.Services.AddRazorPages();

// 在 app.MapControllers() 之前添加
app.MapRazorPages();

// 添加静态文件支持(用于 report.css
app.UseStaticFiles();

正确性属性Correctness Properties

正确性属性是系统在所有有效执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。

Property 1: 结论复制数据一致性

For any 已完成的测评记录和其对应的 AssessmentResult 列表,执行结论复制后,每条 AssessmentRecordConclusion 的 ConclusionType 应等于 MapStarToConclusionType(StarLevel),且 Title 和 Content 应与 report_conclusions 模板表中对应 (CategoryId, ConclusionType) 的记录完全一致。

Validates: Requirements 1.1, 1.3

Property 2: 重新生成报告的幂等性

For any 测评记录,执行"重新生成报告"后产生的 AssessmentResult 和 AssessmentRecordConclusion 数据,应与对同一份原始答案数据执行首次生成的结果完全一致(即手动调整被完全覆盖,恢复到模板状态)。

Validates: Requirements 1.6, 1.7

Property 3: 结论数据自动生成

For any 状态为 4已完成的测评记录如果其对应的 AssessmentRecordConclusion 记录不存在,调用 ReportDataService.GetReportDataAsync 后,该记录的 AssessmentRecordConclusion 应被自动创建,且数据与从模板复制的结果一致。

Validates: Requirements 1.9

Property 4: recordId 参数验证

For any 报告页面路由,当 URL 中缺少 recordId 参数或 recordId 为空/无效时,返回的 HTML 应包含"缺少测评记录参数"错误提示文本,且 document.body 上应有 data-render-error="true" 属性。

Validates: Requirements 2.3, 2.6, 18.1

Property 5: 报告数据完整性

For any 状态为 4 的测评记录GetReportDataAsync 返回的 ReportDataDto 应包含:非空的 RecordInfo含姓名、性别、年龄、学业阶段、省市区、完成时间以及 CategoryType 1-8 中每个有对应 AssessmentResult 的类型都存在于 ResultsByType 字典中。

Validates: Requirements 3.2

Property 6: 页面配置类型字段约束

For any ReportPageConfig 记录,如果 PageType=1 则 ImageUrl 不为空,如果 PageType=2 则 RouteUrl 不为空。

Validates: Requirements 4.2, 4.3

Property 7: 页面配置排序与过滤

For any report_page_configs 记录集合,生成 PDF 时使用的页面列表应仅包含 Status=1 的记录,且按 SortOrder 升序排列。

Validates: Requirements 4.4, 4.5

Property 8: 渲染完成信号

For any 成功渲染的报告页面(无数据异常),输出的 HTML 中 document.body 应包含 data-render-complete="true" 属性。

Validates: Requirements 5.4, 18.3

Property 9: 星级图标渲染正确性

For any 星级值 n1 ≤ n ≤ 5渲染的星级 HTML 应包含恰好 n 个填充星star-filled和 5-n 个空心星star-empty

Validates: Requirements 17.4

Property 10: 空结论占位文本

For any 报告页面中某个分类的 AssessmentRecordConclusion 结论文本为空或不存在时,该位置的渲染输出应包含"暂无分析内容"占位文本,且不影响页面其他内容的正常展示。

Validates: Requirements 18.2

错误处理

数据层错误

场景 处理方式
recordId 对应的记录不存在或已软删除 ReportDataService 抛出 BusinessException("测评记录不存在")
记录状态不为 4已完成 ReportDataService 抛出 BusinessException("报告尚未生成完成")
结论数据不存在 自动触发 EnsureConclusionsExistAsync 生成结论数据
数据库连接异常 记录日志,页面显示通用错误信息

渲染层错误

场景 处理方式
URL 缺少 recordId 参数 返回包含"缺少测评记录参数"的错误 HTMLbody 添加 data-render-error="true"
ReportDataService 抛出异常 捕获异常页面显示错误信息body 添加 data-render-error="true"
某个板块的结论文本为空 对应位置显示"暂无分析内容"占位文本,不影响其他内容
ECharts CDN 加载失败 图表区域显示降级文本,仍然标记 data-render-complete="true"

页面配置错误

场景 处理方式
report_page_configs 表无启用记录 截图服务返回错误"报告页面配置为空,无法生成 PDF"
PageType=1 但 ImageUrl 为空 跳过该页,记录警告日志
PageType=2 但 RouteUrl 为空 跳过该页,记录警告日志

测试策略

测试框架

  • 单元测试xUnit + Moq
  • 属性测试FsCheck.NET 属性测试库)
  • 集成测试WebApplicationFactoryASP.NET Core 集成测试)

单元测试

重点覆盖以下场景:

  1. ReportDataService

    • GetReportDataAsync 正常返回完整数据
    • recordId 不存在时抛出异常
    • 记录状态非 4 时抛出异常
    • 结论不存在时自动触发生成
  2. 结论复制逻辑

    • 星级到结论类型的映射已有测试MapStarToConclusionType
    • 复制后数据与模板一致
    • 重新生成覆盖手动调整
  3. Razor Pages 渲染

    • 缺少 recordId 时显示错误页面
    • 正常数据渲染包含 data-render-complete 属性
    • 空结论显示占位文本
  4. 页面配置

    • 按 SortOrder 排序
    • Status=0 的记录被过滤
    • 空配置返回错误

属性测试

每个属性测试最少运行 100 次迭代,使用 FsCheck 生成随机输入。

属性 测试标签 说明
Property 1 Feature: report-web-pages, Property 1: 结论复制数据一致性 生成随机星级和分类,验证复制后的 ConclusionType 和内容匹配
Property 2 Feature: report-web-pages, Property 2: 重新生成报告的幂等性 生成随机答案数据,验证两次生成结果一致
Property 5 Feature: report-web-pages, Property 5: 报告数据完整性 生成随机完成记录,验证返回数据包含所有必要字段
Property 6 Feature: report-web-pages, Property 6: 页面配置类型字段约束 生成随机 PageConfig验证 PageType 与对应字段的约束
Property 7 Feature: report-web-pages, Property 7: 页面配置排序与过滤 生成随机配置列表,验证过滤和排序结果
Property 9 Feature: report-web-pages, Property 9: 星级图标渲染正确性 生成随机星级值 1-5验证填充星和空心星数量

集成测试

使用 WebApplicationFactory<Program> 进行端到端测试:

  1. 启动测试服务器,访问各报告页面路由
  2. 验证 HTTP 200 响应和 HTML 内容
  3. 验证错误场景的 HTML 输出
  4. 验证 data-render-complete / data-render-error 属性