原生成绩查询、科目/总分排名、达线判断、等级计算及成绩单防伪码:[CandidateService.Results.cs (line 11)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Candidate/CandidateService.Results.cs:11)

原生成绩复议提交,工作流、分配人与审计日志同事务写入。
原生准考证下载窗口校验及 HTML 文书生成:[CandidateAdmitCardRenderer.cs (line 9)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Candidate/CandidateAdmitCardRenderer.cs:9)
三个路由已切换到 ASP.NET Core:[NativeCandidateEndpoints.cs (line 61)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Web/Candidate/NativeCandidateEndpoints.cs:61)
更新迁移说明:[MIGRATION.md (line 50)](C:/Users/BI/Documents/EIS-dotnet/MIGRATION.md:50)
This commit is contained in:
2026-07-22 20:08:08 +08:00 Unverified
parent aecb0a04e7
commit 4417d6d674
12 changed files with 783 additions and 25 deletions
+68
View File
@@ -104,6 +104,28 @@ function Assert-JsonEquivalent {
}
}
function Assert-CandidateResultsEquivalent {
param(
[Parameter(Mandatory)]
[string] $Expected,
[Parameter(Mandatory)]
[string] $Actual
)
$expectedNode = [System.Text.Json.Nodes.JsonNode]::Parse($Expected)
$actualNode = [System.Text.Json.Nodes.JsonNode]::Parse($Actual)
foreach ($node in @($expectedNode, $actualNode)) {
foreach ($summary in $node['summaries'].AsArray()) {
$summary.AsObject().Remove('verificationQr') | Out-Null
}
}
if (-not [System.Text.Json.Nodes.JsonNode]::DeepEquals($expectedNode, $actualNode)) {
$difference = Find-JsonDifference -Expected $expectedNode -Actual $actualNode -Path '$'
throw "Candidate route 'results' JSON payload differs from the legacy API at $difference"
}
}
function Find-JsonDifference {
param(
[AllowNull()]
@@ -407,6 +429,51 @@ try {
}
Assert-JsonEquivalent -Expected $legacyCandidateResponse.Content -Actual $nativeCandidateResponse.Content -Label "Candidate route '$candidateRoute'"
}
$legacyCandidateResultsResponse = Invoke-WebRequest -Uri "$legacyBaseUrl/api/candidate/results" -WebSession $candidateSession
$nativeCandidateResultsResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/results" -WebSession $nativeCandidateSession
if ($nativeCandidateResultsResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw "Candidate route 'results' did not use the native ASP.NET Core endpoint"
}
Assert-CandidateResultsEquivalent -Expected $legacyCandidateResultsResponse.Content -Actual $nativeCandidateResultsResponse.Content
$nativeCandidateResults = $nativeCandidateResultsResponse.Content | ConvertFrom-Json
if (@($nativeCandidateResults.summaries | Where-Object { $_.verificationQr -match '^data:image/png;base64,' }).Count -eq 0) {
throw 'Native candidate results did not include a local PNG verification QR code'
}
$appealableResult = @($nativeCandidateResults.results | Where-Object { $null -eq $_.appeal } | Select-Object -First 1)[0]
if ($null -ne $appealableResult) {
$shortAppeal = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/results/$($appealableResult.id)/appeals" -Method Post -ContentType 'application/json' -Body '{"reason":"短"}' -WebSession $nativeCandidateSession -SkipHttpErrorCheck
if ($shortAppeal.StatusCode -ne 400 -or $shortAppeal.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native score appeal did not validate the minimum reason length'
}
$appealBody = @{ reason = '迁移冒烟验证:成绩与个人估分差异较大,请复核计分。' } | ConvertTo-Json -Compress
$appealResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/results/$($appealableResult.id)/appeals" -Method Post -ContentType 'application/json' -Body $appealBody -WebSession $nativeCandidateSession
$appeal = $appealResponse.Content | ConvertFrom-Json
if ($appealResponse.StatusCode -ne 201 -or $appealResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or $appeal.workflow.status -ne 'pending') {
throw 'Native score appeal did not persist a pending workflow'
}
}
$admitProcess = Start-TestProcess -FileName $nodeExecutable -ArgumentList @(
'tests/helpers/prepare-candidate-admit-smoke.mjs', $smokeDatabasePath, '2026-HZ01-F-0001'
) -Environment $nodeEnvironment
if (-not $admitProcess.WaitForExit(10000)) {
$admitProcess.Kill($true)
throw 'Timed out while preparing the candidate admit-card smoke data'
}
$admitRegistrationId = $admitProcess.StandardOutput.ReadToEnd().Trim()
$admitError = $admitProcess.StandardError.ReadToEnd()
if ($admitProcess.ExitCode -ne 0 -or -not $admitRegistrationId) {
throw "Could not prepare the candidate admit-card smoke data`n$admitError"
}
$admitProcess.Dispose()
$legacyAdmitResponse = Invoke-WebRequest -Uri "$legacyBaseUrl/api/candidate/registrations/$admitRegistrationId/admit-card" -WebSession $candidateSession
$nativeAdmitResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/registrations/$admitRegistrationId/admit-card" -WebSession $nativeCandidateSession
if ($nativeAdmitResponse.StatusCode -ne 200 -or
$nativeAdmitResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
$nativeAdmitResponse.Headers['Content-Disposition'] -notmatch '^attachment;' -or
$nativeAdmitResponse.Content -notmatch '考试考场序号' -or
$nativeAdmitResponse.Content -ne $legacyAdmitResponse.Content) {
throw 'Native admit-card download did not match the legacy HTML document'
}
$registrationSchool = @($homePayload.schools | Select-Object -First 1)[0]
$registrationClass = @($homePayload.classes | Where-Object schoolId -eq $registrationSchool.id | Select-Object -First 1)[0]
@@ -631,6 +698,7 @@ try {
NativeAuthentication = 'passed'
NativeCandidateReads = 'passed'
NativeCandidateWrites = 'passed'
NativeCandidateDocuments = 'passed'
} | Format-List
}
finally {