已完成考生域第一批只读迁移:
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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user