| package semanticcache |
|
|
| import ( |
| "os" |
| "testing" |
| "time" |
|
|
| "github.com/maximhq/bifrost/core/schemas" |
| ) |
|
|
| |
| func TestImageGenerationCacheBasicFunctionality(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping integration test in -short mode") |
| } |
| if os.Getenv("OPENAI_API_KEY") == "" { |
| t.Skip("OPENAI_API_KEY not set") |
| } |
| setup := NewTestSetup(t) |
| defer setup.Cleanup() |
|
|
| ctx := CreateContextWithCacheKey("test-image-gen-value") |
|
|
| |
| testRequest := CreateImageGenerationRequest( |
| "A serene Japanese garden with cherry blossoms in spring", |
| "1024x1024", |
| "low", |
| ) |
|
|
| t.Log("Making first image generation request (should go to OpenAI and be cached)...") |
|
|
| |
| start1 := time.Now() |
| response1, err1 := setup.Client.ImageGenerationRequest(ctx, testRequest) |
| duration1 := time.Since(start1) |
|
|
| if err1 != nil { |
| t.Skipf("First image generation request failed (may be rate limited): %v", err1) |
| return |
| } |
|
|
| if response1 == nil || len(response1.Data) == 0 { |
| t.Fatal("First response is invalid or has no image data") |
| } |
|
|
| t.Logf("First request completed in %v", duration1) |
| t.Logf("Response: ID=%s, Images=%d", response1.ID, len(response1.Data)) |
|
|
| |
| WaitForCache(setup.Plugin) |
|
|
| t.Log("Making second identical request (should be served from cache)...") |
|
|
| |
| start2 := time.Now() |
| response2, err2 := setup.Client.ImageGenerationRequest(ctx, testRequest) |
| duration2 := time.Since(start2) |
|
|
| if err2 != nil { |
| if err2.Error != nil { |
| t.Fatalf("Second request failed: %v", err2.Error.Message) |
| } else { |
| t.Fatalf("Second request failed: %v", err2) |
| } |
| } |
|
|
| if response2 == nil || len(response2.Data) == 0 { |
| t.Fatal("Second response is invalid or has no image data") |
| } |
|
|
| t.Logf("Second request completed in %v", duration2) |
|
|
| |
| AssertCacheHit(t, &schemas.BifrostResponse{ImageGenerationResponse: response2}, string(CacheTypeDirect)) |
|
|
| |
| t.Logf("Performance Summary:") |
| t.Logf("First request (OpenAI): %v", duration1) |
| t.Logf("Second request (Cache): %v", duration2) |
|
|
| if duration2 < duration1 { |
| if duration2 == 0 { |
| t.Errorf("Second request duration too small to compute speedup (duration2=0)") |
| return |
| } |
| speedup := float64(duration1) / float64(duration2) |
| t.Logf("Cache speedup: %.2fx faster", speedup) |
| } else { |
| if duration2 == 0 { |
| t.Errorf("Second request duration too small to compute speedup (duration2=0)") |
| return |
| } |
| speedup := float64(duration1) / float64(duration2) |
| t.Logf("Cache was slower than original: speedup=%.2fx (this can happen due to system load)", speedup) |
| |
| if duration2 > duration1*10 { |
| t.Errorf("Cache is extremely slow compared to original: cache=%v, original=%v (cache may not be working)", duration2, duration1) |
| } |
| } |
|
|
| |
| if len(response2.Data) != len(response1.Data) { |
| t.Errorf("Image count differs between cached and original: original=%d, cached=%d", |
| len(response1.Data), len(response2.Data)) |
| } |
|
|
| |
| if response2.ExtraFields.Provider != testRequest.Provider { |
| t.Errorf("Provider mismatch in cached response: expected %s, got %s", |
| testRequest.Provider, response2.ExtraFields.Provider) |
| } |
|
|
| t.Log("✅ Basic image generation caching test completed successfully!") |
| } |
|
|
| |
| func TestImageGenerationSemanticSearch(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping integration test in -short mode") |
| } |
| if os.Getenv("OPENAI_API_KEY") == "" { |
| t.Skip("OPENAI_API_KEY not set") |
| } |
| |
| config := &Config{ |
| Provider: schemas.OpenAI, |
| EmbeddingModel: "text-embedding-3-small", |
| Dimension: 1536, |
| Threshold: 0.5, |
| Keys: []schemas.Key{ |
| {Value: *schemas.NewEnvVar("env.OPENAI_API_KEY"), Models: []string{}, Weight: 1.0}, |
| }, |
| } |
| setup := NewTestSetupWithConfig(t, config) |
| defer setup.Cleanup() |
|
|
| ctx := CreateContextWithCacheKey("image-semantic-test-value") |
|
|
| |
| firstRequest := CreateImageGenerationRequest( |
| "A beautiful sunset over the ocean with golden clouds", |
| "1024x1024", |
| "low", |
| ) |
|
|
| t.Log("Making first image generation request (should go to OpenAI and be cached)...") |
| start1 := time.Now() |
| response1, err1 := setup.Client.ImageGenerationRequest(ctx, firstRequest) |
| duration1 := time.Since(start1) |
|
|
| if err1 != nil { |
| t.Skipf("First image generation request failed (may be rate limited): %v", err1) |
| return |
| } |
|
|
| if response1 == nil || len(response1.Data) == 0 { |
| t.Fatal("First response is invalid or has no image data") |
| } |
|
|
| t.Logf("First request completed in %v", duration1) |
|
|
| |
| WaitForCache(setup.Plugin) |
|
|
| |
| secondRequest := CreateImageGenerationRequest( |
| "A gorgeous sunset over the sea with orange clouds", |
| "1024x1024", |
| "low", |
| ) |
|
|
| t.Log("Making semantically similar request (should be served from semantic cache)...") |
| start2 := time.Now() |
| response2, err2 := setup.Client.ImageGenerationRequest(ctx, secondRequest) |
| duration2 := time.Since(start2) |
|
|
| if err2 != nil { |
| if err2.Error != nil { |
| t.Fatalf("Second request failed: %v", err2.Error.Message) |
| } else { |
| t.Fatalf("Second request failed: %v", err2) |
| } |
| } |
|
|
| if response2 == nil || len(response2.Data) == 0 { |
| t.Fatal("Second response is invalid or has no image data") |
| } |
|
|
| t.Logf("Second request completed in %v", duration2) |
|
|
| |
| semanticMatch := false |
|
|
| if response2.ExtraFields.CacheDebug != nil && response2.ExtraFields.CacheDebug.CacheHit { |
| if response2.ExtraFields.CacheDebug.HitType != nil && *response2.ExtraFields.CacheDebug.HitType == string(CacheTypeSemantic) { |
| semanticMatch = true |
|
|
| threshold := 0.0 |
| similarity := 0.0 |
|
|
| if response2.ExtraFields.CacheDebug.Threshold != nil { |
| threshold = *response2.ExtraFields.CacheDebug.Threshold |
| } |
| if response2.ExtraFields.CacheDebug.Similarity != nil { |
| similarity = *response2.ExtraFields.CacheDebug.Similarity |
| } |
|
|
| t.Logf("✅ Second request was served from semantic cache! Cache threshold: %f, Cache similarity: %f", threshold, similarity) |
| } |
| } |
|
|
| if !semanticMatch { |
| t.Error("Semantic match expected but not found") |
| return |
| } |
|
|
| |
| t.Logf("Semantic Cache Performance:") |
| t.Logf("First request (OpenAI): %v", duration1) |
| t.Logf("Second request (Semantic): %v", duration2) |
|
|
| if duration2 < duration1 { |
| speedup := float64(duration1) / float64(duration2) |
| t.Logf("Semantic cache speedup: %.2fx faster", speedup) |
| } else { |
| slowdown := float64(duration2) / float64(duration1) |
| t.Logf("Semantic cache was slower than original: %.2fx slower (this can happen due to system load)", slowdown) |
| |
| if slowdown > 10 { |
| t.Errorf("Semantic cache is extremely slow compared to original: slowdown=%.2fx, cache=%v, original=%v (cache may not be working)", slowdown, duration2, duration1) |
| } |
| } |
|
|
| t.Log("✅ Image generation semantic search test completed successfully!") |
| } |
|
|
| |
| func TestImageGenerationDifferentParameters(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping integration test in -short mode") |
| } |
| if os.Getenv("OPENAI_API_KEY") == "" { |
| t.Skip("OPENAI_API_KEY not set") |
| } |
| setup := NewTestSetup(t) |
| defer setup.Cleanup() |
|
|
| ctx := CreateContextWithCacheKey("image-params-test") |
|
|
| basePrompt := "A cute cat sitting on a windowsill" |
|
|
| |
| request1 := CreateImageGenerationRequest(basePrompt, "1024x1024", "low") |
|
|
| t.Log("Making first request with 1024x1024...") |
| _, err1 := setup.Client.ImageGenerationRequest(ctx, request1) |
| if err1 != nil { |
| t.Skipf("First image generation request failed (may be rate limited): %v", err1) |
| return |
| } |
|
|
| WaitForCache(setup.Plugin) |
|
|
| |
| request2 := CreateImageGenerationRequest(basePrompt, "1024x1536", "low") |
|
|
| t.Log("Making second request with different size (1024x1536)...") |
| response2, err2 := setup.Client.ImageGenerationRequest(ctx, request2) |
| if err2 != nil { |
| t.Skipf("Second image generation request failed (may be rate limited): %v", err2) |
| return |
| } |
|
|
| |
| AssertNoCacheHit(t, &schemas.BifrostResponse{ImageGenerationResponse: response2}) |
|
|
| WaitForCache(setup.Plugin) |
|
|
| |
| request3 := CreateImageGenerationRequest(basePrompt, "1024x1024", "high") |
|
|
| t.Log("Making third request with different quality (high)...") |
| response3, err3 := setup.Client.ImageGenerationRequest(ctx, request3) |
| if err3 != nil { |
| t.Skipf("Third image generation request failed (may be rate limited): %v", err3) |
| return |
| } |
|
|
| |
| AssertNoCacheHit(t, &schemas.BifrostResponse{ImageGenerationResponse: response3}) |
|
|
| t.Log("✅ Image generation different parameters test completed!") |
| } |
|
|
| |
| func TestImageGenerationStreamCaching(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping integration test in -short mode") |
| } |
| if os.Getenv("OPENAI_API_KEY") == "" { |
| t.Skip("OPENAI_API_KEY not set") |
| } |
| setup := NewTestSetup(t) |
| defer setup.Cleanup() |
|
|
| ctx := CreateContextWithCacheKey("image-stream-test") |
|
|
| |
| testRequest := CreateImageGenerationRequest( |
| "A futuristic city skyline at night with neon lights", |
| "1024x1024", |
| "low", |
| ) |
|
|
| t.Log("Making first streaming image generation request...") |
|
|
| |
| start1 := time.Now() |
| stream1, err1 := setup.Client.ImageGenerationStreamRequest(ctx, testRequest) |
| if err1 != nil { |
| t.Skipf("First streaming request failed (may be rate limited): %v", err1) |
| return |
| } |
|
|
| var responses1 []schemas.BifrostImageGenerationStreamResponse |
| for streamMsg := range stream1 { |
| if streamMsg.BifrostError != nil { |
| t.Fatalf("Error in first stream: %v", streamMsg.BifrostError) |
| } |
| if streamMsg.BifrostImageGenerationStreamResponse != nil { |
| responses1 = append(responses1, *streamMsg.BifrostImageGenerationStreamResponse) |
| } |
| } |
| duration1 := time.Since(start1) |
|
|
| if len(responses1) == 0 { |
| t.Fatal("First streaming request returned no responses") |
| } |
|
|
| t.Logf("First streaming request completed in %v with %d chunks", duration1, len(responses1)) |
|
|
| |
| WaitForCache(setup.Plugin) |
|
|
| t.Log("Making second identical streaming request (should be served from cache)...") |
|
|
| |
| start2 := time.Now() |
| stream2, err2 := setup.Client.ImageGenerationStreamRequest(ctx, testRequest) |
| if err2 != nil { |
| t.Fatalf("Second streaming request failed: %v", err2) |
| } |
|
|
| var responses2 []schemas.BifrostImageGenerationStreamResponse |
| for streamMsg := range stream2 { |
| if streamMsg.BifrostError != nil { |
| t.Fatalf("Error in second stream: %v", streamMsg.BifrostError) |
| } |
| if streamMsg.BifrostImageGenerationStreamResponse != nil { |
| responses2 = append(responses2, *streamMsg.BifrostImageGenerationStreamResponse) |
| } |
| } |
| duration2 := time.Since(start2) |
|
|
| if len(responses2) == 0 { |
| t.Fatal("Second streaming request returned no responses") |
| } |
|
|
| t.Logf("Second streaming request completed in %v with %d chunks", duration2, len(responses2)) |
|
|
| |
| if len(responses1) != len(responses2) { |
| t.Errorf("Stream chunk count mismatch: original=%d, cached=%d", len(responses1), len(responses2)) |
| } |
|
|
| |
| |
| cached := false |
| if len(responses2) > 0 { |
| lastResponse := responses2[len(responses2)-1] |
| if lastResponse.ExtraFields.CacheDebug != nil && lastResponse.ExtraFields.CacheDebug.CacheHit { |
| cached = true |
| hitType := "unknown" |
| cacheID := "unknown" |
| if lastResponse.ExtraFields.CacheDebug.HitType != nil { |
| hitType = *lastResponse.ExtraFields.CacheDebug.HitType |
| } |
| if lastResponse.ExtraFields.CacheDebug.CacheID != nil { |
| cacheID = *lastResponse.ExtraFields.CacheDebug.CacheID |
| } |
| t.Logf("✅ Cache hit confirmed on last chunk: HitType=%s, CacheID=%s", hitType, cacheID) |
| } else { |
| |
| for i, response := range responses2 { |
| if response.ExtraFields.CacheDebug != nil { |
| t.Logf("Chunk %d: CacheDebug present, CacheHit=%v", i, response.ExtraFields.CacheDebug.CacheHit) |
| } else { |
| t.Logf("Chunk %d: No CacheDebug info", i) |
| } |
| } |
| } |
| } |
|
|
| if !cached { |
| t.Fatal("Second streaming request was not served from cache (CacheDebug not found on last chunk)") |
| } |
|
|
| |
| t.Logf("Streaming Performance Summary:") |
| t.Logf("First request (OpenAI): %v", duration1) |
| t.Logf("Second request (Cache): %v", duration2) |
|
|
| if duration2 < duration1 { |
| speedup := float64(duration1) / float64(duration2) |
| t.Logf("Streaming cache speedup: %.2fx faster", speedup) |
| } else { |
| speedup := float64(duration1) / float64(duration2) |
| t.Logf("Streaming cache was slower than original: speedup=%.2fx (this can happen due to system load)", speedup) |
| |
| if duration2 > duration1*10 { |
| t.Errorf("Streaming cache is extremely slow compared to original: cache=%v, original=%v (cache may not be working)", duration2, duration1) |
| } |
| } |
|
|
| t.Log("✅ Image generation streaming cache test completed successfully!") |
| } |
|
|