240 lines
8.2 KiB
PowerShell
240 lines
8.2 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Alias("h")]
|
|
[switch]$Help,
|
|
[string]$ApiHost = "127.0.0.1",
|
|
[int]$Port = 3101,
|
|
[string]$Log = "warn,tower_http=warn",
|
|
[int]$StartupTimeoutSeconds = 30
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Write-Usage {
|
|
@(
|
|
'Usage:'
|
|
' ./server-rs/scripts/smoke.ps1'
|
|
' ./server-rs/scripts/smoke.ps1 -ApiHost 127.0.0.1 -Port 3201 -Log "info,tower_http=info"'
|
|
''
|
|
'Notes:'
|
|
' 1. Build api-server and start an ephemeral local process'
|
|
' 2. Verify /healthz raw payload, response headers, and envelope contract'
|
|
' 3. Stop the temporary process automatically after the smoke checks finish'
|
|
) -join [Environment]::NewLine
|
|
}
|
|
|
|
function Assert-Condition {
|
|
param(
|
|
[bool]$Condition,
|
|
[string]$Message
|
|
)
|
|
|
|
if (-not $Condition) {
|
|
throw $Message
|
|
}
|
|
}
|
|
|
|
function Set-InheritedEnvVar {
|
|
param(
|
|
[string]$Name,
|
|
[string]$Value,
|
|
[hashtable]$Snapshot
|
|
)
|
|
|
|
if (-not $Snapshot.ContainsKey($Name)) {
|
|
$Snapshot[$Name] = [Environment]::GetEnvironmentVariable($Name, "Process")
|
|
}
|
|
|
|
[Environment]::SetEnvironmentVariable($Name, $Value, "Process")
|
|
}
|
|
|
|
function Restore-InheritedEnvVars {
|
|
param([hashtable]$Snapshot)
|
|
|
|
foreach ($entry in $Snapshot.GetEnumerator()) {
|
|
[Environment]::SetEnvironmentVariable($entry.Key, $entry.Value, "Process")
|
|
}
|
|
}
|
|
|
|
function Get-HeaderValue {
|
|
param(
|
|
$Headers,
|
|
[string]$Name
|
|
)
|
|
|
|
if ($null -eq $Headers) {
|
|
return $null
|
|
}
|
|
|
|
return $Headers[$Name]
|
|
}
|
|
|
|
function Wait-ForHealthz {
|
|
param(
|
|
[string]$Uri,
|
|
[int]$TimeoutSeconds,
|
|
$Process
|
|
)
|
|
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
$lastError = $null
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
if ($Process.HasExited) {
|
|
throw "api-server exited before /healthz became ready."
|
|
}
|
|
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $Uri -UseBasicParsing -TimeoutSec 2
|
|
if ($response.StatusCode -eq 200) {
|
|
return
|
|
}
|
|
|
|
$lastError = "Unexpected status code $($response.StatusCode)"
|
|
}
|
|
catch {
|
|
$lastError = $_.Exception.Message
|
|
}
|
|
|
|
Start-Sleep -Milliseconds 300
|
|
}
|
|
|
|
throw "Timed out waiting for /healthz readiness. Last error: $lastError"
|
|
}
|
|
|
|
function Write-ProcessLogs {
|
|
param(
|
|
[string]$StdoutLog,
|
|
[string]$StderrLog
|
|
)
|
|
|
|
if (Test-Path $StdoutLog) {
|
|
Write-Host "[server-rs:smoke] stdout:"
|
|
Get-Content -Path $StdoutLog
|
|
}
|
|
|
|
if (Test-Path $StderrLog) {
|
|
Write-Host "[server-rs:smoke] stderr:"
|
|
Get-Content -Path $StderrLog
|
|
}
|
|
}
|
|
|
|
if ($Help) {
|
|
Write-Usage
|
|
exit 0
|
|
}
|
|
|
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$serverRsDir = Split-Path -Parent $scriptDir
|
|
$manifestPath = Join-Path $serverRsDir "Cargo.toml"
|
|
$binaryPath = Join-Path $serverRsDir "target\debug\api-server.exe"
|
|
$baseUrl = "http://$ApiHost`:$Port"
|
|
$healthzUrl = "$baseUrl/healthz"
|
|
$runId = [Guid]::NewGuid().ToString("N")
|
|
$stdoutLog = Join-Path $env:TEMP "genarrative-server-rs-smoke-$runId.stdout.log"
|
|
$stderrLog = Join-Path $env:TEMP "genarrative-server-rs-smoke-$runId.stderr.log"
|
|
$envSnapshot = @{}
|
|
$serverProcess = $null
|
|
$shouldKeepLogs = $false
|
|
|
|
if (-not (Test-Path $manifestPath)) {
|
|
throw "Missing server-rs/Cargo.toml, cannot start smoke script."
|
|
}
|
|
|
|
Push-Location $serverRsDir
|
|
try {
|
|
Write-Host "[server-rs:smoke] step: cargo build -p api-server"
|
|
cargo build -p api-server --manifest-path $manifestPath
|
|
|
|
Assert-Condition (Test-Path $binaryPath) "Missing api-server binary at $binaryPath after cargo build."
|
|
|
|
Set-InheritedEnvVar -Name "GENARRATIVE_API_HOST" -Value $ApiHost -Snapshot $envSnapshot
|
|
Set-InheritedEnvVar -Name "GENARRATIVE_API_PORT" -Value "$Port" -Snapshot $envSnapshot
|
|
Set-InheritedEnvVar -Name "GENARRATIVE_API_LOG" -Value $Log -Snapshot $envSnapshot
|
|
|
|
Write-Host "[server-rs:smoke] step: start api-server binary"
|
|
$serverProcess = Start-Process `
|
|
-FilePath $binaryPath `
|
|
-WorkingDirectory $serverRsDir `
|
|
-PassThru `
|
|
-RedirectStandardOutput $stdoutLog `
|
|
-RedirectStandardError $stderrLog
|
|
|
|
Restore-InheritedEnvVars -Snapshot $envSnapshot
|
|
|
|
Write-Host "[server-rs:smoke] step: wait for /healthz readiness"
|
|
Wait-ForHealthz -Uri $healthzUrl -TimeoutSeconds $StartupTimeoutSeconds -Process $serverProcess
|
|
|
|
$rawRequestId = "smoke-healthz-raw"
|
|
Write-Host "[server-rs:smoke] step: verify raw /healthz contract"
|
|
$rawResponse = Invoke-WebRequest `
|
|
-Uri $healthzUrl `
|
|
-Headers @{ "x-request-id" = $rawRequestId } `
|
|
-UseBasicParsing `
|
|
-TimeoutSec 5
|
|
$rawBody = $rawResponse.Content | ConvertFrom-Json
|
|
|
|
Assert-Condition ($rawResponse.StatusCode -eq 200) "Raw /healthz did not return HTTP 200."
|
|
Assert-Condition ($rawBody.ok -eq $true) "Raw /healthz body is missing ok=true."
|
|
Assert-Condition ($rawBody.service -eq "genarrative-node-server") "Raw /healthz body returned an unexpected service name."
|
|
|
|
$apiVersionHeader = Get-HeaderValue -Headers $rawResponse.Headers -Name "x-api-version"
|
|
$routeVersionHeader = Get-HeaderValue -Headers $rawResponse.Headers -Name "x-route-version"
|
|
$requestIdHeader = Get-HeaderValue -Headers $rawResponse.Headers -Name "x-request-id"
|
|
$responseTimeHeader = Get-HeaderValue -Headers $rawResponse.Headers -Name "x-response-time-ms"
|
|
$responseTimeValue = 0
|
|
|
|
Assert-Condition ($requestIdHeader -eq $rawRequestId) "Raw /healthz did not echo x-request-id."
|
|
Assert-Condition (-not [string]::IsNullOrWhiteSpace($apiVersionHeader)) "Raw /healthz is missing x-api-version."
|
|
Assert-Condition ($routeVersionHeader -eq $apiVersionHeader) "Raw /healthz x-route-version is not aligned with x-api-version."
|
|
Assert-Condition (
|
|
[int]::TryParse(($responseTimeHeader | ForEach-Object { $_ }), [ref]$responseTimeValue)
|
|
) "Raw /healthz x-response-time-ms is not a valid integer."
|
|
Assert-Condition ($responseTimeValue -ge 0) "Raw /healthz x-response-time-ms must be >= 0."
|
|
|
|
$envelopeRequestId = "smoke-healthz-envelope"
|
|
Write-Host "[server-rs:smoke] step: verify envelope /healthz contract"
|
|
$envelopeResponse = Invoke-WebRequest `
|
|
-Uri $healthzUrl `
|
|
-Headers @{
|
|
"x-request-id" = $envelopeRequestId
|
|
"x-genarrative-response-envelope" = "1"
|
|
} `
|
|
-UseBasicParsing `
|
|
-TimeoutSec 5
|
|
$envelopeBody = $envelopeResponse.Content | ConvertFrom-Json
|
|
|
|
Assert-Condition ($envelopeResponse.StatusCode -eq 200) "Envelope /healthz did not return HTTP 200."
|
|
Assert-Condition ($envelopeBody.ok -eq $true) "Envelope /healthz body is missing ok=true."
|
|
Assert-Condition ($null -eq $envelopeBody.error) "Envelope /healthz should return error=null."
|
|
Assert-Condition ($envelopeBody.data.ok -eq $true) "Envelope /healthz data.ok should be true."
|
|
Assert-Condition ($envelopeBody.data.service -eq "genarrative-node-server") "Envelope /healthz returned an unexpected service name."
|
|
Assert-Condition ($envelopeBody.meta.apiVersion -eq $apiVersionHeader) "Envelope /healthz meta.apiVersion does not match x-api-version."
|
|
Assert-Condition ($envelopeBody.meta.requestId -eq $envelopeRequestId) "Envelope /healthz meta.requestId did not echo x-request-id."
|
|
Assert-Condition ($envelopeBody.meta.routeVersion -eq $routeVersionHeader) "Envelope /healthz meta.routeVersion does not match x-route-version."
|
|
Assert-Condition ($envelopeBody.meta.operation -eq "GET /healthz") "Envelope /healthz meta.operation is unexpected."
|
|
Assert-Condition ($envelopeBody.meta.latencyMs -ge 0) "Envelope /healthz meta.latencyMs must be >= 0."
|
|
|
|
Write-Host "[server-rs:smoke] all checks passed"
|
|
}
|
|
catch {
|
|
$shouldKeepLogs = $true
|
|
Write-ProcessLogs -StdoutLog $stdoutLog -StderrLog $stderrLog
|
|
throw
|
|
}
|
|
finally {
|
|
Restore-InheritedEnvVars -Snapshot $envSnapshot
|
|
|
|
if ($null -ne $serverProcess -and -not $serverProcess.HasExited) {
|
|
Stop-Process -Id $serverProcess.Id -Force
|
|
$serverProcess.WaitForExit()
|
|
}
|
|
|
|
Pop-Location
|
|
|
|
if (-not $shouldKeepLogs) {
|
|
Remove-Item -LiteralPath $stdoutLog -ErrorAction SilentlyContinue
|
|
Remove-Item -LiteralPath $stderrLog -ErrorAction SilentlyContinue
|
|
}
|
|
}
|