|
| 1 | +# Test-RateLimits.ps1 |
| 2 | +# Verifies Rate Limiting for Tokens, App Creation, Key Creation, and Penalties. |
| 3 | + |
| 4 | +$GatewayUrl = "http://localhost:8000" |
| 5 | +$Username = "ratetestuser_$(Get-Random)" |
| 6 | +$Password = "TestPass123!" |
| 7 | + |
| 8 | +function Invoke-RestMethodWithMetrics { |
| 9 | + param( |
| 10 | + [string]$Uri, |
| 11 | + [string]$Method, |
| 12 | + [hashtable]$Headers, |
| 13 | + [object]$Body, |
| 14 | + [bool]$SkipError = $true |
| 15 | + ) |
| 16 | + try { |
| 17 | + if ($Body) { |
| 18 | + $jsonBody = $Body | ConvertTo-Json -Depth 10 |
| 19 | + return Invoke-RestMethod -Uri $Uri -Method $Method -Headers $Headers -Body $jsonBody -ContentType "application/json" -ErrorAction Stop |
| 20 | + } else { |
| 21 | + return Invoke-RestMethod -Uri $Uri -Method $Method -Headers $Headers -ContentType "application/json" -ErrorAction Stop |
| 22 | + } |
| 23 | + } catch { |
| 24 | + if ($SkipError) { |
| 25 | + if ($_.Exception.Response) { |
| 26 | + # Attempt to read the error stream |
| 27 | + $stream = $_.Exception.Response.GetResponseStream() |
| 28 | + if ($stream) { |
| 29 | + $reader = New-Object System.IO.StreamReader($stream) |
| 30 | + $errorBody = $reader.ReadToEnd() |
| 31 | + try { |
| 32 | + # Add a fake property to mimic Invoke-RestMethod object so downstream checks pass |
| 33 | + $errObj = $errorBody | ConvertFrom-Json |
| 34 | + # We need to attach StatusCode somehow or just return the raw PSCustomObject |
| 35 | + # But our checks look for .StatusCode or .status_code property validation wrapper |
| 36 | + # Let's return a custom object |
| 37 | + return [PSCustomObject]@{ |
| 38 | + StatusCode = [int]$_.Exception.Response.StatusCode |
| 39 | + status_code = [int]$_.Exception.Response.StatusCode # For compatibility |
| 40 | + Body = $errObj |
| 41 | + Raw = $errorBody |
| 42 | + IsError = $true |
| 43 | + } |
| 44 | + } catch { |
| 45 | + return [PSCustomObject]@{ |
| 46 | + StatusCode = [int]$_.Exception.Response.StatusCode |
| 47 | + status_code = [int]$_.Exception.Response.StatusCode |
| 48 | + Body = $errorBody |
| 49 | + IsError = $true |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | + return $_.Exception.Response |
| 54 | + } else { |
| 55 | + Write-Host "Error request failed with no response object: $($_.Exception.Message)" -ForegroundColor Red |
| 56 | + return $null |
| 57 | + } |
| 58 | + } else { |
| 59 | + throw $_ |
| 60 | + } |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +Write-Host "--- Rate Limit Verification Script ---" -ForegroundColor Cyan |
| 65 | + |
| 66 | +# 1. Setup User & Auth |
| 67 | +Write-Host "`n[Setup] Creating User and Logging in..." |
| 68 | +$userBody = @{ email = "$Username@example.com"; password = $Password; full_name = "Rate Test User" } |
| 69 | +$signup = Invoke-RestMethodWithMetrics -Uri "$GatewayUrl/api/v1/auth/register" -Method Post -Body $userBody -SkipError $false |
| 70 | +Write-Host "User created: $($signup.id)" |
| 71 | + |
| 72 | +$loginBody = @{ username = "$Username@example.com"; password = $Password } |
| 73 | +# Note: Login expects form-data usually, but let's try JSON or adjust if needed. EagleEye auth often uses OAuth2 form. |
| 74 | +# If Gateway proxies /api/v1/auth/login, it might expect form data. |
| 75 | +$formBody = "username=$Username@example.com&password=$Password" |
| 76 | +try { |
| 77 | + $tokenResponse = Invoke-RestMethod -Uri "$GatewayUrl/api/v1/auth/login" -Method Post -Body $formBody -ContentType "application/x-www-form-urlencoded" |
| 78 | +} catch { |
| 79 | + Write-Error "Login failed. Ensure Gateway is running and proxies to EagleEye." |
| 80 | + exit 1 |
| 81 | +} |
| 82 | +$token = $tokenResponse.access_token |
| 83 | +$authHeader = @{ "Authorization" = "Bearer $token" } |
| 84 | +Write-Host "Got Token." |
| 85 | + |
| 86 | +# 2. App Creation Limit Test (Limit: 2) |
| 87 | +Write-Host "`n[Test 1] App Creation Limit (Target: 2)" |
| 88 | +for ($i = 1; $i -le 3; $i++) { |
| 89 | + $rand = Get-Random |
| 90 | + $appBody = @{ name = "App_$($i)_$rand"; description = "Test App" } |
| 91 | + $response = Invoke-RestMethodWithMetrics -Uri "$GatewayUrl/api/v1/apps/" -Method Post -Headers $authHeader -Body $appBody |
| 92 | + |
| 93 | + if ($null -eq $response) { continue } |
| 94 | + if ($response.IsError -eq $true -or $response.GetType().Name -eq "HttpResponseMessageWrapper") { |
| 95 | + # Error response |
| 96 | + $code = if ($response.StatusCode) { $response.StatusCode } else { $response.status_code } |
| 97 | + if ([int]$code -eq 429) { |
| 98 | + Write-Host "[$i] Request blocked as expected (429)." -ForegroundColor Green |
| 99 | + } else { |
| 100 | + Write-Host "[$i] Request failed with unexpected code: $code" -ForegroundColor Red |
| 101 | + Write-Host "DEBUG Info: Type=$($response.GetType().Name)" -ForegroundColor DarkGray |
| 102 | + # Attempt to print body if exists |
| 103 | + try { Write-Host "DEBUG Body: $($response.Body | ConvertTo-Json -Depth 2)" -ForegroundColor DarkGray } catch {} |
| 104 | + } |
| 105 | + } else { |
| 106 | + Write-Host "[$i] App created: $($response.id)" -ForegroundColor Yellow |
| 107 | + if ($i -eq 1) { $global:appId = $response.id; $global:appName = $response.name } |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +# 3. Key Creation Limit Test (Limit: 4) |
| 112 | +Write-Host "`n[Test 2] Key Creation Limit (Target: 4)" |
| 113 | +if (-not $global:appId) { |
| 114 | + Write-Warning "Skipping Test 2: No App ID available from Test 1." |
| 115 | +} else { |
| 116 | + # Use the first app created |
| 117 | + for ($i = 1; $i -le 5; $i++) { |
| 118 | + $keyBody = @{ name = "Key_$i" } |
| 119 | + $response = Invoke-RestMethodWithMetrics -Uri "$GatewayUrl/api/v1/apps/$global:appId/keys" -Method Post -Headers $authHeader -Body $keyBody |
| 120 | + |
| 121 | + if ($null -eq $response) { continue } |
| 122 | + if ($response.IsError -eq $true -or $response.GetType().Name -eq "HttpResponseMessageWrapper") { |
| 123 | + $code = if ($response.StatusCode) { $response.StatusCode } else { $response.status_code } |
| 124 | + if ([int]$code -eq 429) { |
| 125 | + Write-Host "[$i] Request blocked as expected (429)." -ForegroundColor Green |
| 126 | + } else { |
| 127 | + Write-Host "[$i] Request failed with unexpected code: $code" -ForegroundColor Red |
| 128 | + } |
| 129 | + } else { |
| 130 | + Write-Host "[$i] Key created: $($response.key_prefix)..." -ForegroundColor Yellow |
| 131 | + if ($i -eq 1) { $global:apiKey = $response.api_key; $global:keyId = $response.id } |
| 132 | + } |
| 133 | +} |
| 134 | +} |
| 135 | + |
| 136 | +# 4. Token Limit & Penalty Test |
| 137 | +Write-Host "`n[Test 3] Token Usage & Penalty (Limit: 5k/5min, 2 Strikes)" |
| 138 | +if (-not $global:apiKey) { |
| 139 | + Write-Warning "Skipping Test 3: No API Key available from Test 2." |
| 140 | +} else { |
| 141 | +$headers = @{ "X-API-Key" = $global:apiKey; "Content-Type" = "application/json" } |
| 142 | +$chatBody = @{ |
| 143 | + query = "Write a 500 word story about a space adventure to Mars." |
| 144 | + model = "gemini-3-flash-preview" |
| 145 | + moderation = "moderate" |
| 146 | + max_output_tokens = 1000 |
| 147 | +} |
| 148 | + |
| 149 | +# We need to loop until we hit 10k tokens. |
| 150 | +# Assuming each request uses ~100 tokens. 100 requests. |
| 151 | +$simulatedTokens = 0 |
| 152 | +$limit = 5000 |
| 153 | +$count = 0 |
| 154 | + |
| 155 | +while ($simulatedTokens -lt $limit + 2000) { # Go a bit over |
| 156 | + $count++ |
| 157 | + $response = Invoke-RestMethodWithMetrics -Uri "$GatewayUrl/chat/" -Method Post -Headers $headers -Body $chatBody |
| 158 | + |
| 159 | + if ($null -eq $response) { continue } |
| 160 | + if ($response.IsError -eq $true -or $response.GetType().Name -eq "HttpResponseMessageWrapper") { |
| 161 | + $code = if ($response.StatusCode) { $response.StatusCode } else { $response.status_code } |
| 162 | + |
| 163 | + if ([int]$code -eq 429) { |
| 164 | + Write-Host "[$count] Rate Limit Hit (429)!" -ForegroundColor Green |
| 165 | + # Checking penalty |
| 166 | + # To trigger penalty (disable key), we need to hit 429 TWICE. |
| 167 | + # We just hit it once. We should wait a second and hit it again. |
| 168 | + Start-Sleep -Seconds 1 |
| 169 | + Write-Host "Attempting to trigger 2nd strike..." |
| 170 | + $response2 = Invoke-RestMethodWithMetrics -Uri "$GatewayUrl/chat/" -Method Post -Headers $headers -Body $chatBody |
| 171 | + $code2 = if ($response2.StatusCode) { $response2.StatusCode } else { $response2.status_code } |
| 172 | + |
| 173 | + if ([int]$code2 -eq 429) { |
| 174 | + Write-Host "2nd Strike recorded." -ForegroundColor Green |
| 175 | + } |
| 176 | + |
| 177 | + # Now, 3rd attempt should be 403 Forbidden (Key Disabled) |
| 178 | + Write-Host "Verifying Key Disabling..." |
| 179 | + $response3 = Invoke-RestMethodWithMetrics -Uri "$GatewayUrl/chat/" -Method Post -Headers $headers -Body $chatBody |
| 180 | + $code3 = if ($response3.StatusCode) { $response3.StatusCode } else { $response3.status_code } |
| 181 | + |
| 182 | + if ([int]$code3 -eq 403) { |
| 183 | + Write-Host "SUCCESS: Key has been disabled (403: Blocked by app)." -ForegroundColor Green |
| 184 | + |
| 185 | + # Try to print the message |
| 186 | + if ($response3.detail) { |
| 187 | + Write-Host "Message: $($response3.detail)" -ForegroundColor Cyan |
| 188 | + } elseif ($response3.message) { |
| 189 | + Write-Host "Message: $($response3.message)" -ForegroundColor Cyan |
| 190 | + } else { |
| 191 | + # Fallback for raw stream reading if needed, but Invoke-RestMethod parsed it |
| 192 | + Write-Host "Response Body: $($response3 | ConvertTo-Json -Depth 1 -Compress)" -ForegroundColor Cyan |
| 193 | + } |
| 194 | + break |
| 195 | + } else { |
| 196 | + Write-Host "FAILURE: Key was not disabled. Code: $code3" -ForegroundColor Red |
| 197 | + break |
| 198 | + } |
| 199 | + } else { |
| 200 | + Write-Host "[$count] Failed: $code" -ForegroundColor Red |
| 201 | + Write-Host "DEBUG Info: Type=$($response.GetType().Name)" -ForegroundColor DarkGray |
| 202 | + try { Write-Host "DEBUG Body: $($response.Body | ConvertTo-Json -Depth 2)" -ForegroundColor DarkGray } catch {} |
| 203 | + break |
| 204 | + } |
| 205 | + } else { |
| 206 | + # Valid response |
| 207 | + $usage = $response.metrics.token_usage.total_tokens |
| 208 | + $simulatedTokens += $usage |
| 209 | + Write-Host "[$count] OK. Used $usage tokens. Total: $simulatedTokens / $limit" -NoNewline |
| 210 | + if ($count % 5 -eq 0) { Write-Host "" } else { Write-Host " | " -NoNewline } |
| 211 | + } |
| 212 | +} |
| 213 | +} |
| 214 | + |
| 215 | +Write-Host "`nTest Complete." |
0 commit comments