255 lines
8.1 KiB
PowerShell
255 lines
8.1 KiB
PowerShell
<#
|
||
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
|
||
}
|
||
|