已完成第十一批迁移:考试维护功能已原生化到 ASP.NET Core 10。

支持考试创建、草稿修改、发布状态切换及不可逆归档。
支持科目、费用、满分和合格线策略校验。
保留已有报名禁止修改科目、待处理成绩复议禁止归档等规则。
数据修改、科目替换与审计日志使用同一数据库事务。
新增 ADMIN_NATIVE_EXAM_MANAGEMENT_ENABLED 独立开关。
路由与实现见 [AdminExamManagementService.cs (line 9)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Administration/AdminExamManagementService.cs:9) 和 [NativeAdminReadEndpoints.cs (line 133)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Web/Administration/NativeAdminReadEndpoints.cs:133)。
迁移说明已更新至 [MIGRATION.md (line 57)](C:/Users/BI/Documents/EIS-dotnet/MIGRATION.md:57)。
This commit is contained in:
2026-07-23 10:50:24 +08:00 Unverified
parent 2589305f3c
commit 25501651e1
12 changed files with 688 additions and 11 deletions
+95
View File
@@ -446,6 +446,7 @@ try {
ADMIN_NATIVE_CANDIDATE_MANAGEMENT_ENABLED = 'true'
ADMIN_NATIVE_REGISTRATION_PAYMENT_WRITES_ENABLED = 'true'
ADMIN_NATIVE_WORKFLOW_OPERATIONS_ENABLED = 'true'
ADMIN_NATIVE_EXAM_MANAGEMENT_ENABLED = 'true'
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
LegacyNode__Enabled = 'true'
LegacyNode__BaseUrl = $legacyBaseUrl
@@ -1353,6 +1354,99 @@ try {
$restoreSettingBody = @{ enabled = $originalSelfRegistration } | ConvertTo-Json -Compress
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/settings/self-registration" -Method Put -ContentType 'application/json' -Body $restoreSettingBody -WebSession $nativeSession | Out-Null
$examCreateForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -Method Post -ContentType 'application/json' -Body '{}' -WebSession $nativeSchoolSession -SkipHttpErrorCheck
if ($examCreateForbidden.StatusCode -ne 403 -or $examCreateForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native exam creation did not preserve the super-admin boundary'
}
$invalidExam = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -Method Post -ContentType 'application/json' -Body (@{ name = '缺少日期的考试' } | ConvertTo-Json -Compress) -WebSession $nativeSession -SkipHttpErrorCheck
if ($invalidExam.StatusCode -ne 400 -or $invalidExam.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native exam creation accepted a request without the required dates'
}
$invalidExamScoringBody = @{
name = '合格线无效的考试'
registrationStart = '2035-01-01T00:00:00.000Z'
registrationEnd = '2035-01-10T00:00:00.000Z'
examStart = '2035-02-01T00:00:00.000Z'
examEnd = '2035-02-02T00:00:00.000Z'
subjects = @(@{ name = '语文'; fullScore = 100; passRule = 'fixed_score'; passValue = 101 })
passPolicy = 'fixed_score'
passValue = 60
} | ConvertTo-Json -Depth 10 -Compress
$invalidExamScoring = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -Method Post -ContentType 'application/json' -Body $invalidExamScoringBody -WebSession $nativeSession -SkipHttpErrorCheck
if ($invalidExamScoring.StatusCode -ne 400) {
throw 'Native exam creation accepted an invalid subject passing score'
}
$examCode = "NATIVE-$([guid]::NewGuid().ToString('N').Substring(0, 12))"
$examCreateBody = @{
code = $examCode
name = '原生迁移考试'
description = '考试维护迁移冒烟验证'
registrationStart = '2035-01-01T00:00:00.000Z'
registrationEnd = '2035-01-10T00:00:00.000Z'
examStart = '2035-02-01T00:00:00.000Z'
examEnd = '2035-02-02T00:00:00.000Z'
location = '迁移测试考点'
passPolicy = 'fixed_score'
passValue = 130
status = 'draft'
subjects = @(
@{ name = '语文'; fullScore = 120; passRule = 'fixed_score'; passValue = 72; fee = 30 },
@{ name = '英语'; fullScore = 100; passRule = 'rank_percent'; passValue = 30; fee = 25 }
)
} | ConvertTo-Json -Depth 10 -Compress
$examCreateResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -Method Post -ContentType 'application/json' -Body $examCreateBody -WebSession $nativeSession
$createdExam = ($examCreateResponse.Content | ConvertFrom-Json).exam
if ($examCreateResponse.StatusCode -ne 201 -or $examCreateResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
$createdExam.code -ne $examCode -or $createdExam.status -ne 'draft' -or @($createdExam.subjects).Count -ne 2) {
throw 'Native exam creation did not persist the requested draft and subjects'
}
$legacyExamsAfterCreate = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/exams" -WebSession $session
$nativeExamsAfterCreate = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -WebSession $nativeSession
Assert-JsonEquivalent -Expected $legacyExamsAfterCreate.Content -Actual $nativeExamsAfterCreate.Content -Label 'Exam creation follow-up'
$examUpdateBody = @{
name = '原生迁移考试(已修改)'
location = '迁移测试新考点'
passPolicy = 'subject_scores'
passValue = 0
subjects = @(
@{ name = '综合能力'; fullScore = 100; passRule = 'fixed_score'; passValue = 60; fee = 40; start = '13:30'; end = '15:30' }
)
} | ConvertTo-Json -Depth 10 -Compress
$examUpdateResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)" -Method Patch -ContentType 'application/json' -Body $examUpdateBody -WebSession $nativeSession
$updatedExam = ($examUpdateResponse.Content | ConvertFrom-Json).exam
if ($examUpdateResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or $updatedExam.name -ne '原生迁移考试(已修改)' -or
$updatedExam.passPolicy -ne 'subject_scores' -or @($updatedExam.subjects).Count -ne 1 -or $updatedExam.subjects[0].start -ne '13:30') {
throw 'Native exam update did not replace the draft configuration'
}
$examPublishBody = @{ status = 'published' } | ConvertTo-Json -Compress
$examPublishResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)" -Method Patch -ContentType 'application/json' -Body $examPublishBody -WebSession $nativeSession
if (($examPublishResponse.Content | ConvertFrom-Json).exam.status -ne 'published') {
throw 'Native exam update did not publish the draft'
}
$publishedExamEdit = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)" -Method Patch -ContentType 'application/json' -Body (@{ name = '不应保存的名称' } | ConvertTo-Json -Compress) -WebSession $nativeSession -SkipHttpErrorCheck
if ($publishedExamEdit.StatusCode -ne 409) {
throw 'Native exam update allowed detail changes while published'
}
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)" -Method Patch -ContentType 'application/json' -Body (@{ status = 'draft' } | ConvertTo-Json -Compress) -WebSession $nativeSession | Out-Null
$examArchiveResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)/archive" -Method Post -WebSession $nativeSession
$archivedExam = ($examArchiveResponse.Content | ConvertFrom-Json).exam
if ($examArchiveResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or $archivedExam.status -ne 'closed' -or
$archivedExam.registrationState -ne 'archived' -or -not $archivedExam.archivedAt -or $archivedExam.totalScore -ne 100) {
throw 'Native exam archive did not permanently close the exam'
}
$duplicateExamArchive = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)/archive" -Method Post -WebSession $nativeSession -SkipHttpErrorCheck
if ($duplicateExamArchive.StatusCode -ne 409) {
throw 'Native exam archive did not reject a duplicate archive operation'
}
$archivedExamUpdate = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams/$($createdExam.id)" -Method Patch -ContentType 'application/json' -Body (@{ status = 'draft' } | ConvertTo-Json -Compress) -WebSession $nativeSession -SkipHttpErrorCheck
if ($archivedExamUpdate.StatusCode -ne 409) {
throw 'Native exam update did not keep archived exams locked'
}
$legacyExamsAfterArchive = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/exams" -WebSession $session
$nativeExamsAfterArchive = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/exams" -WebSession $nativeSession
Assert-JsonEquivalent -Expected $legacyExamsAfterArchive.Content -Actual $nativeExamsAfterArchive.Content -Label 'Exam archive follow-up'
$noticeCreateForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/notices" -Method Post -ContentType 'application/json' -Body (@{ title = '越权公告'; content = '<p>无权发布</p>' } | ConvertTo-Json -Compress) -WebSession $nativeSchoolSession -SkipHttpErrorCheck
if ($noticeCreateForbidden.StatusCode -ne 403 -or $noticeCreateForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native notice creation did not preserve the super-admin boundary'
@@ -1487,6 +1581,7 @@ try {
NativeAdminCandidateManagement = 'passed'
NativeAdminRegistrationPaymentWrites = 'passed'
NativeAdminWorkflowOperations = 'passed'
NativeAdminExamManagement = 'passed'
} | Format-List
}
finally {