238 lines
7.3 KiB
PowerShell
238 lines
7.3 KiB
PowerShell
param(
|
|
[string]$ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot ".." )).Path,
|
|
[string]$EnvFile = ".env",
|
|
[string]$SampleImageUrl = "https://files.skinbase.org/img/aa/bb/cc/md.webp",
|
|
[switch]$SkipAnalyze
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Write-Info([string]$Message) {
|
|
Write-Host "[INFO] $Message" -ForegroundColor Cyan
|
|
}
|
|
|
|
function Write-Ok([string]$Message) {
|
|
Write-Host "[OK] $Message" -ForegroundColor Green
|
|
}
|
|
|
|
function Write-Fail([string]$Message) {
|
|
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
|
}
|
|
|
|
function Get-EnvMap([string]$Root, [string]$RelativeEnvFile) {
|
|
$map = @{}
|
|
|
|
$envPath = Join-Path $Root $RelativeEnvFile
|
|
if (-not (Test-Path $envPath)) {
|
|
$fallback = Join-Path $Root ".env.example"
|
|
if (Test-Path $fallback) {
|
|
Write-Info "Env file '$RelativeEnvFile' not found. Falling back to .env.example."
|
|
$envPath = $fallback
|
|
} else {
|
|
throw "Neither '$RelativeEnvFile' nor '.env.example' was found in $Root"
|
|
}
|
|
}
|
|
|
|
Get-Content -Path $envPath | ForEach-Object {
|
|
$line = $_.Trim()
|
|
if ($line -eq "" -or $line.StartsWith("#")) { return }
|
|
$idx = $line.IndexOf("=")
|
|
if ($idx -lt 1) { return }
|
|
|
|
$key = $line.Substring(0, $idx).Trim()
|
|
$val = $line.Substring($idx + 1).Trim()
|
|
|
|
if ($val.StartsWith('"') -and $val.EndsWith('"') -and $val.Length -ge 2) {
|
|
$val = $val.Substring(1, $val.Length - 2)
|
|
}
|
|
|
|
if (-not $map.ContainsKey($key)) {
|
|
$map[$key] = $val
|
|
}
|
|
}
|
|
|
|
return $map
|
|
}
|
|
|
|
function Get-Setting([hashtable]$Map, [string]$Key, [string]$Default = "") {
|
|
$fromProcess = [Environment]::GetEnvironmentVariable($Key)
|
|
if (-not [string]::IsNullOrWhiteSpace($fromProcess)) {
|
|
return $fromProcess.Trim()
|
|
}
|
|
|
|
if ($Map.ContainsKey($Key)) {
|
|
return [string]$Map[$Key]
|
|
}
|
|
|
|
return $Default
|
|
}
|
|
|
|
function Test-Truthy([string]$Value, [bool]$Default = $false) {
|
|
if ([string]::IsNullOrWhiteSpace($Value)) { return $Default }
|
|
|
|
switch ($Value.Trim().ToLowerInvariant()) {
|
|
"1" { return $true }
|
|
"true" { return $true }
|
|
"yes" { return $true }
|
|
"on" { return $true }
|
|
"0" { return $false }
|
|
"false" { return $false }
|
|
"no" { return $false }
|
|
"off" { return $false }
|
|
default { return $Default }
|
|
}
|
|
}
|
|
|
|
function Join-Url([string]$Base, [string]$Path) {
|
|
$left = $Base.TrimEnd('/')
|
|
$right = $Path.TrimStart('/')
|
|
return "$left/$right"
|
|
}
|
|
|
|
function Invoke-Health([string]$Name, [string]$BaseUrl) {
|
|
if ([string]::IsNullOrWhiteSpace($BaseUrl)) {
|
|
throw "$Name base URL is empty"
|
|
}
|
|
|
|
$url = Join-Url $BaseUrl "/health"
|
|
Write-Info "Checking $Name health: $url"
|
|
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $url -Method GET -TimeoutSec 10 -UseBasicParsing
|
|
} catch {
|
|
throw "$Name health request failed: $($_.Exception.Message)"
|
|
}
|
|
|
|
if ($response.StatusCode -lt 200 -or $response.StatusCode -ge 300) {
|
|
throw "$Name health returned status $($response.StatusCode)"
|
|
}
|
|
|
|
Write-Ok "$Name health check passed"
|
|
}
|
|
|
|
function Invoke-Analyze([string]$ClipBaseUrl, [string]$AnalyzeEndpoint, [string]$ImageUrl) {
|
|
if ([string]::IsNullOrWhiteSpace($ClipBaseUrl)) {
|
|
throw "CLIP base URL is empty"
|
|
}
|
|
|
|
$url = Join-Url $ClipBaseUrl $AnalyzeEndpoint
|
|
Write-Info "Running sample CLIP analyze call: $url"
|
|
|
|
$payload = @{
|
|
image_url = $ImageUrl
|
|
} | ConvertTo-Json -Depth 4
|
|
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $url -Method POST -ContentType "application/json" -Body $payload -TimeoutSec 15 -UseBasicParsing
|
|
} catch {
|
|
throw "CLIP analyze request failed: $($_.Exception.Message)"
|
|
}
|
|
|
|
if ($response.StatusCode -lt 200 -or $response.StatusCode -ge 300) {
|
|
throw "CLIP analyze returned status $($response.StatusCode)"
|
|
}
|
|
|
|
$json = $null
|
|
try {
|
|
$json = $response.Content | ConvertFrom-Json
|
|
} catch {
|
|
throw "CLIP analyze response is not valid JSON"
|
|
}
|
|
|
|
$hasTags = $false
|
|
if ($json -is [System.Collections.IEnumerable] -and -not ($json -is [string])) {
|
|
$hasTags = $true
|
|
}
|
|
if ($null -ne $json.tags) { $hasTags = $true }
|
|
if ($null -ne $json.data) { $hasTags = $true }
|
|
|
|
if (-not $hasTags) {
|
|
throw "CLIP analyze response does not contain expected tags/data payload"
|
|
}
|
|
|
|
Write-Ok "Sample CLIP analyze call passed"
|
|
}
|
|
|
|
function Test-NonBlockingPublish([string]$Root) {
|
|
Write-Info "Validating non-blocking publish path (code assertions)"
|
|
|
|
$uploadController = Join-Path $Root "app/Http/Controllers/Api/UploadController.php"
|
|
$derivativesJob = Join-Path $Root "app/Jobs/GenerateDerivativesJob.php"
|
|
|
|
if (-not (Test-Path $uploadController)) { throw "Missing file: $uploadController" }
|
|
if (-not (Test-Path $derivativesJob)) { throw "Missing file: $derivativesJob" }
|
|
|
|
$uploadText = Get-Content -Raw -Path $uploadController
|
|
$jobText = Get-Content -Raw -Path $derivativesJob
|
|
|
|
if ($uploadText -notmatch "AutoTagArtworkJob::dispatch\(") {
|
|
throw "UploadController does not dispatch AutoTagArtworkJob"
|
|
}
|
|
|
|
if ($jobText -notmatch "AutoTagArtworkJob::dispatch\(") {
|
|
throw "GenerateDerivativesJob does not dispatch AutoTagArtworkJob"
|
|
}
|
|
|
|
if ($uploadText -match "dispatchSync\(" -or $jobText -match "dispatchSync\(") {
|
|
throw "Found dispatchSync in publish path; auto-tagging must remain async"
|
|
}
|
|
|
|
if ($uploadText -match "Illuminate\\Support\\Facades\\Http" -or $uploadText -match "Http::") {
|
|
throw "UploadController appears to call external vision HTTP directly"
|
|
}
|
|
|
|
Write-Ok "Non-blocking publish path validation passed"
|
|
}
|
|
|
|
$failed = $false
|
|
|
|
try {
|
|
Write-Info "Vision smoke test starting"
|
|
Write-Info "Project root: $ProjectRoot"
|
|
|
|
$envMap = Get-EnvMap -Root $ProjectRoot -RelativeEnvFile $EnvFile
|
|
|
|
$visionEnabled = Test-Truthy (Get-Setting -Map $envMap -Key "VISION_ENABLED" -Default "true") $true
|
|
if (-not $visionEnabled) {
|
|
throw "VISION_ENABLED=false. Vision integration is disabled; smoke check cannot continue."
|
|
}
|
|
|
|
$clipBaseUrl = Get-Setting -Map $envMap -Key "CLIP_BASE_URL"
|
|
$clipAnalyzeEndpoint = Get-Setting -Map $envMap -Key "CLIP_ANALYZE_ENDPOINT" -Default "/analyze"
|
|
|
|
$yoloEnabled = Test-Truthy (Get-Setting -Map $envMap -Key "YOLO_ENABLED" -Default "true") $true
|
|
$yoloBaseUrl = Get-Setting -Map $envMap -Key "YOLO_BASE_URL"
|
|
|
|
Invoke-Health -Name "CLIP" -BaseUrl $clipBaseUrl
|
|
|
|
if ($yoloEnabled) {
|
|
if ([string]::IsNullOrWhiteSpace($yoloBaseUrl)) {
|
|
Write-Info "YOLO is enabled but YOLO_BASE_URL is empty; skipping YOLO /health check."
|
|
} else {
|
|
Invoke-Health -Name "YOLO" -BaseUrl $yoloBaseUrl
|
|
}
|
|
} else {
|
|
Write-Info "YOLO is disabled; skipping YOLO /health check."
|
|
}
|
|
|
|
if ($SkipAnalyze) {
|
|
Write-Info "Skipping sample analyze call (SkipAnalyze set)."
|
|
} else {
|
|
Invoke-Analyze -ClipBaseUrl $clipBaseUrl -AnalyzeEndpoint $clipAnalyzeEndpoint -ImageUrl $SampleImageUrl
|
|
}
|
|
|
|
Test-NonBlockingPublish -Root $ProjectRoot
|
|
|
|
Write-Ok "Vision smoke test completed successfully"
|
|
} catch {
|
|
$failed = $true
|
|
Write-Fail $_.Exception.Message
|
|
}
|
|
|
|
if ($failed) {
|
|
exit 1
|
|
}
|
|
|
|
exit 0
|