gephyr 1.16.18

Gephyr is a headless local AI relay/proxy API handling OpenAI, Claude, and Gemini-compatible APIs
Documentation
<#
.SYNOPSIS
Smoke-test the dedicated proxy-pool strategy endpoint.

.DESCRIPTION
Validates that `POST /api/proxy/pool/strategy` can update only proxy-pool
strategy without full `/api/config` writes, and that the change is visible via
`GET /api/proxy/pool/strategy`.

.PARAMETER Port
Proxy port. Default: 8045.

.PARAMETER ContainerName
Docker container name used by console.ps1. Default: gephyr.

.PARAMETER Image
Docker image tag used by console.ps1. Default: gephyr:latest.

.PARAMETER DataDir
Gephyr data directory passed to console.ps1.

.PARAMETER TargetStrategy
Desired strategy to test. Default: round_robin.
Allowed: round_robin, random, priority, least_connections, weighted_round_robin.

.PARAMETER SkipStart
Use currently running server instead of starting via console.ps1.

.PARAMETER KeepChange
Do not restore original strategy after the test.

.PARAMETER Help
Print usage/examples and exit.

.EXAMPLE
.\scripts\test-proxy-pool-strategy.ps1

.EXAMPLE
.\scripts\test-proxy-pool-strategy.ps1 -TargetStrategy weighted_round_robin

.EXAMPLE
.\scripts\test-proxy-pool-strategy.ps1 -SkipStart -KeepChange

.EXAMPLE
Get-Help .\scripts\test-proxy-pool-strategy.ps1 -Detailed
#>
param(
    [int]$Port = 8045,
    [string]$ContainerName = "gephyr",
    [string]$Image = "gephyr:latest",
    [string]$DataDir = "$env:USERPROFILE\.gephyr",
    [string]$TargetStrategy = "round_robin",
    [switch]$SkipStart,
    [switch]$KeepChange,
    [Alias("h", "?")]
    [switch]$Help
)

$ErrorActionPreference = "Stop"

$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$ConsoleScript = Join-Path $RepoRoot "console.ps1"
$EnvFile = Join-Path $RepoRoot ".env.local"
$BaseUrl = "http://127.0.0.1:$Port"
$AllowedStrategies = @(
    "round_robin",
    "random",
    "priority",
    "least_connections",
    "weighted_round_robin"
)

function Show-Usage {
    Write-Host ""
    Write-Host "Usage:" -ForegroundColor Cyan
    Write-Host "  .\scripts\test-proxy-pool-strategy.ps1 [options]"
    Write-Host ""
    Write-Host "Common examples:" -ForegroundColor Cyan
    Write-Host "  .\scripts\test-proxy-pool-strategy.ps1"
    Write-Host "  .\scripts\test-proxy-pool-strategy.ps1 -TargetStrategy weighted_round_robin"
    Write-Host "  .\scripts\test-proxy-pool-strategy.ps1 -SkipStart -KeepChange"
    Write-Host ""
    Write-Host "Options:" -ForegroundColor Cyan
    Write-Host "  -Port <int>                     Default: 8045"
    Write-Host "  -TargetStrategy <string>        Default: round_robin"
    Write-Host "  -SkipStart                      Uses currently running server"
    Write-Host "  -KeepChange                     Do not restore original strategy"
    Write-Host "  -Help                           Print this usage"
    Write-Host ""
    Write-Host "PowerShell native help:" -ForegroundColor Cyan
    Write-Host "  Get-Help .\scripts\test-proxy-pool-strategy.ps1 -Detailed"
}

function Write-Section {
    param([string]$Title)
    Write-Host ""
    Write-Host ("=" * 76) -ForegroundColor Cyan
    Write-Host $Title -ForegroundColor Cyan
    Write-Host ("=" * 76) -ForegroundColor Cyan
}

function Write-Step {
    param(
        [int]$Number,
        [string]$Title
    )
    Write-Host ""
    Write-Host ("[{0}] {1}" -f $Number, $Title) -ForegroundColor Yellow
}

