You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
19 KiB
262 lines
19 KiB
# ============================================================================
|
|
# V35 RIASEC assessment BLACK-BOX ACCEPTANCE (real MySQL/Docker) - rev2
|
|
# Scenario isolation: each scenario uses a freshly auto-created mock student.
|
|
# No secrets printed; no business data deleted; only appends assessment rows.
|
|
# ============================================================================
|
|
$ErrorActionPreference = 'Continue'
|
|
Import-Module ThreadJob -ErrorAction SilentlyContinue
|
|
$BASE = 'http://127.0.0.1:8080/api/v1'
|
|
$LOG = 'C:\Users\15642\Desktop\zhilai\backend-infra\app.log'
|
|
$ENV = 'C:\Users\15642\Desktop\zhilai\backend-infra\.env'
|
|
$RMQ_API = 'http://127.0.0.1:15672/api/queues/%2F'
|
|
|
|
$kv=@{}; Get-Content $ENV | ForEach-Object{ if($_ -match '^\s*([^#][^=]*?)\s*=\s*(.*)\s*$'){ $kv[$matches[1].Trim()]=$matches[2].Trim() } }
|
|
$env:MPW=$kv['MYSQL_PASSWORD']; $RMQ_USER=$kv['RABBITMQ_USER']; $RMQ_PASS=$kv['RABBITMQ_PASS']
|
|
|
|
function MySQL($sql){ docker exec -e MYSQL_PWD=$env:MPW mydreamjob-mysql mysql -umydreamjob -N mydreamjob -e $sql 2>&1 }
|
|
function FirstOf($sql){ $r=(MySQL $sql | Select-Object -First 1); if($null -eq $r){return ''}; (($r -split '\s+')[0]).Trim() }
|
|
function PhoneOfRole($role){
|
|
# ADMIN uses a separate query; do not apply the pure-COMPANY exclusion.
|
|
if($role -eq 'ADMIN'){
|
|
FirstOf ("SELECT u.phone FROM users u JOIN user_roles ur ON ur.user_id=u.id AND ur.role='ADMIN' ORDER BY u.id LIMIT 1");
|
|
} else {
|
|
FirstOf ("SELECT u.phone FROM users u JOIN user_roles ur ON ur.user_id=u.id AND ur.role='$role' AND u.id NOT IN (SELECT user_id FROM user_roles WHERE role IN ('STUDENT','ADMIN')) LIMIT 1");
|
|
}
|
|
}
|
|
|
|
$pass=0;$fail=0;$skip=0;$failures=@()
|
|
function Chk($n,$c,$d){ if($c){$script:pass++;Write-Output "PASS - $n"}else{$script:fail++;$script:failures+="$n :: $d";Write-Output "FAIL - $n | $d"} }
|
|
function ChkSkip($n,$d){ $script:skip++;Write-Output "SKIP - $n | $d" }
|
|
function Api($method,$url,$token,$body){
|
|
$h=@{}; if($token){$h['Authorization']="Bearer $token"}
|
|
if($null -ne $body){$h['Content-Type']='application/json'}
|
|
try { $a=@{Method=$method;Uri=$url;Headers=$h;TimeoutSec=30;UseBasicParsing=$true}; if($null -ne $body){$a.Body=($body|ConvertTo-Json -Depth 8)}; $r=Invoke-WebRequest @a; $j=$null;try{$j=$r.Content|ConvertFrom-Json}catch{}; return @{status=[int]$r.StatusCode;json=$j;raw=$r.Content} }
|
|
catch { $c='';if($_.ErrorDetails -and $_.ErrorDetails.Message){$c=$_.ErrorDetails.Message};$st=if($_.Exception.Response -and $_.Exception.Response.StatusCode){[int]$_.Exception.Response.StatusCode}else{-1};$j=$null;try{$j=$c|ConvertFrom-Json}catch{};return @{status=$st;json=$j;raw=$c} }
|
|
}
|
|
function F($o,$n){ if($null -eq $o){return $null}; if($o.PSObject.Properties.Name -contains $n){return $o.$n}; return $null }
|
|
function Login($phone){
|
|
for($i=0;$i -lt 3;$i++){
|
|
try{ Invoke-RestMethod -Uri "$BASE/auth/sms/send?phone=$phone&purpose=login" -Method Post -TimeoutSec 15 | Out-Null }catch{}
|
|
Start-Sleep -Milliseconds 900
|
|
$m = Get-Content $LOG -ErrorAction SilentlyContinue | Select-String -Pattern "MOCK SMS CODE: phone=$phone, purpose=login, code=([0-9]{6})"
|
|
if($m){ $code=$m[-1].Matches[0].Groups[1].Value; $b=@{phone=$phone;code=$code;purpose='login'}|ConvertTo-Json
|
|
try{ $t=(Invoke-RestMethod -Uri "$BASE/auth/login" -Method Post -ContentType 'application/json' -Body $b -TimeoutSec 15).data.accessToken; if($t -and "$t".Length -gt 50){return $t} }catch{} }
|
|
Start-Sleep -Milliseconds 600
|
|
}
|
|
return $null
|
|
}
|
|
function RmqStat($name){
|
|
$q=$null; try{ $q=(& curl.exe -s -u "${RMQ_USER}:${RMQ_PASS}" "$RMQ_API" 2>&1 | Out-String)|ConvertFrom-Json }catch{}
|
|
if($null -eq $q){return @{ready=-1;unacked=-1}}
|
|
$h=$q|Where-Object{$_.name -eq $name}|Select-Object -First 1
|
|
# Missing queue is not an empty queue; return -1 so it cannot pass as ready=0.
|
|
if($null -eq $h){return @{ready=-1;unacked=-1}}
|
|
return @{ready=[long]$h.messages;unacked=[long]$h.messages_unacknowledged}
|
|
}
|
|
function NewStudent(){ $ph='188'+('{0:D8}' -f (Get-Random -Minimum 1000000 -Maximum 99999999)); $t=Login $ph; $id=FirstOf "SELECT id FROM users WHERE phone='$ph'"; return @{token=$t;phone=$ph;id=$id} }
|
|
|
|
Write-Output "===== V35 ASSESSMENT BLACK-BOX (real MySQL) rev2 ====="
|
|
|
|
# ---------- shared role tokens ----------
|
|
$companyTok = Login (PhoneOfRole 'COMPANY')
|
|
$adminTok = Login (PhoneOfRole 'ADMIN')
|
|
|
|
# ============ S1 main flow (fresh student) ============
|
|
Write-Output '== S1 main flow =='
|
|
$s1 = NewStudent; $tok=$s1.token
|
|
Chk 'S1 fresh student login' ($null -ne $tok)
|
|
if(-not $tok){ exit 1 }
|
|
$k = 'bb1-'+[guid]::NewGuid().ToString('N')
|
|
$c = Api 'POST' "$BASE/assessments/attempts" $tok @{typeCode='HOLLAND_RIASEC';idempotencyKey=$k}
|
|
$aid = F $c.json.data 'attemptId'; $v = F $c.json.data 'version'
|
|
Chk 'S1 create 200' ($c.status -eq 200) $c.status
|
|
Chk 'S1 totalQuestions=30' ((F $c.json.data 'totalQuestions') -eq 30) (F $c.json.data 'totalQuestions')
|
|
Chk 'S1 questions length=30' (@($c.json.data.questions).Count -eq 30) (@($c.json.data.questions).Count)
|
|
$cnt = FirstOf "SELECT COUNT(*) FROM assessment_attempt_questions WHERE attempt_id=$aid"
|
|
Chk 'S1 DB attempt_questions=30' ($cnt -eq 30) $cnt
|
|
$dimMap=@{}; foreach($d in (MySQL "SELECT q.dimension,COUNT(*) FROM assessment_attempt_questions aq JOIN assessment_questions q ON q.id=aq.question_id WHERE aq.attempt_id=$aid GROUP BY q.dimension")){ $p=($d -split '\s+'); if($p -and $p[0] -and $p[1]){$dimMap[$p[0]]=[int]$p[1]} }
|
|
Chk 'S1 DB 6 dims x5' ((@('A','C','E','I','R','S' | ForEach-Object{$dimMap[$_]-eq 5}) -contains $false) -eq $false) ($dimMap|ConvertTo-Json -Compress)
|
|
$qids=@($c.json.data.questions|ForEach-Object{$_.id})
|
|
# mid-save 10
|
|
$p1 = Api 'PATCH' "$BASE/assessments/attempts/$aid/answers" $tok @{version=$v;answers=(@($qids|Select-Object -First 10)|ForEach-Object{@{questionId=$_;value=4}})}
|
|
Chk 'S1 PATCH partial 200 answered=10' ($p1.status -eq 200 -and (F $p1.json.data 'answeredCount') -eq 10) "$($p1.status)/$(F $p1.json.data 'answeredCount')"
|
|
$g = Api 'GET' "$BASE/assessments/attempts/$aid" $tok
|
|
$gids=@($g.json.data.questions|ForEach-Object{$_.id})
|
|
Chk 'S1 GET order unchanged' (($gids -join ',') -eq ($qids -join ','))
|
|
Chk 'S1 GET restored 10' (@($g.json.data.questions|Where-Object{$null -ne $_.value}).Count -eq 10)
|
|
$nv = F $g.json.data 'version'
|
|
# 29-ish submit (only 10 answered) -> 9706
|
|
$s29 = Api 'POST' "$BASE/assessments/attempts/$aid/submit" $tok $null
|
|
Chk 'S1 10/30 submit -> 400/9706' ($s29.status -eq 400 -and (F $s29.json 'code') -eq 9706) "$($s29.status)/$(F $s29.json 'code')"
|
|
# full 30
|
|
$full = Api 'PATCH' "$BASE/assessments/attempts/$aid/answers" $tok @{version=$nv;answers=(@($qids)|ForEach-Object{@{questionId=$_;value=4}})}
|
|
$fv = F $full.json.data 'version'
|
|
$sub = Api 'POST' "$BASE/assessments/attempts/$aid/submit" $tok $null
|
|
Chk 'S1 submit 200 COMPLETED' ($sub.status -eq 200 -and (F $sub.json.data 'status') -eq 'COMPLETED') "$($sub.status)/$(F $sub.json.data 'status')"
|
|
Chk 'S1 result primaryCode present' ($null -ne (F $sub.json.data 'primaryCode')) (F $sub.json.data 'primaryCode')
|
|
Chk 'S1 result disclaimer' ($null -ne (F $sub.json.data 'disclaimer'))
|
|
$sub2 = Api 'POST' "$BASE/assessments/attempts/$aid/submit" $tok $null
|
|
Chk 'S1 repeat submit same result' ($sub2.status -eq 200 -and (F $sub2.json.data 'primaryCode') -eq (F $sub.json.data 'primaryCode')) "$($sub2.status)"
|
|
Chk 'S1 DB result count=1' ((FirstOf "SELECT COUNT(*) FROM assessment_results WHERE attempt_id=$aid") -eq 1)
|
|
Chk 'S1 result GET 200' ((Api 'GET' "$BASE/assessments/attempts/$aid/result" $tok).status -eq 200)
|
|
# active_key released
|
|
Chk 'S1 active_key released' (((FirstOf "SELECT COUNT(*) FROM assessment_attempts WHERE id=$aid AND active_key IS NULL") -eq 1))
|
|
# non-owner 403
|
|
$sOther = NewStudent
|
|
Chk 'S1 non-owner GET attempt 403/2002' ((Api 'GET' "$BASE/assessments/attempts/$aid" $sOther.token).status -eq 403)
|
|
|
|
# ============ S2 duplicate active (fresh student) ============
|
|
Write-Output '== S2 duplicate active =='
|
|
$s2=NewStudent; $a1=Api 'POST' "$BASE/assessments/attempts" $s2.token @{typeCode='HOLLAND_RIASEC';idempotencyKey=('bb2a-'+[guid]::NewGuid().ToString('N'))}
|
|
Chk 'S2 first create 200' ($a1.status -eq 200) $a1.status
|
|
$dup=Api 'POST' "$BASE/assessments/attempts" $s2.token @{typeCode='HOLLAND_RIASEC';idempotencyKey=('bb2b-'+[guid]::NewGuid().ToString('N'))}
|
|
Chk 'S2 duplicate active -> 409/9708' ($dup.status -eq 409 -and (F $dup.json 'code') -eq 9708) "$($dup.status)/$(F $dup.json 'code')"
|
|
# verify only one DRAFT row in DB
|
|
Chk 'S2 DB only one active' ((FirstOf "SELECT COUNT(*) FROM assessment_attempts WHERE student_user_id=$($s2.id) AND status IN ('DRAFT','IN_PROGRESS') AND active_key IS NOT NULL") -eq 1)
|
|
|
|
# ============ S3 idempotency replay ============
|
|
Write-Output '== S3 idempotency replay =='
|
|
$s3=NewStudent; $kk='bb3-' + [guid]::NewGuid().ToString('N')
|
|
$r1=Api 'POST' "$BASE/assessments/attempts" $s3.token @{typeCode='HOLLAND_RIASEC';idempotencyKey=$kk}
|
|
$r2=Api 'POST' "$BASE/assessments/attempts" $s3.token @{typeCode='HOLLAND_RIASEC';idempotencyKey=$kk}
|
|
Chk 'S3 same key same body -> same attempt' ((F $r1.json.data 'attemptId') -eq (F $r2.json.data 'attemptId')) "$(F $r1.json.data 'attemptId') vs $(F $r2.json.data 'attemptId')"
|
|
|
|
# ============ S4 permissions ============
|
|
Write-Output '== S4 permissions =='
|
|
$pc = Api 'POST' "$BASE/assessments/attempts" $companyTok @{typeCode='HOLLAND_RIASEC';idempotencyKey=('bb4c-'+[guid]::NewGuid().ToString('N'))}
|
|
Chk 'S4 COMPANY create 403/2002' ($pc.status -eq 403 -and (F $pc.json 'code') -eq 2002) "$($pc.status)/$(F $pc.json 'code')"
|
|
$pn = Api 'POST' "$BASE/assessments/attempts" $null @{typeCode='HOLLAND_RIASEC';idempotencyKey=('bb4n-'+[guid]::NewGuid().ToString('N'))}
|
|
Chk 'S4 anonymous create 401' ($pn.status -eq 401) $pn.status
|
|
# INACTIVE: use a REAL inactive definition if present in DB; otherwise SKIP (never use unknown type NOPE as substitute)
|
|
$inactiveType = FirstOf "SELECT type_code FROM assessment_definitions WHERE status='INACTIVE' LIMIT 1"
|
|
if($inactiveType){
|
|
$pi = Api 'POST' "$BASE/assessments/attempts" $tok @{typeCode=$inactiveType;idempotencyKey=('bb4i-'+[guid]::NewGuid().ToString('N'))}
|
|
Chk "S4 INACTIVE($inactiveType) create 404/9701" ($pi.status -eq 404 -and (F $pi.json 'code') -eq 9701) "$($pi.status)/$(F $pi.json 'code')"
|
|
} else { ChkSkip 'S4 INACTIVE type create 404/9701' 'no INACTIVE definition seed in DB; NOT substituting unknown type' }
|
|
# ADMIN black-box coverage: if no ADMIN account exists -> explicit SKIP (not PASS)
|
|
if($adminTok){
|
|
$pa = Api 'POST' "$BASE/assessments/attempts" $adminTok @{typeCode='HOLLAND_RIASEC';idempotencyKey=('bb4a-'+[guid]::NewGuid().ToString('N'))}
|
|
Chk 'S4 ADMIN create 403/2002' ($pa.status -eq 403 -and (F $pa.json 'code') -eq 2002) "$($pa.status)/$(F $pa.json 'code')"
|
|
} else { ChkSkip 'S4 ADMIN create 403/2002' 'no ADMIN account in DB; ADMIN black-box not covered (covered by unit test AssessmentAttemptServiceTest.adminUser_create_returnsForbidden)' }
|
|
|
|
# ============ S5 concurrency (fresh student) ============
|
|
Write-Output '== S5 concurrent create + submit =='
|
|
$s5=NewStudent
|
|
$s5tok = $s5.token; $s5id = $s5.id
|
|
$csBefore = [long](FirstOf "SELECT COUNT(*) FROM candidate_dimension_scores")
|
|
$nThreads=4
|
|
$tmpDir = Join-Path $env:TEMP ('v35bb-' + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
|
|
|
|
# --- concurrent create (different idempotency keys): capture real status + code per request ---
|
|
$createSb = {
|
|
param($base,$tk,$body,$outFile)
|
|
function W($st,$code){ [System.IO.File]::WriteAllText($outFile, ("$st|"+$code)) }
|
|
try {
|
|
$resp = Invoke-WebRequest -Uri "$base/assessments/attempts" -Method Post -Headers @{Authorization="Bearer $tk"} -ContentType 'application/json' -Body $body -UseBasicParsing -TimeoutSec 30
|
|
$code=-1; try { $j=$resp.Content|ConvertFrom-Json; $code=[int]$j.code } catch {}
|
|
W $resp.StatusCode $code
|
|
} catch {
|
|
$st = if($_.Exception.Response -and $_.Exception.Response.StatusCode){[int]$_.Exception.Response.StatusCode}else{-1}
|
|
$code=-1
|
|
if($_.ErrorDetails -and $_.ErrorDetails.Message){ try { $j=$_.ErrorDetails.Message|ConvertFrom-Json; $code=[int]$j.code } catch {} }
|
|
W $st $code
|
|
}
|
|
}
|
|
$pool=[runspacefactory]::CreateRunspacePool(1,$nThreads); $pool.Open()
|
|
$jobs=@()
|
|
foreach($i in 1..$nThreads){
|
|
$body = @{typeCode='HOLLAND_RIASEC';idempotencyKey=('ccr-'+$i+'-'+[guid]::NewGuid().ToString('N'))}|ConvertTo-Json -Compress
|
|
$out = Join-Path $tmpDir ("c$i.txt")
|
|
$rs=[powershell]::Create(); $rs.RunspacePool=$pool
|
|
$null=$rs.AddScript($createSb).AddArgument($BASE).AddArgument($s5tok).AddArgument($body).AddArgument($out)
|
|
$jobs += ,@($rs,$rs.BeginInvoke())
|
|
}
|
|
foreach($j in $jobs){ $j[0].EndInvoke($j[1])|Out-Null; $j[0].Dispose() }
|
|
$pool.Close()
|
|
$cresults=@()
|
|
foreach($i in 1..$nThreads){
|
|
$f=Join-Path $tmpDir ("c$i.txt")
|
|
if(Test-Path $f){
|
|
$p=(Get-Content $f -Raw) -replace '\s+','' -split '\|'
|
|
if($p.Count -ge 2){ $cresults += [pscustomobject]@{status=[int]$p[0];code=[int]$p[1]} }
|
|
}
|
|
}
|
|
$cstat=@($cresults | ForEach-Object {$_.status})
|
|
$ccode=@($cresults | ForEach-Object {$_.code})
|
|
Write-Output ("S5 concurrent create pairs: " + (($cresults | ForEach-Object {"$($_.status)/$($_.code)"}) -join ','))
|
|
$nWinner = @($cresults | Where-Object {$_.status -eq 200}).Count
|
|
$nLoser = @($cresults | Where-Object {$_.status -eq 409 -and $_.code -eq 9708}).Count
|
|
$badCreate = @($cresults | Where-Object {$_.status -ne 200 -and -not ($_.status -eq 409 -and $_.code -eq 9708)}).Count
|
|
Chk 'S5 concurrent create: exactly one 200' ($cresults.Count -eq $nThreads -and $nWinner -eq 1) (($cresults | ForEach-Object {"$($_.status)/$($_.code)"}) -join ',')
|
|
Chk 'S5 concurrent create: every loser is 409/9708' ($nLoser -eq ($nThreads-1) -and $badCreate -eq 0) (($cresults | ForEach-Object {"$($_.status)/$($_.code)"}) -join ',')
|
|
Chk 'S5 concurrent create: no 500/-1/empty' ($cresults.Count -eq $nThreads -and @($cresults|Where-Object{$_.status -eq 500 -or $_.status -eq -1 -or $_.code -eq -1}).Count -eq 0) (($cresults | ForEach-Object {"$($_.status)/$($_.code)"}) -join ',')
|
|
Chk 'S5 DB: exactly one attempt persisted' ((FirstOf "SELECT COUNT(*) FROM assessment_attempts WHERE student_user_id=$s5id") -eq 1)
|
|
Chk 'S5 DB: exactly one active attempt' ((FirstOf "SELECT COUNT(*) FROM assessment_attempts WHERE student_user_id=$s5id AND active_key IS NOT NULL") -eq 1)
|
|
|
|
# --- concurrent submit (2 threads) on the winning active attempt: allowed 200, no 500 ---
|
|
$winAid = FirstOf "SELECT id FROM assessment_attempts WHERE student_user_id=$s5id AND active_key IS NOT NULL LIMIT 1"
|
|
if($winAid){
|
|
$s5g = Api 'GET' "$BASE/assessments/attempts/$winAid" $s5tok
|
|
$s5all = @($s5g.json.data.questions | ForEach-Object { @{questionId=$_.id; value=4} })
|
|
Api 'PATCH' "$BASE/assessments/attempts/$winAid/answers" $s5tok @{version=(F $s5g.json.data 'version');answers=$s5all} | Out-Null
|
|
$submitSb = {
|
|
param($base,$tk,$aid,$outFile)
|
|
function W2($st,$code){ [System.IO.File]::WriteAllText($outFile, ("$st|$code")) }
|
|
try {
|
|
$resp=Invoke-WebRequest -Uri "$base/assessments/attempts/$aid/submit" -Method Post -Headers @{Authorization="Bearer $tk"} -UseBasicParsing -TimeoutSec 30
|
|
$code=-1; try { $j=$resp.Content|ConvertFrom-Json; $code=[int]$j.code } catch {}
|
|
W2 ([int]$resp.StatusCode) $code
|
|
}
|
|
catch {
|
|
$st=if($_.Exception.Response -and $_.Exception.Response.StatusCode){[int]$_.Exception.Response.StatusCode}else{-1}
|
|
$code=-1
|
|
if($_.ErrorDetails -and $_.ErrorDetails.Message){ try { $j=$_.ErrorDetails.Message|ConvertFrom-Json; $code=[int]$j.code } catch {} }
|
|
W2 $st $code
|
|
}
|
|
}
|
|
$pool2=[runspacefactory]::CreateRunspacePool(1,2); $pool2.Open()
|
|
$j2=@(); foreach($i in 1..2){ $out=Join-Path $tmpDir ("s$i.txt"); $rs=[powershell]::Create(); $rs.RunspacePool=$pool2; $null=$rs.AddScript($submitSb).AddArgument($BASE).AddArgument($s5tok).AddArgument($winAid).AddArgument($out); $j2 += ,@($rs,$rs.BeginInvoke()) }
|
|
foreach($j in $j2){ $j[0].EndInvoke($j[1])|Out-Null; $j[0].Dispose() }; $pool2.Close()
|
|
$sresults=@()
|
|
foreach($i in 1..2){
|
|
$f=Join-Path $tmpDir ("s$i.txt")
|
|
if(Test-Path $f){
|
|
$p=(Get-Content $f -Raw) -replace '\s+','' -split '\|'
|
|
if($p.Count -ge 2){ $sresults += [pscustomobject]@{status=[int]$p[0];code=[int]$p[1]} }
|
|
}
|
|
}
|
|
Write-Output ("S5 concurrent submit pairs: " + (($sresults | ForEach-Object {"$($_.status)/$($_.code)"}) -join ','))
|
|
# Submit is idempotent: success is 200; a race-window conflict may only be 409/9703.
|
|
$validSubmit = @($sresults | Where-Object { $_.status -eq 200 -or ($_.status -eq 409 -and $_.code -eq 9703) }).Count
|
|
Chk 'S5 concurrent submit: only 200 or 409/9703' ($sresults.Count -eq 2 -and $validSubmit -eq 2) (($sresults | ForEach-Object {"$($_.status)/$($_.code)"}) -join ',')
|
|
Chk 'S5 concurrent submit: only one result' ((FirstOf "SELECT COUNT(*) FROM assessment_results WHERE attempt_id=$winAid") -eq 1)
|
|
Chk 'S5 concurrent submit: only one audit' ((FirstOf "SELECT COUNT(*) FROM audit_logs WHERE entity_id=$winAid AND action='ASSESSMENT_ATTEMPT_SUBMITTED'") -eq 1)
|
|
} else { ChkSkip 'S5 concurrent submit' 'no winning active attempt found' }
|
|
|
|
$csAfter = [long](FirstOf "SELECT COUNT(*) FROM candidate_dimension_scores")
|
|
Chk 'S5 candidate_dimension_scores unchanged' ($csBefore -eq $csAfter) "$csBefore vs $csAfter"
|
|
|
|
# ============ S6 audit desensitization (scan student audits) ============
|
|
Write-Output '== S6 audit desensitization =='
|
|
$phones = @($s1.phone,$s2.phone,$s3.phone,$s5.phone)
|
|
$bad=''
|
|
$auditRows = @(MySQL "SELECT details FROM audit_logs WHERE user_id IN ($($s1.id),$($s2.id),$($s5.id)) AND action IN ('ASSESSMENT_ATTEMPT_CREATED','ASSESSMENT_ATTEMPT_SUBMITTED') ORDER BY id DESC LIMIT 40")
|
|
foreach($row in $auditRows){
|
|
$hit=$false; foreach($ph in $phones){ if($ph -and $row -like "*$ph*"){ $hit=$true;break } }
|
|
if($hit){ $bad='leaked phone'; break }
|
|
if($row -match '答案|我对|Bearer|password|secret|email=|JWT|accessToken'){ $bad=$row; break }
|
|
}
|
|
Chk 'S6 audit details no phone/email/password/token/stem' ($bad -eq '') $(if($bad){$bad.Substring(0,[Math]::Min(80,$bad.Length))}else{'clean'})
|
|
Chk 'S6 submit audit rows written' (@($auditRows | Where-Object { $_ -match 'answeredCount' }).Count -ge 1)
|
|
|
|
# ============ S7 rabbitmq ============
|
|
Write-Output '== S7 rabbitmq final =='
|
|
$n1=RmqStat 'queue.notification'; $n2=RmqStat 'queue.audit'; $n3=RmqStat 'queue.notification.dlq'
|
|
Chk 'S7 queue.notification ready/unacked=0' ($n1.ready -eq 0 -and $n1.unacked -eq 0) "r=$($n1.ready) u=$($n1.unacked)"
|
|
Chk 'S7 queue.audit ready/unacked=0' ($n2.ready -eq 0 -and $n2.unacked -eq 0) "r=$($n2.ready) u=$($n2.unacked)"
|
|
Chk 'S7 queue.notification.dlq ready/unacked=0' ($n3.ready -eq 0 -and $n3.unacked -eq 0) "r=$($n3.ready) u=$($n3.unacked)"
|
|
|
|
Write-Output "============================================================"
|
|
Write-Output "SUMMARY PASS=$pass FAIL=$fail SKIP=$skip"
|
|
if($failures.Count -gt 0){ Write-Output 'FAILURES:'; $failures|ForEach-Object{Write-Output " - $_"} }else{ Write-Output 'NO FAILURES' }
|
|
|