Files
aza/AzA march 2026 - Kopie/deploy/smoke_suite.ps1

255 lines
8.1 KiB
PowerShell
Raw Normal View History

2026-03-25 13:42:48 +01:00
<#
AZA Step 13: Local Sanity / Regression Smoke Suite (PowerShell 5.1 safe)
What it checks (no token leakage):
1) GET /health -> 200 and JSON {"ok": true} (or ok truthy)
2) GET /version -> 200 and JSON has "name" and "build"
3) GET /license/status WITHOUT token -> 401/403 expected
4) GET /license/status WITH token -> 200 and JSON has {valid:boolean, valid_until:number|null}
5) GET /stripe/health -> 200 if present, otherwise WARN if 404
6) GET /openapi.json -> 200 (optional, but useful)
Run (while backend is running on 127.0.0.1:8000):
powershell -ExecutionPolicy Bypass -File .\deploy\smoke_suite.ps1
Optional:
powershell -ExecutionPolicy Bypass -File .\deploy\smoke_suite.ps1 -BaseUrl http://127.0.0.1:8000
#>
[CmdletBinding()]
param(
[string]$BaseUrl = "http://127.0.0.1:8000",
[string]$EnvFile = ".\deploy\.env",
[string]$DeviceId = ""
)
function Load-DotEnv([string]$Path) {
if (-not (Test-Path -LiteralPath $Path)) {
throw "Missing .env file at: $Path"
}
$map = @{}
Get-Content -LiteralPath $Path | ForEach-Object {
$line = $_.Trim()
if ($line.Length -eq 0) { return }
if ($line.StartsWith("#")) { return }
$idx = $line.IndexOf("=")
if ($idx -lt 1) { return }
$k = $line.Substring(0, $idx).Trim()
$v = $line.Substring($idx + 1).Trim()
if (($v.StartsWith('"') -and $v.EndsWith('"')) -or ($v.StartsWith("'") -and $v.EndsWith("'"))) {
$v = $v.Substring(1, $v.Length - 2)
}
$map[$k] = $v
}
return $map
}
function All-TokensFromValue([string]$value) {
if (-not $value) { return @() }
$value = $value.Trim()
return ($value -split "[,\r\n]+" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" })
}
function Get-TokensFromEnv([hashtable]$envMap) {
if ($envMap.ContainsKey("MEDWORK_API_TOKENS")) { return @{ src="MEDWORK_API_TOKENS"; tokens=(All-TokensFromValue $envMap["MEDWORK_API_TOKENS"]) } }
if ($envMap.ContainsKey("MEDWORK_API_TOKEN")) { return @{ src="MEDWORK_API_TOKEN"; tokens=(All-TokensFromValue $envMap["MEDWORK_API_TOKEN"]) } }
return @{ src=""; tokens=@() }
}
function Http-Get([string]$url, [hashtable]$headers = $null) {
try {
$resp = Invoke-WebRequest -Method GET -Uri $url -Headers $headers -TimeoutSec 15 -UseBasicParsing
return @{ ok=$true; status=[int]$resp.StatusCode; text=$resp.Content }
} catch {
$e = $_.Exception
$status = $null
$text = ""
if ($e.Response -and $e.Response.StatusCode) {
$status = [int]$e.Response.StatusCode
try {
$stream = $e.Response.GetResponseStream()
if ($stream) {
$reader = New-Object System.IO.StreamReader($stream)
$text = $reader.ReadToEnd()
}
} catch { }
}
return @{ ok=$false; status=$status; text=$text; message=$e.Message }
}
}
function Http-GetRest([string]$url, [hashtable]$headers) {
# Use Invoke-RestMethod for auth calls (matches authorized_test.ps1 behavior in PS5.1)
try {
$obj = Invoke-RestMethod -Method GET -Uri $url -Headers $headers -TimeoutSec 15
$txt = $obj | ConvertTo-Json -Depth 10 -Compress
return @{ ok=$true; status=200; text=$txt }
} catch {
$e = $_.Exception
$status = $null
$text = ""
if ($e.Response -and $e.Response.StatusCode) {
$status = [int]$e.Response.StatusCode
try {
$stream = $e.Response.GetResponseStream()
if ($stream) {
$reader = New-Object System.IO.StreamReader($stream)
$text = $reader.ReadToEnd()
}
} catch { }
}
return @{ ok=$false; status=$status; text=$text; message=$e.Message }
}
}
function Parse-Json([string]$s) {
try { return (ConvertFrom-Json -InputObject $s -ErrorAction Stop) } catch { return $null }
}
function Assert([bool]$cond, [string]$msg, [ref]$failCount) {
if ($cond) {
Write-Host ("PASS: " + $msg)
return $true
} else {
Write-Host ("FAIL: " + $msg)
$failCount.Value++
return $false
}
}
function Warn([string]$msg) {
Write-Host ("WARN: " + $msg)
}
function Snip([string]$s, [int]$n = 220) {
if (-not $s) { return "" }
$t = $s.Trim()
if ($t.Length -le $n) { return $t }
return ($t.Substring(0, $n) + " ...")
}
$script:lastDetails = @()
function Note-FailDetail([string]$label, $resp) {
$st = $resp.status
$tx = Snip $resp.text
$script:lastDetails += (" " + $label + ": status=" + $st + " body='" + $tx + "'")
}
$base = $BaseUrl.TrimEnd("/")
if (-not $DeviceId) {
$DeviceId = "ps-" + $env:COMPUTERNAME + "-" + $env:USERNAME
}
Write-Host "[AZA] Smoke Suite (Step 13)"
Write-Host (" BaseUrl: " + $base)
Write-Host (" EnvFile: " + $EnvFile)
Write-Host (" DeviceId: " + $DeviceId)
Write-Host ""
$fail = 0
# Load tokens (but never print them)
$envMap = $null
try { $envMap = Load-DotEnv $EnvFile } catch { $envMap = @{} }
$tk = Get-TokensFromEnv $envMap
if (-not $tk.tokens -or $tk.tokens.Count -lt 1) {
Warn "No tokens found in .env (MEDWORK_API_TOKENS / MEDWORK_API_TOKEN). Authorized checks will fail."
}
# 1) /health
$r = Http-Get "$base/health"
$health200 = ($r.status -eq 200)
if (-not $health200) { Note-FailDetail "/health" $r }
Assert ($health200) "GET /health returns 200" ([ref]$fail) | Out-Null
if ($health200) {
$j = Parse-Json $r.text
if (-not $j) {
Warn "GET /health did not parse as JSON (still OK if your health is plain text)."
} elseif ($j.PSObject.Properties.Name -contains "ok") {
if (-not ($j.ok -eq $true -or $j.ok -eq 1)) {
Warn "GET /health JSON has field ok, but it is not true."
}
}
}
Write-Host ""
# 2) /version
$r = Http-Get "$base/version"
$versionOk = $false
if ($r.status -eq 200) {
$j = Parse-Json $r.text
if ($j -and $j.name -and $j.build -ne $null) { $versionOk = $true }
}
if (-not $versionOk) { Note-FailDetail "/version" $r }
Assert ($versionOk) "GET /version returns 200 with fields {name, build}" ([ref]$fail) | Out-Null
Write-Host ""
# 3) /license/status without token should be 401/403
$r = Http-Get "$base/license/status"
$unauthOk = ($r.status -eq 401 -or $r.status -eq 403)
Assert ($unauthOk) "GET /license/status without token returns 401/403" ([ref]$fail) | Out-Null
Write-Host ""
# 4) /license/status with token should be 200 with stable schema
$authOk = $false
$lastAuthResp = $null
if ($tk.tokens -and $tk.tokens.Count -gt 0) {
foreach ($t in $tk.tokens) {
$h = @{
"X-API-Token" = $t
"X-Device-Id" = $DeviceId
"Accept" = "application/json"
}
$r = Http-GetRest "$base/license/status" $h
$lastAuthResp = $r
if ($r.status -eq 200) {
$j = Parse-Json $r.text
if ($j -and ($j.valid -is [bool]) -and ($j.PSObject.Properties.Name -contains "valid_until")) {
$vu = $j.valid_until
$vuOk = ($vu -eq $null) -or ($vu -is [int]) -or ($vu -is [long]) -or ($vu -is [double])
if ($vuOk) { $authOk = $true; break }
}
}
if ($r.status -eq 401 -or $r.status -eq 403) { continue }
if ($r.status -eq 404) { break }
}
}
if (-not $authOk -and $lastAuthResp) { Note-FailDetail "/license/status (auth)" $lastAuthResp }
Assert ($authOk) "GET /license/status with token returns 200 and schema {valid, valid_until}" ([ref]$fail) | Out-Null
Write-Host ""
# 5) /stripe/health (optional: warn on 404)
$r = Http-Get "$base/stripe/health"
if ($r.status -eq 200) {
Write-Host "PASS: GET /stripe/health returns 200"
} elseif ($r.status -eq 404) {
Warn "GET /stripe/health returned 404 (endpoint not present in this build)"
} else {
Assert $false ("GET /stripe/health returns 200 (or 404 if not included), got " + $r.status) ([ref]$fail) | Out-Null
}
Write-Host ""
# 6) /openapi.json (optional but useful)
$r = Http-Get "$base/openapi.json"
if ($r.status -eq 200) {
Write-Host "PASS: GET /openapi.json returns 200"
} elseif ($r.status -eq 404) {
Warn "GET /openapi.json returned 404"
} else {
Warn ("GET /openapi.json unexpected status: " + $r.status)
}
Write-Host ""
if ($fail -eq 0) {
Write-Host "RESULT: PASS (Step 13 smoke suite)"
exit 0
} else {
Write-Host ("RESULT: FAIL (" + $fail + " failing check(s))")
if ($script:lastDetails.Count -gt 0) {
Write-Host "Failure details (status + short body snippet):"
$script:lastDetails | ForEach-Object { Write-Host $_ }
}
exit 1
}