Files
EIS-dotnet/tests/Eis.Infrastructure.Tests/Administration/AdminResultStatisticsTests.cs
T
biss cde754621a 成绩提交时按考试一次性计算并存储单科名次、排名百分比、等级、合格状态、总分排名及整场合格状态。
新增 result_statistics、registration_result_summaries 两张统计表,现有数据库首次启动时自动回填。
SQL 查询首先通过 examId 限定范围。
科目成绩只排序一次,并使用 registration/subject 字典组织数据。
汇总接口与成绩明细接口已拆分,明细、成绩录入名单和特征分名单均改为服务端分页。
所有成绩写入口统一限制为:0—满分,且只能是整数或 .5,包括手工录入、批量录入、Excel 导入和复议改分。
前端也增加了相同的即时校验。
修复其他考试卡片显示 undefined 的问题。
2026-07-23 19:21:56 +08:00

121 lines
4.6 KiB
C#

using System.Text.Json.Nodes;
using Eis.Infrastructure.Administration;
namespace Eis.Infrastructure.Tests.Administration;
public sealed class AdminResultStatisticsTests
{
[Theory]
[InlineData(0, 100, true)]
[InlineData(10, 100, true)]
[InlineData(10.5, 100, true)]
[InlineData(10.1, 100, false)]
[InlineData(-0.5, 100, false)]
[InlineData(100.5, 100, false)]
public void IsValidScore_OnlyAcceptsWholeOrHalfPoints(double score, double fullScore, bool expected)
{
Assert.Equal(expected, AdminResultStatistics.IsValidScore(score, fullScore));
}
[Fact]
public void Calculate_ComputesEachSubjectAndTotalRankingOnceForTheExam()
{
var exam = Exam("rank_percent", 50);
var registrations = new[] { Registration("r1"), Registration("r2"), Registration("r3") };
var results = new[]
{
Result("r1-s1", "r1", "s1", 90), Result("r1-s2", "r1", "s2", 80),
Result("r2-s1", "r2", "s1", 90), Result("r2-s2", "r2", "s2", 70),
Result("r3-s1", "r3", "s1", 60), Result("r3-s2", "r3", "s2", 70)
};
var calculation = AdminResultStatistics.Calculate(exam, registrations, results, "2026-07-23T00:00:00.000Z");
Assert.Equal(6, calculation.Metrics.Count);
Assert.Equal(1, calculation.Metrics.Single(item => item.ResultId == "r1-s1").Rank);
Assert.Equal(1, calculation.Metrics.Single(item => item.ResultId == "r2-s1").Rank);
Assert.Equal(3, calculation.Metrics.Single(item => item.ResultId == "r3-s1").Rank);
Assert.Equal(2, calculation.Metrics.Single(item => item.ResultId == "r2-s2").Rank);
Assert.Equal(2, calculation.Metrics.Single(item => item.ResultId == "r3-s2").Rank);
var summaries = calculation.Summaries.ToDictionary(item => item.RegistrationId);
Assert.Equal(170, summaries["r1"].TotalScore);
Assert.Equal(1, summaries["r1"].Rank);
Assert.True(summaries["r1"].Qualified);
Assert.Equal(2, summaries["r2"].Rank);
Assert.True(summaries["r2"].Qualified);
Assert.Equal(3, summaries["r3"].Rank);
Assert.False(summaries["r3"].Qualified);
Assert.All(summaries.Values, item => Assert.Equal(3, item.CohortSize));
}
[Fact]
public void Calculate_PersistsIncompleteAndDraftStatesWithoutIncludingDraftInPublishedSummary()
{
var exam = Exam("subject_scores", 0);
var registrations = new[] { Registration("r1"), Registration("r2") };
var results = new[]
{
Result("r1-s1", "r1", "s1", 95),
Result("r1-s2", "r1", "s2", 88, published: false),
Result("r2-s1", "r2", "s1", 80),
Result("r2-s2", "r2", "s2", 75)
};
var calculation = AdminResultStatistics.Calculate(exam, registrations, results, "2026-07-23T00:00:00.000Z");
var draft = calculation.Metrics.Single(item => item.ResultId == "r1-s2");
var summaries = calculation.Summaries.ToDictionary(item => item.RegistrationId);
Assert.Equal("待发布", draft.Grade);
Assert.Equal(2, draft.CohortSize);
Assert.False(summaries["r1"].Complete);
Assert.Null(summaries["r1"].Qualified);
Assert.Equal(1, summaries["r1"].PublishedSubjectCount);
Assert.True(summaries["r2"].Complete);
}
private static OperationalExam Exam(string passPolicy, double passValue)
{
var subjects = new[]
{
new JsonObject { ["id"] = "s1", ["name"] = "语文", ["fullScore"] = 100, ["passRule"] = "fixed_score", ["passValue"] = 60 },
new JsonObject { ["id"] = "s2", ["name"] = "数学", ["fullScore"] = 100, ["passRule"] = "fixed_score", ["passValue"] = 60 }
};
return new("exam-1", new JsonObject
{
["id"] = "exam-1",
["passPolicy"] = passPolicy,
["passValue"] = passValue
}, subjects);
}
private static OperationalRegistration Registration(string id) => new(
id,
$"user-{id}",
"exam-1",
"approved",
"paid",
null,
"2026-07-23T00:00:00.000Z",
new JsonObject
{
["id"] = id,
["subjectIds"] = new JsonArray("s1", "s2")
});
private static AdminWorkflowResult Result(
string id,
string registrationId,
string subjectId,
double score,
bool published = true) => new(
id,
registrationId,
subjectId,
score,
published ? "" : "待发布",
published,
"2026-07-23T00:00:00.000Z",
published ? "2026-07-23T00:00:00.000Z" : null);
}