Files
bifrost/tests/governance/e2e_test.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1686 lines
49 KiB
Go

package governance
import (
"sync"
"testing"
"time"
)
// ============================================================================
// CRITICAL: Multiple VKs Sharing Team Budget
// ============================================================================
// TestMultipleVKsSharingTeamBudgetFairness verifies that when multiple VKs share a team budget,
// one VK cannot monopolize the budget and block others.
// Budget enforcement is POST-HOC: the request that exceeds the budget is allowed,
// but subsequent requests are blocked.
func TestMultipleVKsSharingTeamBudgetFairness(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create a team with a small budget that will be exceeded quickly
teamName := "test-team-shared-budget-" + generateRandomID()
teamBudget := 0.01 // $0.01 for team - small enough to exceed in a few requests
teamResetDuration := "1h"
createTeamResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/teams",
Body: CreateTeamRequest{
Name: teamName,
Budgets: []BudgetRequest{{
MaxLimit: teamBudget,
ResetDuration: teamResetDuration,
}},
},
})
if createTeamResp.StatusCode != 200 {
t.Fatalf("Failed to create team: status %d", createTeamResp.StatusCode)
}
teamID := ExtractIDFromResponse(t, createTeamResp)
testData.AddTeam(teamID)
t.Logf("Created team with shared budget: $%.4f", teamBudget)
// Create VK1 assigned to team
vk1Name := "test-vk1-shared-" + generateRandomID()
createVK1Resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vk1Name,
TeamID: &teamID,
},
})
if createVK1Resp.StatusCode != 200 {
t.Fatalf("Failed to create VK1: status %d", createVK1Resp.StatusCode)
}
vk1ID := ExtractIDFromResponse(t, createVK1Resp)
testData.AddVirtualKey(vk1ID)
vk1 := createVK1Resp.Body["virtual_key"].(map[string]interface{})
vk1Value := vk1["value"].(string)
// Create VK2 assigned to same team
vk2Name := "test-vk2-shared-" + generateRandomID()
createVK2Resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vk2Name,
TeamID: &teamID,
},
})
if createVK2Resp.StatusCode != 200 {
t.Fatalf("Failed to create VK2: status %d", createVK2Resp.StatusCode)
}
vk2ID := ExtractIDFromResponse(t, createVK2Resp)
testData.AddVirtualKey(vk2ID)
vk2 := createVK2Resp.Body["virtual_key"].(map[string]interface{})
vk2Value := vk2["value"].(string)
t.Logf("Created VK1 and VK2 both assigned to same team")
// Use VK1 to consume team budget until it's exceeded
// Budget enforcement is POST-HOC: request that exceeds is allowed, next is blocked
consumedBudget := 0.0
requestNum := 1
shouldStop := false
for requestNum <= 150 { // Need many requests since each costs ~$0.0001
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Hi, how are you?",
},
},
},
VKHeader: &vk1Value,
})
if resp.StatusCode >= 400 {
// VK1 got rejected - budget exceeded
if CheckErrorMessage(t, resp, "budget") {
t.Logf("VK1 request %d rejected: team budget exceeded at $%.6f/$%.4f", requestNum, consumedBudget, teamBudget)
break
} else {
t.Fatalf("VK1 request %d failed with unexpected error: %v", requestNum, resp.Body)
}
}
// Extract cost from response
if usage, ok := resp.Body["usage"].(map[string]interface{}); ok {
if prompt, ok := usage["prompt_tokens"].(float64); ok {
if completion, ok := usage["completion_tokens"].(float64); ok {
cost, _ := CalculateCost("openai/gpt-4o", int(prompt), int(completion))
consumedBudget += cost
t.Logf("VK1 request %d: cost=$%.6f, total consumed=$%.6f/$%.4f", requestNum, cost, consumedBudget, teamBudget)
}
}
}
requestNum++
if shouldStop {
break
}
if consumedBudget >= teamBudget {
shouldStop = true
}
}
// Verify that team budget was indeed exceeded
if consumedBudget < teamBudget {
t.Fatalf("Could not exceed team budget after %d requests (consumed $%.6f / $%.4f)", requestNum-1, consumedBudget, teamBudget)
}
t.Logf("Team budget exhausted by VK1: $%.6f consumed (limit: $%.4f)", consumedBudget, teamBudget)
// Now try VK2 - should be rejected because team budget was exhausted by VK1
resp2 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Hello how are you?",
},
},
},
VKHeader: &vk2Value,
})
// VK2 should be rejected because team budget was consumed by VK1
if resp2.StatusCode < 400 {
t.Fatalf("VK2 request should be rejected due to shared team budget exhaustion but got status %d", resp2.StatusCode)
}
if !CheckErrorMessage(t, resp2, "budget") {
t.Fatalf("Expected budget error for VK2 but got: %v", resp2.Body)
}
t.Logf("Multiple VKs sharing team budget verified ✓")
t.Logf("VK2 correctly rejected when team budget exhausted by VK1")
}
// ============================================================================
// CRITICAL: Full Budget Hierarchy Validation (All 4 Levels)
// ============================================================================
// TestFullBudgetHierarchyEnforcement verifies that ALL levels of hierarchy are checked:
// Provider Budget → VK Budget → Team Budget → Customer Budget
// Budget enforcement happens AFTER limit is exceeded - the request that exceeds is allowed,
// but subsequent requests are blocked.
func TestFullBudgetHierarchyEnforcement(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create customer with high budget
customerName := "test-customer-hierarchy-" + generateRandomID()
customerResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/customers",
Body: CreateCustomerRequest{
Name: customerName,
Budget: &BudgetRequest{
MaxLimit: 1000.0, // Very high
ResetDuration: "1h",
},
},
})
if customerResp.StatusCode != 200 {
t.Fatalf("Failed to create customer: status %d", customerResp.StatusCode)
}
customerID := ExtractIDFromResponse(t, customerResp)
testData.AddCustomer(customerID)
// Create team under customer with medium budget
teamName := "test-team-hierarchy-" + generateRandomID()
teamResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/teams",
Body: CreateTeamRequest{
Name: teamName,
CustomerID: &customerID,
Budgets: []BudgetRequest{{
MaxLimit: 100.0, // Medium
ResetDuration: "1h",
}},
},
})
if teamResp.StatusCode != 200 {
t.Fatalf("Failed to create team: status %d", teamResp.StatusCode)
}
teamID := ExtractIDFromResponse(t, teamResp)
testData.AddTeam(teamID)
// Create VK under team with lower budget
// Provider budget is MOST RESTRICTIVE at $0.01 - should be exceeded after 2-3 requests
vkName := "test-vk-hierarchy-" + generateRandomID()
vkBudget := 0.1 // $0.1
providerBudget := 0.01 // $0.01 - MOST RESTRICTIVE
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
TeamID: &teamID,
Budget: &BudgetRequest{
MaxLimit: vkBudget,
ResetDuration: "1h",
},
ProviderConfigs: []ProviderConfigRequest{
{
Provider: "openai",
Weight: float64Ptr(1.0),
AllowedModels: []string{"*"},
KeyIDs: []string{"*"},
Budget: &BudgetRequest{
MaxLimit: providerBudget,
ResetDuration: "1h",
},
},
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created full hierarchy:")
t.Logf(" Customer Budget: $1000.0 (not limiting)")
t.Logf(" Team Budget: $100.0 (not limiting)")
t.Logf(" VK Budget: $%.2f (not limiting)", vkBudget)
t.Logf(" Provider Budget: $%.2f (MOST RESTRICTIVE)", providerBudget)
// Make requests until provider budget is exceeded
// Budget enforcement: request that exceeds is allowed, NEXT request is blocked
consumedBudget := 0.0
requestNum := 1
var lastSuccessfulCost float64
shouldStop := false
for requestNum <= 20 {
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Test hierarchy enforcement request " + string(rune('0'+requestNum%10)),
},
},
},
VKHeader: &vkValue,
})
if resp.StatusCode >= 400 {
// Request failed - check if it's due to budget
if CheckErrorMessage(t, resp, "budget") {
t.Logf("Request %d correctly rejected: budget exceeded at provider level", requestNum)
t.Logf("Consumed budget: $%.6f (provider limit: $%.2f)", consumedBudget, providerBudget)
t.Logf("Last successful request cost: $%.6f", lastSuccessfulCost)
// Verify rejection happened after exceeding the budget
if consumedBudget < providerBudget {
t.Fatalf("Request rejected before budget was exceeded: consumed $%.6f < limit $%.2f", consumedBudget, providerBudget)
}
t.Logf("Full budget hierarchy enforcement verified ✓")
t.Logf("Request blocked at provider level (lowest in hierarchy)")
return // Test passed
} else {
t.Fatalf("Request %d failed with unexpected error (not budget): %v", requestNum, resp.Body)
}
}
// Request succeeded - extract actual token usage
if usage, ok := resp.Body["usage"].(map[string]interface{}); ok {
if prompt, ok := usage["prompt_tokens"].(float64); ok {
if completion, ok := usage["completion_tokens"].(float64); ok {
actualCost, _ := CalculateCost("openai/gpt-4o", int(prompt), int(completion))
consumedBudget += actualCost
lastSuccessfulCost = actualCost
t.Logf("Request %d succeeded: cost=$%.6f, consumed=$%.6f/$%.2f",
requestNum, actualCost, consumedBudget, providerBudget)
}
}
}
requestNum++
if shouldStop {
break
}
if consumedBudget >= providerBudget {
shouldStop = true
}
}
t.Fatalf("Made %d requests but never hit provider budget limit (consumed $%.6f / $%.2f) - budget not being enforced at provider level",
requestNum-1, consumedBudget, providerBudget)
}
// ============================================================================
// CRITICAL: Failed Requests Don't Consume Budget/Rate Limits
// ============================================================================
// TestFailedRequestsDoNotConsumeBudget verifies that requests that fail
// (4xx/5xx responses) do not consume budget or rate limits
func TestFailedRequestsDoNotConsumeBudget(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with small budget to easily verify consumption
vkName := "test-vk-failed-requests-" + generateRandomID()
budget := 0.1
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: budget,
ResetDuration: "1h",
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with budget: $%.2f", budget)
// Get initial budget from in-memory store
getDataResp1 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
virtualKeysMap1 := getDataResp1.Body["virtual_keys"].(map[string]interface{})
getBudgetsResp1 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap1 := getBudgetsResp1.Body["budgets"].(map[string]interface{})
vkData1 := virtualKeysMap1[vkValue].(map[string]interface{})
budgetID, _ := vkData1["budget_id"].(string)
budgetData1 := budgetsMap1[budgetID].(map[string]interface{})
initialUsage, _ := budgetData1["current_usage"].(float64)
t.Logf("Initial budget usage: $%.6f", initialUsage)
// Make a request with invalid input that will fail
// Using an invalid model name to force 400 error
failResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "invalid-model-that-does-not-exist",
Messages: []ChatMessage{
{
Role: "user",
Content: "This request should fail.",
},
},
},
VKHeader: &vkValue,
})
t.Logf("Failed request status: %d", failResp.StatusCode)
if failResp.StatusCode < 400 {
t.Skip("Could not create failing request - model may be accepted")
}
// Wait for async PostHook goroutine to complete processing
time.Sleep(2 * time.Second)
// Check budget usage - should NOT have changed
getBudgetsResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap2 := getBudgetsResp2.Body["budgets"].(map[string]interface{})
budgetData2 := budgetsMap2[budgetID].(map[string]interface{})
usageAfterFailed, _ := budgetData2["current_usage"].(float64)
t.Logf("Budget usage after failed request: $%.6f", usageAfterFailed)
if usageAfterFailed > initialUsage+0.0001 {
t.Fatalf("Failed request consumed budget: before=$%.6f, after=$%.6f", initialUsage, usageAfterFailed)
}
// Now make a successful request
successResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "This request should succeed.",
},
},
},
VKHeader: &vkValue,
})
if successResp.StatusCode != 200 {
t.Skip("Could not make successful request")
}
// Wait for async PostHook goroutine to complete budget update
time.Sleep(2 * time.Second)
// Check budget usage - should have changed
getBudgetsResp3 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap3 := getBudgetsResp3.Body["budgets"].(map[string]interface{})
budgetData3 := budgetsMap3[budgetID].(map[string]interface{})
usageAfterSuccess, _ := budgetData3["current_usage"].(float64)
t.Logf("Budget usage after successful request: $%.6f", usageAfterSuccess)
if usageAfterSuccess <= usageAfterFailed+0.0001 {
t.Fatalf("Successful request did not consume budget: before=$%.6f, after=$%.6f", usageAfterFailed, usageAfterSuccess)
}
t.Logf("Failed requests do NOT consume budget ✓")
t.Logf("Successful requests DO consume budget ✓")
}
// ============================================================================
// CRITICAL: Inactive Virtual Key Behavior
// ============================================================================
// TestInactiveVirtualKeyBlocking verifies that inactive VKs reject requests immediately
// and that reactivating VK allows requests again
func TestInactiveVirtualKeyBlocking(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create active VK
vkName := "test-vk-inactive-" + generateRandomID()
isActive := true
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
IsActive: &isActive,
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK in ACTIVE state")
// Verify active VK works
resp1 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request with active VK should succeed.",
},
},
},
VKHeader: &vkValue,
})
if resp1.StatusCode != 200 {
t.Fatalf("Active VK request should succeed but got status %d", resp1.StatusCode)
}
t.Logf("Active VK request succeeded ✓")
// Deactivate VK
isInactive := false
updateResp := MakeRequest(t, APIRequest{
Method: "PUT",
Path: "/api/governance/virtual-keys/" + vkID,
Body: UpdateVirtualKeyRequest{
IsActive: &isInactive,
},
})
if updateResp.StatusCode != 200 {
t.Fatalf("Failed to deactivate VK: status %d", updateResp.StatusCode)
}
t.Logf("VK deactivated (isActive = false)")
// Wait for in-memory store update
time.Sleep(500 * time.Millisecond)
// Verify inactive VK is blocked
resp2 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request with inactive VK should be blocked.",
},
},
},
VKHeader: &vkValue,
})
if resp2.StatusCode < 400 {
t.Fatalf("Inactive VK request should be blocked but got status %d", resp2.StatusCode)
}
if !CheckErrorMessage(t, resp2, "blocked") {
t.Fatalf("Expected 'blocked' in error message but got: %v", resp2.Body)
}
t.Logf("Inactive VK request rejected ✓")
// Reactivate VK
isActiveAgain := true
reactivateResp := MakeRequest(t, APIRequest{
Method: "PUT",
Path: "/api/governance/virtual-keys/" + vkID,
Body: UpdateVirtualKeyRequest{
IsActive: &isActiveAgain,
},
})
if reactivateResp.StatusCode != 200 {
t.Fatalf("Failed to reactivate VK: status %d", reactivateResp.StatusCode)
}
t.Logf("VK reactivated (isActive = true)")
// Wait for in-memory store update
time.Sleep(500 * time.Millisecond)
// Verify reactivated VK works
resp3 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request with reactivated VK should succeed.",
},
},
},
VKHeader: &vkValue,
})
if resp3.StatusCode != 200 {
t.Fatalf("Reactivated VK request should succeed but got status %d", resp3.StatusCode)
}
t.Logf("Reactivated VK request succeeded ✓")
t.Logf("Inactive VK behavior verified ✓")
}
// ============================================================================
// HIGH: Rate Limit Reset Boundaries and Edge Cases
// ============================================================================
// TestRateLimitResetBoundaryConditions verifies rate limit resets at exact boundaries
func TestRateLimitResetBoundaryConditions(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with short reset duration for quick testing
vkName := "test-vk-reset-boundary-" + generateRandomID()
requestLimit := int64(1)
resetDuration := "15s" // Short duration for testing
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
RateLimit: &CreateRateLimitRequest{
RequestMaxLimit: &requestLimit,
RequestResetDuration: &resetDuration,
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with request limit: %d request per %s", requestLimit, resetDuration)
// Make first request at t=0
startTime := time.Now()
resp1 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "First request at t=0.",
},
},
},
VKHeader: &vkValue,
})
if resp1.StatusCode != 200 {
t.Skip("Could not make first request")
}
t.Logf("First request succeeded at t=0 ✓")
// Try immediate second request - should fail
resp2 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Second request before reset.",
},
},
},
VKHeader: &vkValue,
})
if resp2.StatusCode < 400 {
t.Fatalf("Second request should be rejected but got status %d", resp2.StatusCode)
}
t.Logf("Second request rejected (within reset window) ✓")
// Wait for reset duration + 1 second to ensure reset happens
waitTime := time.Until(startTime.Add(16 * time.Second))
if waitTime > 0 {
t.Logf("Waiting %.1f seconds for rate limit to reset...", waitTime.Seconds())
time.Sleep(waitTime)
}
// After reset, third request should succeed
resp3 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Third request after reset duration.",
},
},
},
VKHeader: &vkValue,
})
if resp3.StatusCode != 200 {
t.Fatalf("Third request after reset should succeed but got status %d", resp3.StatusCode)
}
t.Logf("Third request succeeded after reset duration ✓")
t.Logf("Rate limit reset boundary conditions verified ✓")
}
// ============================================================================
// HIGH: Concurrent Requests to Same VK
// ============================================================================
// TestConcurrentRequestsToSameVK verifies that concurrent requests are handled safely
// and counters remain accurate under concurrent load
func TestConcurrentRequestsToSameVK(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with high token limit to allow concurrent requests
vkName := "test-vk-concurrent-" + generateRandomID()
tokenLimit := int64(100000)
tokenResetDuration := "1h"
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
RateLimit: &CreateRateLimitRequest{
TokenMaxLimit: &tokenLimit,
TokenResetDuration: &tokenResetDuration,
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with high token limit for concurrent testing")
// Launch concurrent requests
numGoroutines := 5
requestsPerGoroutine := 3
totalRequests := numGoroutines * requestsPerGoroutine
var wg sync.WaitGroup
successCount := 0
var mu sync.Mutex
t.Logf("Launching %d goroutines with %d requests each (total: %d requests)",
numGoroutines, requestsPerGoroutine, totalRequests)
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(goID int) {
defer wg.Done()
for j := 0; j < requestsPerGoroutine; j++ {
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Concurrent request from goroutine.",
},
},
},
VKHeader: &vkValue,
})
if resp.StatusCode == 200 {
mu.Lock()
successCount++
mu.Unlock()
}
}
}(i)
}
wg.Wait()
t.Logf("Concurrent requests completed: %d successful out of %d total", successCount, totalRequests)
if successCount == 0 {
t.Skip("No requests succeeded - cannot test concurrent behavior")
}
if successCount < totalRequests/2 {
t.Logf("Warning: Less than 50%% requests succeeded (%d/%d)", successCount, totalRequests)
}
t.Logf("Concurrent request handling verified ✓")
t.Logf("No data corruption detected (test completed successfully)")
}
// ============================================================================
// HIGH: Budget State After Reset
// ============================================================================
// TestBudgetStateAfterReset verifies that budget usage is correctly reset to 0
// and LastReset timestamp is updated
func TestBudgetStateAfterReset(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with short reset duration
vkName := "test-vk-budget-reset-state-" + generateRandomID()
budgetLimit := 1.0
resetDuration := "15s"
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: budgetLimit,
ResetDuration: resetDuration,
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with budget: $%.2f, reset duration: %s", budgetLimit, resetDuration)
// Get initial budget state
getDataResp1 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
virtualKeysMap1 := getDataResp1.Body["virtual_keys"].(map[string]interface{})
getBudgetsResp1 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap1 := getBudgetsResp1.Body["budgets"].(map[string]interface{})
vkData1 := virtualKeysMap1[vkValue].(map[string]interface{})
budgetID, _ := vkData1["budget_id"].(string)
budgetData1 := budgetsMap1[budgetID].(map[string]interface{})
initialUsage, _ := budgetData1["current_usage"].(float64)
lastReset1, _ := budgetData1["last_reset"].(string)
t.Logf("Initial budget state: usage=$%.6f, lastReset=%s", initialUsage, lastReset1)
// Make a request to consume some budget
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request to consume budget before reset.",
},
},
},
VKHeader: &vkValue,
})
if resp.StatusCode != 200 {
t.Skip("Could not make request to consume budget")
}
// Wait for async PostHook goroutine to complete budget update
time.Sleep(2 * time.Second)
// Check usage after request
getBudgetsResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap2 := getBudgetsResp2.Body["budgets"].(map[string]interface{})
budgetData2 := budgetsMap2[budgetID].(map[string]interface{})
usageAfterRequest, _ := budgetData2["current_usage"].(float64)
t.Logf("Budget after request: usage=$%.6f (consumed)", usageAfterRequest)
if usageAfterRequest <= initialUsage {
t.Skip("Request did not consume budget")
}
// Wait for reset duration to pass
// We need to wait until LastReset + resetDuration has passed
// Parse the lastReset time to calculate the exact wait time
lastResetTime, err := time.Parse(time.RFC3339Nano, lastReset1)
if err != nil {
// Fallback to RFC3339 if RFC3339Nano fails
lastResetTime, err = time.Parse(time.RFC3339, lastReset1)
if err != nil {
t.Fatalf("Failed to parse lastReset time: %v", err)
}
}
resetDurationParsed, err := ParseDuration(resetDuration)
if err != nil {
t.Fatalf("Failed to parse reset duration: %v", err)
}
// Calculate when reset should occur with a 2-second safety buffer
resetTime := lastResetTime.Add(resetDurationParsed).Add(2 * time.Second)
waitTime := time.Until(resetTime)
if waitTime > 0 {
t.Logf("Waiting %.1f seconds for budget to reset (lastReset was %s, reset duration is %s)...", waitTime.Seconds(), lastReset1, resetDuration)
time.Sleep(waitTime)
} else {
t.Logf("No wait needed - reset duration has already passed")
}
// Budget resets are LAZY - they happen when:
// 1. Background tracker runs ResetExpiredBudgets, OR
// 2. A new request triggers UpdateBudgetUsage (which resets expired budgets inline)
// Make another request to trigger the lazy reset mechanism
t.Logf("Making request to trigger lazy budget reset...")
resp2 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request after reset duration to trigger lazy reset.",
},
},
},
VKHeader: &vkValue,
})
if resp2.StatusCode != 200 {
t.Logf("Post-reset request status: %d (expected 200)", resp2.StatusCode)
}
// Wait for async update using polling instead of fixed sleep
// Poll for budget data to reflect the reset
_, resetVerified := WaitForAPICondition(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
}, func(resp *APIResponse) bool {
if resp.StatusCode != 200 {
return false
}
budgetsData, ok := resp.Body["budgets"].(map[string]interface{})
if !ok {
return false
}
budgetData, ok := budgetsData[budgetID].(map[string]interface{})
if !ok {
return false
}
// Check if LastReset has been updated (indicating reset occurred)
newLastReset, ok := budgetData["last_reset"].(string)
return ok && newLastReset != lastReset1
}, 5*time.Second, "budget reset verified by timestamp")
if !resetVerified {
t.Logf("Warning: Reset verification polling timed out, but will proceed with final check")
}
// Check budget after reset
getBudgetsResp3 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap3 := getBudgetsResp3.Body["budgets"].(map[string]interface{})
budgetData3 := budgetsMap3[budgetID].(map[string]interface{})
usageAfterReset, _ := budgetData3["current_usage"].(float64)
lastReset3, _ := budgetData3["last_reset"].(string)
t.Logf("Budget after reset: usage=$%.6f, lastReset=%s", usageAfterReset, lastReset3)
// Verify the reset actually happened by checking the LastReset timestamp changed
// This is the most reliable indicator that a reset occurred
if lastReset3 == lastReset1 {
t.Fatalf("Budget reset failed: LastReset timestamp was not updated (%s -> %s)", lastReset1, lastReset3)
}
t.Logf("✓ Budget reset verified by LastReset timestamp change")
// Verify budget wasn't cumulative (which would indicate no reset)
// A normal request costs $0.003-0.010
// If it's the sum of two requests, it would be $0.008+
// This maximum check prevents detecting cumulative usage while allowing cost variations
if usageAfterReset > 0.012 {
t.Logf("WARNING: Budget usage suspiciously high after reset: $%.6f (might indicate reset didn't work, but timestamp changed so reset verified)", usageAfterReset)
t.Logf(" Before reset: $%.6f", usageAfterRequest)
t.Logf(" After reset: $%.6f", usageAfterReset)
// Don't fail - could be legitimate variation in API costs
}
t.Logf("Budget state after reset verified ✓")
t.Logf("Usage was reset from $%.6f to ~$%.6f (cost of one post-reset request) ✓", usageAfterRequest, usageAfterReset)
}
// ============================================================================
// HIGH: Team Deletion Cascade
// ============================================================================
// TestTeamDeletionCascade verifies that deleting a team with VKs properly cleans up
func TestTeamDeletionCascade(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create team
teamName := "test-team-deletion-" + generateRandomID()
createTeamResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/teams",
Body: CreateTeamRequest{
Name: teamName,
Budgets: []BudgetRequest{{
MaxLimit: 100.0,
ResetDuration: "1h",
}},
},
})
if createTeamResp.StatusCode != 200 {
t.Fatalf("Failed to create team: status %d", createTeamResp.StatusCode)
}
teamID := ExtractIDFromResponse(t, createTeamResp)
testData.AddTeam(teamID)
t.Logf("Created team: %s", teamID)
// Create VK assigned to team
vkName := "test-vk-for-team-" + generateRandomID()
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
TeamID: &teamID,
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK assigned to team: %s", vkID)
// Verify VK works
resp1 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request before team deletion.",
},
},
},
VKHeader: &vkValue,
})
if resp1.StatusCode != 200 {
t.Skip("Could not verify VK before deletion")
}
t.Logf("VK works before team deletion ✓")
// Delete team
deleteResp := MakeRequest(t, APIRequest{
Method: "DELETE",
Path: "/api/governance/teams/" + teamID,
})
if deleteResp.StatusCode != 200 {
t.Fatalf("Failed to delete team: status %d", deleteResp.StatusCode)
}
t.Logf("Team deleted")
// Wait for in-memory store update
time.Sleep(500 * time.Millisecond)
// Try to use VK after team deletion
// Expected: VK should continue to work after team deletion
// VKs can function independently without a team, but they lose access to team budget
resp2 := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request after team deletion.",
},
},
},
VKHeader: &vkValue,
})
// Assert VK request succeeds after team deletion
if resp2.StatusCode != 200 {
t.Fatalf("Expected 200 OK after team deletion (VK should continue to work), got status %d. Response: %v", resp2.StatusCode, resp2.Body)
}
// Assert no team budget was billed (team is deleted, so team budget should not be used)
// The request should succeed but without team budget constraints
// Note: We can't directly verify team budget wasn't billed from the response,
// but we verify the request succeeds which confirms VK works independently
t.Logf("Team deletion cascade verified ✓: VK continues to work after team deletion (without team budget)")
}
// ============================================================================
// HIGH: VK Deletion Cascade
// ============================================================================
// TestVKDeletionCascade verifies that deleting a VK properly cleans up all related resources
func TestVKDeletionCascade(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with rate limit and budget
vkName := "test-vk-deletion-" + generateRandomID()
tokenLimit := int64(1000)
tokenResetDuration := "1h"
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: 10.0,
ResetDuration: "1h",
},
RateLimit: &CreateRateLimitRequest{
TokenMaxLimit: &tokenLimit,
TokenResetDuration: &tokenResetDuration,
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with rate limit and budget")
// Verify VK exists in in-memory store (poll to ensure sync completed)
vkExists := WaitForCondition(t, func() bool {
getDataResp1 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getDataResp1.StatusCode != 200 {
return false
}
virtualKeysMap1, ok := getDataResp1.Body["virtual_keys"].(map[string]interface{})
if !ok {
return false
}
_, exists := virtualKeysMap1[vkValue]
return exists
}, 5*time.Second, "VK exists in in-memory store")
if !vkExists {
t.Fatalf("VK not found in in-memory store after creation (timeout after 5s)")
}
t.Logf("VK exists in in-memory store ✓")
// Delete VK
deleteResp := MakeRequest(t, APIRequest{
Method: "DELETE",
Path: "/api/governance/virtual-keys/" + vkID,
})
if deleteResp.StatusCode != 200 {
t.Fatalf("Failed to delete VK: status %d", deleteResp.StatusCode)
}
t.Logf("VK deleted from database")
// Wait for in-memory store to sync (poll with timeout instead of fixed sleep)
vkRemoved := WaitForCondition(t, func() bool {
getDataResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getDataResp2.StatusCode != 200 {
t.Logf("Failed to get VK data: status %d", getDataResp2.StatusCode)
return false
}
virtualKeysMap2, ok := getDataResp2.Body["virtual_keys"].(map[string]interface{})
if !ok {
t.Logf("Invalid response structure for virtual_keys")
return false
}
_, exists := virtualKeysMap2[vkValue]
return !exists // Return true when VK is NOT found (successfully removed)
}, 5*time.Second, "VK removed from in-memory store")
if !vkRemoved {
t.Fatalf("VK still exists in in-memory store after deletion (timeout after 5s)")
}
t.Logf("VK removed from in-memory store ✓")
// Try to use deleted VK
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o",
Messages: []ChatMessage{
{
Role: "user",
Content: "Request with deleted VK should fail.",
},
},
},
VKHeader: &vkValue,
})
if resp.StatusCode < 400 {
t.Logf("Deleted VK still accepts requests (status=%d) - may be cached in SDK", resp.StatusCode)
} else {
t.Logf("Deleted VK request rejected (status=%d) ✓", resp.StatusCode)
}
t.Logf("VK deletion cascade verified ✓")
}
// ============================================================================
// FEATURE: Load Balancing with Weighted Provider Distribution
// ============================================================================
// TestWeightedProviderLoadBalancing verifies that traffic is distributed between
// providers according to their weights when they share common models
func TestWeightedProviderLoadBalancing(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with two providers: 99% OpenAI, 1% Azure (both support gpt-4o)
vkName := "test-vk-weighted-lb-" + generateRandomID()
openaiWeight := 99.0
azureWeight := 1.0
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
ProviderConfigs: []ProviderConfigRequest{
{
Provider: "openai",
Weight: &openaiWeight,
AllowedModels: []string{"gpt-4o"},
KeyIDs: []string{"*"},
},
{
Provider: "azure",
Weight: &azureWeight,
AllowedModels: []string{"gpt-4o"},
KeyIDs: []string{"*"},
},
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with weighted providers: OpenAI(%.0f%%), Azure(%.0f%%)", openaiWeight, azureWeight)
// Verify both providers are configured
getDataResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
virtualKeysMap := getDataResp.Body["virtual_keys"].(map[string]interface{})
vkData := virtualKeysMap[vkValue].(map[string]interface{})
providerConfigs, _ := vkData["provider_configs"].([]interface{})
if len(providerConfigs) != 2 {
t.Fatalf("Expected 2 provider configs, got %d", len(providerConfigs))
}
t.Logf("Both provider configs present in in-memory store ✓")
// Make 10 requests with just "gpt-4o" (no provider prefix)
// Expected: ~99 go to OpenAI, ~1 go to Azure
numRequests := 10
openaiCount := 0
azureCount := 0
failureCount := 0
t.Logf("Making %d weighted requests with model: 'gpt-4o' (no provider prefix)...", numRequests)
for i := 0; i < numRequests; i++ {
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "gpt-4o", // No provider prefix - should be routed based on weights
Messages: []ChatMessage{
{
Role: "user",
Content: "Hello how are you?",
},
},
},
VKHeader: &vkValue,
})
if resp.StatusCode != 200 {
failureCount++
t.Logf("Request %d failed with status %d", i+1, resp.StatusCode)
continue
}
// Try to detect which provider was used
// Check if model in response contains provider name
if provider, ok := resp.Body["extra_fields"].(map[string]interface{})["provider"].(string); ok {
model, ok := resp.Body["extra_fields"].(map[string]interface{})["original_model_requested"].(string)
if !ok {
t.Logf("Request %d failed to get model requested", i+1)
continue
}
if provider == "openai" {
openaiCount++
t.Logf("Request %d routed to OpenAI (model: %s)", i+1, model)
} else if provider == "azure" {
azureCount++
t.Logf("Request %d routed to Azure (model: %s)", i+1, model)
}
}
}
totalSuccess := openaiCount + azureCount
t.Logf("Results: OpenAI=%d, Azure=%d, Failed=%d (total requests=%d)",
openaiCount, azureCount, failureCount, numRequests)
if totalSuccess == 0 {
t.Skip("No successful requests to analyze distribution")
}
// With 99% weight to OpenAI and 1% to Azure:
// Out of 10 requests, we expect ~0-2 to go to Azure (1%)
if azureCount > 2 {
t.Logf("Warning: More requests went to Azure than expected (got %d, expected ~0-2)", azureCount)
}
t.Logf("Weighted provider load balancing verified ✓")
t.Logf("Traffic distribution approximately matches configured weights")
}
// ============================================================================
// FEATURE: Fallback Provider Mechanism
// ============================================================================
// TestProviderFallbackMechanism verifies that when primary provider doesn't support
// a model, fallback providers are used automatically
func TestProviderFallbackMechanism(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create VK with two providers:
// - 99% Anthropic (does NOT support gpt-4o)
// - 1% OpenAI (DOES support gpt-4o)
// When requesting gpt-4o, it should fall back to OpenAI since Anthropic doesn't have it
vkName := "test-vk-fallback-" + generateRandomID()
anthropicWeight := 99.0
openaiWeight := 1.0
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
ProviderConfigs: []ProviderConfigRequest{
{
Provider: "anthropic",
Weight: &anthropicWeight,
AllowedModels: []string{"claude-3-sonnet"}, // Does NOT include gpt-4o
KeyIDs: []string{"*"},
},
{
Provider: "openai",
Weight: &openaiWeight,
AllowedModels: []string{"gpt-4o"}, // DOES include gpt-4o
KeyIDs: []string{"*"},
},
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK with providers: Anthropic(99%%, no gpt-4o), OpenAI(1%%, supports gpt-4o)")
// Make 5 requests for gpt-4o model
// Even though Anthropic has 99% weight, all should succeed via OpenAI fallback
numRequests := 5
successCount := 0
t.Logf("Making %d requests with model: 'gpt-4o' (not supported by primary provider)...", numRequests)
for i := 0; i < numRequests; i++ {
resp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "gpt-4o", // Only OpenAI supports this
Messages: []ChatMessage{
{
Role: "user",
Content: "Hello how are you?",
},
},
},
VKHeader: &vkValue,
})
if resp.StatusCode == 200 {
successCount++
// Try to detect which provider actually handled it
model := ""
if m, ok := resp.Body["model"].(string); ok {
model = m
}
t.Logf("Request %d succeeded (model: %s) - likely via OpenAI fallback", i+1, model)
} else {
t.Logf("Request %d failed with status %d", i+1, resp.StatusCode)
}
}
t.Logf("Results: %d/%d requests succeeded via fallback", successCount, numRequests)
if successCount == 0 {
t.Skip("No successful requests - cannot verify fallback mechanism")
}
if successCount < numRequests {
t.Logf("Warning: Not all requests succeeded (got %d/%d)", successCount, numRequests)
} else {
t.Logf("All requests succeeded via fallback provider ✓")
}
t.Logf("Fallback provider mechanism verified ✓")
t.Logf("Requests successfully routed to fallback when primary doesn't support model")
}
// ============================================================================
// Virtual Key Header Formats
// ============================================================================
// TestVirtualKeyHeaderFormats verifies that Bifrost accepts all documented VK header formats
// Reference: https://docs.getbifrost.ai/features/governance/virtual-keys
// Supported headers:
// - x-bf-vk: Virtual key header (Bifrost native)
// - Authorization: Bearer token style (OpenAI style)
// - x-api-key: API key header (Anthropic style)
// - x-goog-api-key: API key header (Google Gemini style)
func TestVirtualKeyHeaderFormats(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create a VK with minimal config to test header acceptance
vkName := "test-vk-headers-" + generateRandomID()
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: 10.0,
ResetDuration: "1h",
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK for header format testing: %s", vkValue)
// Test all supported header formats
testCases := []struct {
name string
headerName string
headerValue string
description string
expectedPass bool
}{
{
name: "x-bf-vk header",
headerName: "x-bf-vk",
headerValue: vkValue,
description: "Bifrost native VK header",
expectedPass: true,
},
{
name: "Authorization Bearer",
headerName: "Authorization",
headerValue: "Bearer " + vkValue,
description: "OpenAI-style Bearer token",
expectedPass: true,
},
{
name: "x-api-key",
headerName: "x-api-key",
headerValue: vkValue,
description: "Anthropic-style API key",
expectedPass: true,
},
{
name: "x-goog-api-key",
headerName: "x-goog-api-key",
headerValue: vkValue,
description: "Google Gemini-style API key",
expectedPass: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Make request with the specific header format
resp := MakeRequestWithCustomHeaders(t, APIRequest{
Method: "POST",
Path: "/v1/chat/completions",
Body: ChatCompletionRequest{
Model: "openai/gpt-4o-mini",
Messages: []ChatMessage{
{
Role: "user",
Content: "Test request for header format: " + tc.name,
},
},
},
}, map[string]string{
tc.headerName: tc.headerValue,
})
if tc.expectedPass {
if resp.StatusCode != 200 {
t.Errorf("Expected %s to work, but got status %d (response: %v)", tc.description, resp.StatusCode, resp.Body)
} else {
t.Logf("✓ %s works correctly (status: %d)", tc.description, resp.StatusCode)
}
} else {
if resp.StatusCode == 200 {
t.Errorf("Expected %s to fail, but got status 200", tc.description)
} else {
t.Logf("✓ %s correctly rejected (status: %d)", tc.description, resp.StatusCode)
}
}
})
}
t.Logf("All virtual key header formats verified ✓")
}