已完成考生域第一批只读迁移:
GET /api/candidate/dashboard GET /api/candidate/notices GET /api/candidate/profile GET /api/candidate/exams GET /api/candidate/registrations
This commit is contained in:
1 parent
ae472aabb0
commit
07efa6813b
11 files changed
+1171
-3
No files matched your search
@@ -23,6 +23,8 @@ PORT=4173
|
||||
# 原生 ASP.NET Core 认证切换。迁移期间默认关闭;生产环境开启时必须配置共享 Redis,
|
||||
# 以便尚未迁移的 Node 受保护接口识别由 ASP.NET Core 创建的会话。
|
||||
AUTH_NATIVE_ENABLED=false
|
||||
# 第一批考生只读接口切换;必须与 AUTH_NATIVE_ENABLED=true 同时使用。
|
||||
CANDIDATE_NATIVE_ENABLED=false
|
||||
|
||||
# 仅在首次创建空数据库时使用。部署前务必修改初始密码。
|
||||
INITIAL_ADMIN_USERNAME=admin
|
||||
|
||||
@@ -47,6 +47,13 @@ $env:AUTH_NATIVE_ENABLED = 'true'
|
||||
|
||||
开发环境未配置 Redis 时可以使用进程内状态独立验证原生认证。生产环境以及仍需访问 Node 受保护接口的联调环境必须配置 `REDIS_URL` 或 `REDIS_SESSION_URL`;两个运行时会复用相同逻辑库和 `exam-information:auth` 键前缀,从而共享登录会话。`GET /health/migration` 会报告 `authentication.nativeEnabled`、状态后端和跨运行时会话共享能力。
|
||||
|
||||
考生域第一批只读端点(首页、通知、个人资料、可报名考试、我的报名)可通过以下开关原生运行;该开关必须与原生认证及共享 Redis 同时启用。资料更新、报名提交、成绩、准考证、复议和志愿填报仍会继续转发给 Node:
|
||||
|
||||
```powershell
|
||||
$env:AUTH_NATIVE_ENABLED = 'true'
|
||||
$env:CANDIDATE_NATIVE_ENABLED = 'true'
|
||||
```
|
||||
|
||||
完整的宿主、静态资源、JSON 转发和 Session Cookie 冒烟测试:
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -84,6 +84,74 @@ function Wait-ForUrl {
|
||||
throw "Timed out waiting for $Uri"
|
||||
}
|
||||
|
||||
function Assert-JsonEquivalent {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Expected,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Actual,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Label
|
||||
)
|
||||
|
||||
$expectedNode = [System.Text.Json.Nodes.JsonNode]::Parse($Expected)
|
||||
$actualNode = [System.Text.Json.Nodes.JsonNode]::Parse($Actual)
|
||||
if (-not [System.Text.Json.Nodes.JsonNode]::DeepEquals($expectedNode, $actualNode)) {
|
||||
$difference = Find-JsonDifference -Expected $expectedNode -Actual $actualNode -Path '$'
|
||||
throw "$Label JSON payload differs from the legacy API at $difference"
|
||||
}
|
||||
}
|
||||
|
||||
function Find-JsonDifference {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[System.Text.Json.Nodes.JsonNode] $Expected,
|
||||
|
||||
[AllowNull()]
|
||||
[System.Text.Json.Nodes.JsonNode] $Actual,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Path
|
||||
)
|
||||
|
||||
if ($null -eq $Expected -or $null -eq $Actual) {
|
||||
return "$Path (expected=$Expected, actual=$Actual)"
|
||||
}
|
||||
|
||||
if ($Expected -is [System.Text.Json.Nodes.JsonObject] -and $Actual -is [System.Text.Json.Nodes.JsonObject]) {
|
||||
foreach ($entry in $Expected) {
|
||||
if (-not $Actual.ContainsKey($entry.Key)) {
|
||||
return "$Path.$($entry.Key) (missing from actual)"
|
||||
}
|
||||
if (-not [System.Text.Json.Nodes.JsonNode]::DeepEquals($entry.Value, $Actual[$entry.Key])) {
|
||||
return Find-JsonDifference -Expected $entry.Value -Actual $Actual[$entry.Key] -Path "$Path.$($entry.Key)"
|
||||
}
|
||||
}
|
||||
foreach ($entry in $Actual) {
|
||||
if (-not $Expected.ContainsKey($entry.Key)) {
|
||||
return "$Path.$($entry.Key) (unexpected in actual)"
|
||||
}
|
||||
}
|
||||
return "$Path (object values differ)"
|
||||
}
|
||||
|
||||
if ($Expected -is [System.Text.Json.Nodes.JsonArray] -and $Actual -is [System.Text.Json.Nodes.JsonArray]) {
|
||||
if ($Expected.Count -ne $Actual.Count) {
|
||||
return "$Path.Count (expected=$($Expected.Count), actual=$($Actual.Count))"
|
||||
}
|
||||
for ($index = 0; $index -lt $Expected.Count; $index++) {
|
||||
if (-not [System.Text.Json.Nodes.JsonNode]::DeepEquals($Expected[$index], $Actual[$index])) {
|
||||
return Find-JsonDifference -Expected $Expected[$index] -Actual $Actual[$index] -Path "$Path[$index]"
|
||||
}
|
||||
}
|
||||
return "$Path (array values differ)"
|
||||
}
|
||||
|
||||
return "$Path (expected=$($Expected.ToJsonString()), actual=$($Actual.ToJsonString()))"
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $testDirectory | Out-Null
|
||||
|
||||
@@ -308,6 +376,8 @@ try {
|
||||
) -Environment @{
|
||||
ASPNETCORE_ENVIRONMENT = 'Development'
|
||||
AUTH_NATIVE_ENABLED = 'true'
|
||||
CANDIDATE_NATIVE_ENABLED = 'true'
|
||||
CANDIDATE_NATIVE_ALLOW_MEMORY = 'true'
|
||||
LegacyNode__Enabled = 'true'
|
||||
LegacyNode__BaseUrl = $legacyBaseUrl
|
||||
DATABASE_CLIENT = 'sqlite'
|
||||
@@ -319,6 +389,25 @@ try {
|
||||
}
|
||||
Wait-ForUrl -Uri "$nativeAuthBaseUrl/health/live" -Processes @($nodeProcess, $nativeAuthProcess)
|
||||
|
||||
$anonymousCandidate = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/profile" -SkipHttpErrorCheck
|
||||
if ($anonymousCandidate.StatusCode -ne 401 -or $anonymousCandidate.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native candidate API did not reject an anonymous request'
|
||||
}
|
||||
|
||||
$nativeCandidateSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$nativeCandidateLogin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $candidateLoginBody -WebSession $nativeCandidateSession
|
||||
if ($nativeCandidateLogin.user.candidateNumber -ne '2026-HZ01-F-0001') {
|
||||
throw 'Native authentication could not create the candidate parity-test session'
|
||||
}
|
||||
foreach ($candidateRoute in @('dashboard', 'notices', 'profile', 'exams', 'registrations')) {
|
||||
$legacyCandidateResponse = Invoke-WebRequest -Uri "$legacyBaseUrl/api/candidate/$candidateRoute" -WebSession $candidateSession
|
||||
$nativeCandidateResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/$candidateRoute" -WebSession $nativeCandidateSession
|
||||
if ($nativeCandidateResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw "Candidate route '$candidateRoute' did not use the native ASP.NET Core endpoint"
|
||||
}
|
||||
Assert-JsonEquivalent -Expected $legacyCandidateResponse.Content -Actual $nativeCandidateResponse.Content -Label "Candidate route '$candidateRoute'"
|
||||
}
|
||||
|
||||
$registrationSchool = @($homePayload.schools | Select-Object -First 1)[0]
|
||||
$registrationClass = @($homePayload.classes | Where-Object schoolId -eq $registrationSchool.id | Select-Object -First 1)[0]
|
||||
if ($null -eq $registrationSchool -or $null -eq $registrationClass) {
|
||||
@@ -337,10 +426,19 @@ try {
|
||||
}
|
||||
$registration = $registrationResponse.Content | ConvertFrom-Json
|
||||
$registeredLoginBody = @{ username = $registration.registrationNumber; password = 'Registration456!' } | ConvertTo-Json -Compress
|
||||
$registeredLogin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $registeredLoginBody
|
||||
$registeredSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$registeredLogin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $registeredLoginBody -WebSession $registeredSession
|
||||
if ($registeredLogin.user.candidateNumber -ne $registration.registrationNumber) {
|
||||
throw 'Native self-registration did not create a usable candidate account'
|
||||
}
|
||||
$incompleteDashboard = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/dashboard" -WebSession $registeredSession -SkipHttpErrorCheck
|
||||
if ($incompleteDashboard.StatusCode -ne 428) {
|
||||
throw 'Native candidate API did not require completion of a newly registered profile'
|
||||
}
|
||||
$incompleteProfile = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/profile" -WebSession $registeredSession
|
||||
if ($incompleteProfile.StatusCode -ne 200) {
|
||||
throw 'Native candidate profile route was not available during onboarding'
|
||||
}
|
||||
|
||||
$nativeSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$nativeLoginResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $loginBody -WebSession $nativeSession
|
||||
@@ -351,6 +449,10 @@ try {
|
||||
if ($nativeLogin.ok -ne $true -or $nativeLogin.user.username -ne 'admin') {
|
||||
throw 'Native authentication could not verify the existing Node PBKDF2 account'
|
||||
}
|
||||
$adminCandidateRoute = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/profile" -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($adminCandidateRoute.StatusCode -ne 403) {
|
||||
throw 'Native candidate API did not enforce the candidate role boundary'
|
||||
}
|
||||
|
||||
$nativeMe = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/me" -WebSession $nativeSession
|
||||
if ($nativeMe.user.username -ne 'admin' -or $nativeMe.permissions[0] -ne '*') {
|
||||
@@ -425,6 +527,7 @@ try {
|
||||
PublicParity = 'passed'
|
||||
DocumentCodes = 'passed'
|
||||
NativeAuthentication = 'passed'
|
||||
NativeCandidateReads = 'passed'
|
||||
} | Format-List
|
||||
}
|
||||
finally {
|
||||
|
||||
Loaded 3 of 11 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user