本轮完成了完整的通知公告管理迁移:

原生 GET /api/admin/notices
原生五类系统公示聚合:计划、资格、录取、分数线、报到
原生 PATCH /api/admin/publications/{type}/{id} 显隐控制
权限、事务审计和 Node 返回结构保持一致
开关升级为 ADMIN_NATIVE_NOTICE_MANAGEMENT_ENABLED
兼容旧变量 ADMIN_NATIVE_NOTICE_WRITES_ENABLED
冒烟脚本现在会自动构建 Web,避免使用过期程序集
This commit is contained in:
2026-07-23 08:58:12 +08:00 Unverified
parent 9e21a277f0
commit e845ba8d3a
13 changed files with 597 additions and 37 deletions
@@ -123,7 +123,7 @@ public sealed class AdminMigrationOptionsTests
}
[Fact]
public void EnablesNoticeWritesWithReadsAndSharedSessions()
public void EnablesNoticeManagementWithReadsAndSharedSessions()
{
WithEnvironment("true", null, null, () =>
{
@@ -133,8 +133,22 @@ public sealed class AdminMigrationOptionsTests
sharesLegacySessions: true);
Assert.True(options.NativeReadsEnabled);
Assert.True(options.NativeNoticeWritesEnabled);
}, noticeWritesEnabled: "true");
Assert.True(options.NativeNoticeManagementEnabled);
}, noticeManagementEnabled: "true");
}
[Fact]
public void AcceptsLegacyNoticeWritesEnvironmentVariable()
{
WithEnvironment("true", null, null, () =>
{
var options = AdminMigrationOptions.FromEnvironment(
configuredNativeReadsEnabled: false,
authenticationNativeEnabled: true,
sharesLegacySessions: true);
Assert.True(options.NativeNoticeManagementEnabled);
}, legacyNoticeWritesEnabled: "true");
}
private static void WithEnvironment(
@@ -144,13 +158,15 @@ public sealed class AdminMigrationOptionsTests
Action test,
string? accountBatchesEnabled = null,
string? configurationEnabled = null,
string? noticeWritesEnabled = null)
string? noticeManagementEnabled = null,
string? legacyNoticeWritesEnabled = null)
{
var previousNativeReadsEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_READS_ENABLED");
var previousNativeOrganizationWritesEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED");
var previousAccountBatchesEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_ACCOUNT_BATCHES_ENABLED");
var previousConfigurationEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_CONFIGURATION_ENABLED");
var previousNoticeWritesEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_NOTICE_WRITES_ENABLED");
var previousNoticeManagementEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_NOTICE_MANAGEMENT_ENABLED");
var previousLegacyNoticeWritesEnabled = Environment.GetEnvironmentVariable("ADMIN_NATIVE_NOTICE_WRITES_ENABLED");
var previousAllowMemory = Environment.GetEnvironmentVariable("ADMIN_NATIVE_ALLOW_MEMORY");
try
{
@@ -158,7 +174,8 @@ public sealed class AdminMigrationOptionsTests
Environment.SetEnvironmentVariable("ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED", nativeOrganizationWritesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_ACCOUNT_BATCHES_ENABLED", accountBatchesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_CONFIGURATION_ENABLED", configurationEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_NOTICE_WRITES_ENABLED", noticeWritesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_NOTICE_MANAGEMENT_ENABLED", noticeManagementEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_NOTICE_WRITES_ENABLED", legacyNoticeWritesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_ALLOW_MEMORY", allowMemory);
test();
}
@@ -168,7 +185,8 @@ public sealed class AdminMigrationOptionsTests
Environment.SetEnvironmentVariable("ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED", previousNativeOrganizationWritesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_ACCOUNT_BATCHES_ENABLED", previousAccountBatchesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_CONFIGURATION_ENABLED", previousConfigurationEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_NOTICE_WRITES_ENABLED", previousNoticeWritesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_NOTICE_MANAGEMENT_ENABLED", previousNoticeManagementEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_NOTICE_WRITES_ENABLED", previousLegacyNoticeWritesEnabled);
Environment.SetEnvironmentVariable("ADMIN_NATIVE_ALLOW_MEMORY", previousAllowMemory);
}
}
@@ -0,0 +1,88 @@
using System.Text.Json.Nodes;
using Eis.Infrastructure.Administration;
namespace Eis.Infrastructure.Tests.Administration;
public sealed class AdminPublicationProjectorTests
{
[Fact]
public void VirtualRoundUsesSettingAsPatchTargetAndSuppressesFinalDuplicate()
{
var setting = Record(
"setting-1",
"setting",
"completed",
new JsonObject
{
["autoPublish"] = true,
["publicVisible"] = false,
["completedAt"] = "2026-06-02T00:00:00.000Z",
["roundPublishedAt"] = "2026-06-01T00:00:00.000Z"
});
var placement = Record(
"placement-1",
"placement",
"final",
new JsonObject { ["finalizedRound"] = 2 });
var snapshot = Snapshot([setting, placement]);
var publication = Assert.Single(AdminPublicationProjector.Build(snapshot));
Assert.Equal("setting-1", publication["id"]?.GetValue<string>());
Assert.Equal("admission", publication["sourceType"]?.GetValue<string>());
Assert.Equal("示例考试第 2 轮录取名单公示", publication["title"]?.GetValue<string>());
Assert.False(publication["visible"]?.GetValue<bool>());
Assert.Equal("hidden", publication["status"]?.GetValue<string>());
Assert.Same(setting, AdminPublicationProjector.FindSourceRecord(snapshot.Records, "admission", "setting-1"));
}
[Fact]
public void ReportingPublicationMatchesNodeProjection()
{
var report = new AdminPublicationRecord(
"report-1",
"notification",
"exam-1",
null,
"school-1",
"approved",
new JsonObject
{
["type"] = "admission_reporting",
["supplementDecision"] = "supplement",
["approvedAt"] = "2026-06-03T00:00:00.000Z",
["statistics"] = new JsonObject
{
["totalQuota"] = 20,
["reportedCount"] = 15,
["reportingRate"] = 75
}
},
"2026-06-01T00:00:00.000Z",
"2026-06-03T00:00:00.000Z");
var publication = Assert.Single(AdminPublicationProjector.Build(Snapshot([report])));
Assert.Equal("reporting", publication["sourceType"]?.GetValue<string>());
Assert.Equal("示例考试 · 示例学校考生报到情况及补录说明", publication["title"]?.GetValue<string>());
Assert.Equal("计划 20 人,已报到 15 人,完成率 75%。", publication["summary"]?.GetValue<string>());
Assert.True(publication["visible"]?.GetValue<bool>());
}
private static AdminPublicationRecord Record(string id, string kind, string status, JsonObject payload) => new(
id,
kind,
"exam-1",
null,
null,
status,
payload,
"2026-06-01T00:00:00.000Z",
"2026-06-02T00:00:00.000Z");
private static AdminNoticeManagementSnapshot Snapshot(IReadOnlyList<AdminPublicationRecord> records) => new(
[],
records,
new Dictionary<string, string> { ["exam-1"] = "示例考试" },
new Dictionary<string, string> { ["school-1"] = "示例学校" });
}