function Load-EnvLocal {
    if (-not (Test-Path $EnvFile)) {
        return
    }

    foreach ($raw in Get-Content $EnvFile) {
        $line = $raw.Trim()
        if (-not $line -or $line.StartsWith("#") -or -not $line.Contains("=")) {
            continue
        }
        $parts = $line.Split("=", 2)
        $name = $parts[0].Trim()
        $value = $parts[1].Trim().Trim('"').Trim("'")
        if ($name -and $value -and -not (Get-Item "Env:$name" -ErrorAction SilentlyContinue)) {
            Set-Item -Path "Env:$name" -Value $value
        }
    }
}

function Get-AuthHeaders {
    if (-not $env:API_KEY) {
        throw "Missing API_KEY. Set env var or add it to .env.local."
    }
    return @{ Authorization = "Bearer $($env:API_KEY)" }
}

function Assert-DockerReady {
    if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
        Write-Host ""
        Write-Host "Docker CLI not found in PATH." -ForegroundColor Red
        Write-Host ""
        Write-Host "Install Docker:" -ForegroundColor Yellow
        Write-Host "  Docker Desktop : https://docs.docker.com/desktop/install/windows-install/"
        Write-Host "  winget         : winget install Docker.DockerDesktop"
        Write-Host "  choco          : choco install docker-desktop"
        throw "Missing prerequisite: docker"
    }
    docker info *> $null
    if ($LASTEXITCODE -ne 0) {
        throw "Docker daemon is not reachable. Start Docker Desktop first."
    }
}

function Wait-ServiceReady {
    param(
        [int]$Attempts = 50,
        [int]$DelayMs = 500
    )

    $headers = Get-AuthHeaders
    for ($i = 0; $i -lt $Attempts; $i++) {
        try {
            $resp = Invoke-WebRequest -Uri "$BaseUrl/health" -Headers $headers -UseBasicParsing -TimeoutSec 3
            if ($resp.StatusCode -eq 200) {
                return $true
            }
        } catch {
            Start-Sleep -Milliseconds $DelayMs
        }
    }
    return $false
}

function Start-Server {
    & $ConsoleScript -Command start -EnableAdminApi -Port $Port -ContainerName $ContainerName -Image $Image -DataDir $DataDir
}

function Api-Get {
    param([string]$Path)
    $headers = Get-AuthHeaders
    Invoke-RestMethod -Uri "$BaseUrl$Path" -Headers $headers -Method Get -TimeoutSec 30
}

function Api-PostJson {
    param(
        [string]$Path,
        [object]$Payload
    )
    $headers = Get-AuthHeaders
    $headers["Content-Type"] = "application/json"
    $json = $Payload | ConvertTo-Json -Depth 20
    Invoke-RestMethod -Uri "$BaseUrl$Path" -Headers $headers -Method Post -Body $json -TimeoutSec 30
}

function Select-AlternateStrategy {
    param([string]$Current)
    foreach ($candidate in $AllowedStrategies) {
        if ($candidate -ne $Current) {
            return $candidate
        }
    }
    return $Current
}

if ($Help.IsPresent) {
    Show-Usage
    return
}

Write-Section "Gephyr Proxy-Pool Strategy Endpoint Smoke Test"
Write-Host "This script validates GET/POST /api/proxy/pool/strategy behavior." -ForegroundColor Gray

if (-not (Test-Path $ConsoleScript)) {
    throw "console.ps1 not found at $ConsoleScript"
}

Load-EnvLocal
Assert-DockerReady
[void](Get-AuthHeaders)

$normalizedTarget = $TargetStrategy.Trim().ToLowerInvariant()
if ($AllowedStrategies -notcontains $normalizedTarget) {
    throw "Invalid TargetStrategy '$TargetStrategy'. Allowed: $($AllowedStrategies -join ', ')"
}

$originalStrategy = $null
$updatedStrategy = $null
$restoreAttempted = $false

try {
    if (-not $SkipStart.IsPresent) {
        Write-Step 1 "Start server with admin API enabled"
        Start-Server
        if (-not (Wait-ServiceReady)) {
            throw "Service did not become ready on $BaseUrl"
        }
        Write-Host "Service is ready." -ForegroundColor Green
    } else {
        Write-Step 1 "Using running server (SkipStart)"
        if (-not (Wait-ServiceReady -Attempts 5 -DelayMs 300)) {
            throw "Server is not reachable on $BaseUrl."
        }
        Write-Host "Service is reachable." -ForegroundColor Green
    }

    Write-Step 2 "Verify route capability"
    $caps = Api-Get "/api/version/routes"
    if (-not $caps.routes.'GET /api/proxy/pool/strategy' -or -not $caps.routes.'POST /api/proxy/pool/strategy') {
        throw "Running image does not expose GET/POST /api/proxy/pool/strategy."
    }
    Write-Host ("Running version: {0}" -f $caps.version) -ForegroundColor Green

    Write-Step 3 "Read current strategy snapshot"
    $before = Api-Get "/api/proxy/pool/strategy"
    $originalStrategy = [string]$before.strategy
    if ([string]::IsNullOrWhiteSpace($originalStrategy)) {
        throw "Current strategy is missing from response."
    }
    Write-Host ("Current strategy: {0}" -f $originalStrategy) -ForegroundColor Gray
    Write-Host ("Pool enabled: {0}, auto_failover: {1}, health_check_interval: {2}" -f $before.enabled, $before.auto_failover, $before.health_check_interval) -ForegroundColor Gray

    if ($normalizedTarget -eq $originalStrategy) {
        $normalizedTarget = Select-AlternateStrategy -Current $originalStrategy
        Write-Host ("Target matched current; using alternate strategy: {0}" -f $normalizedTarget) -ForegroundColor Yellow
    }

    Write-Step 4 "Update strategy via dedicated endpoint"
    $post = Api-PostJson -Path "/api/proxy/pool/strategy" -Payload @{
        strategy = $normalizedTarget
    }
    if (-not $post.ok -or -not $post.saved) {
        throw "Strategy update endpoint did not return success payload."
    }
    $updatedStrategy = [string]$post.proxy_pool.strategy
    Write-Host ("Updated strategy: {0}" -f $updatedStrategy) -ForegroundColor Green

    Write-Step 5 "Verify persisted runtime snapshot"
    $after = Api-Get "/api/proxy/pool/strategy"
    if ([string]$after.strategy -ne $normalizedTarget) {
        throw "Expected strategy '$normalizedTarget', got '$($after.strategy)'."
    }
    Write-Host ("Verified strategy after update: {0}" -f $after.strategy) -ForegroundColor Green

    Write-Step 6 "Result summary"
    Write-Host ("Original strategy: {0}" -f $originalStrategy)
    Write-Host ("Target strategy:   {0}" -f $normalizedTarget)
    Write-Host ("Final strategy:    {0}" -f $after.strategy)
    Write-Host ""
    Write-Host "PASS: Dedicated pool-strategy endpoint is working." -ForegroundColor Green
}
finally {
    if (-not $KeepChange.IsPresent -and $originalStrategy -and $updatedStrategy -and ($originalStrategy -ne $updatedStrategy)) {
        try {
            $restoreAttempted = $true
            Api-PostJson -Path "/api/proxy/pool/strategy" -Payload @{ strategy = $originalStrategy } | Out-Null
            Write-Host ("Restored original strategy: {0}" -f $originalStrategy) -ForegroundColor Gray
        } catch {
            Write-Host "Warning: failed to restore original strategy automatically." -ForegroundColor Yellow
        }
    } elseif ($KeepChange.IsPresent) {
        Write-Host "KeepChange set: strategy left as updated value." -ForegroundColor Gray
    } elseif ($restoreAttempted) {
        Write-Host "Restore attempt completed." -ForegroundColor Gray
    }
}