184 lines
6.4 KiB
Go
184 lines
6.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
|
|
coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
|
coresession "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/session"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
func TestGetContextWithCancelCapturesClientRequestMetadata(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ginCtx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
ginCtx.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
|
|
ginCtx.Request.RemoteAddr = "192.0.2.10:43123"
|
|
ginCtx.Request.Header.Add("X-Forwarded-For", "203.0.113.5")
|
|
ginCtx.Request.Header.Add("X-Forwarded-For", "198.51.100.8")
|
|
ginCtx.Request.Header.Set("User-Agent", "test-client/1.0")
|
|
|
|
handler := &BaseAPIHandler{Cfg: &config.SDKConfig{}}
|
|
ctx, cancel := handler.GetContextWithCancel(nil, ginCtx, context.Background())
|
|
defer cancel()
|
|
|
|
metadata := logging.GetClientRequestMetadata(ctx)
|
|
if metadata.ClientIP != "192.0.2.10" {
|
|
t.Fatalf("ClientIP = %q, want direct peer IP", metadata.ClientIP)
|
|
}
|
|
if metadata.XForwardedFor != "203.0.113.5, 198.51.100.8" {
|
|
t.Fatalf("XForwardedFor = %q", metadata.XForwardedFor)
|
|
}
|
|
if metadata.UserAgent != "test-client/1.0" {
|
|
t.Fatalf("UserAgent = %q", metadata.UserAgent)
|
|
}
|
|
}
|
|
|
|
func TestRequestExecutionMetadataIncludesExecutionSessionWithoutIdempotencyKey(t *testing.T) {
|
|
ctx := WithExecutionSessionID(context.Background(), "session-1")
|
|
|
|
meta := requestExecutionMetadata(ctx)
|
|
if got := meta[coreexecutor.ExecutionSessionMetadataKey]; got != "session-1" {
|
|
t.Fatalf("ExecutionSessionMetadataKey = %v, want %q", got, "session-1")
|
|
}
|
|
if _, ok := meta[idempotencyKeyMetadataKey]; ok {
|
|
t.Fatalf("unexpected idempotency key in metadata: %v", meta[idempotencyKeyMetadataKey])
|
|
}
|
|
}
|
|
|
|
func TestRequestExecutionMetadataIncludesHashedCallerScope(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ginCtx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
ginCtx.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
|
|
ginCtx.Set("userApiKey", "downstream-secret")
|
|
ctx := context.WithValue(context.Background(), "gin", ginCtx)
|
|
|
|
meta := requestExecutionMetadata(ctx)
|
|
got, _ := meta[coreexecutor.CallerScopeMetadataKey].(string)
|
|
want := coresession.CallerScope("downstream-secret")
|
|
if got != want {
|
|
t.Fatalf("CallerScopeMetadataKey = %q, want %q", got, want)
|
|
}
|
|
if got == "downstream-secret" {
|
|
t.Fatal("caller scope contains the raw downstream credential")
|
|
}
|
|
}
|
|
|
|
func TestRequestExecutionMetadataTraceCallbackWebsocketDetection(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
t.Run("skips websocket upgrade", func(t *testing.T) {
|
|
ginCtx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
ginCtx.Request = httptest.NewRequest(http.MethodGet, "/v1/responses", nil)
|
|
ginCtx.Request.Header.Set("Connection", "Upgrade")
|
|
ginCtx.Request.Header.Set("Upgrade", "websocket")
|
|
logging.SetGinRequestID(ginCtx, "1234abcd")
|
|
ctx := context.WithValue(context.Background(), "gin", ginCtx)
|
|
|
|
meta := requestExecutionMetadata(ctx)
|
|
|
|
if _, exists := meta[coreexecutor.SelectedAuthIndexCallbackMetadataKey]; exists {
|
|
t.Fatal("unexpected selected auth index callback for websocket upgrade")
|
|
}
|
|
})
|
|
|
|
t.Run("keeps callback for incomplete upgrade headers", func(t *testing.T) {
|
|
ginCtx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
|
ginCtx.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
|
|
ginCtx.Request.Header.Set("Upgrade", "websocket")
|
|
logging.SetGinRequestID(ginCtx, "1234abcd")
|
|
ctx := context.WithValue(context.Background(), "gin", ginCtx)
|
|
|
|
meta := requestExecutionMetadata(ctx)
|
|
|
|
if _, exists := meta[coreexecutor.SelectedAuthIndexCallbackMetadataKey]; !exists {
|
|
t.Fatal("missing selected auth index callback for ordinary HTTP request")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSetReasoningEffortMetadataUsesSuffixOverBody(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setReasoningEffortMetadata(meta, "openai", "gpt-5.4(high)", []byte(`{"reasoning_effort":"low"}`))
|
|
|
|
if got := meta[coreexecutor.ReasoningEffortMetadataKey]; got != "high" {
|
|
t.Fatalf("ReasoningEffortMetadataKey = %v, want %q", got, "high")
|
|
}
|
|
}
|
|
|
|
func TestSetReasoningEffortMetadataSupportsOpenAIResponses(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setReasoningEffortMetadata(meta, "openai-response", "gpt-5.4", []byte(`{"reasoning":{"effort":"medium"}}`))
|
|
|
|
if got := meta[coreexecutor.ReasoningEffortMetadataKey]; got != "medium" {
|
|
t.Fatalf("ReasoningEffortMetadataKey = %v, want %q", got, "medium")
|
|
}
|
|
}
|
|
|
|
func TestSetServiceTierMetadataExtractsValue(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setServiceTierMetadata(meta, []byte(`{"service_tier":"priority"}`))
|
|
|
|
gotServiceTier := meta[coreexecutor.ServiceTierMetadataKey]
|
|
if gotServiceTier != "priority" {
|
|
t.Fatalf("ServiceTierMetadataKey = %v, want %q", gotServiceTier, "priority")
|
|
}
|
|
}
|
|
|
|
func TestSetServiceTierMetadataDefaultsWhenMissing(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setServiceTierMetadata(meta, []byte(`{"model":"gpt-5.4"}`))
|
|
|
|
gotServiceTier := meta[coreexecutor.ServiceTierMetadataKey]
|
|
if gotServiceTier != "auto" {
|
|
t.Fatalf("ServiceTierMetadataKey = %v, want %q", gotServiceTier, "auto")
|
|
}
|
|
}
|
|
|
|
func TestSetServiceTierMetadataPreservesExplicitDefault(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setServiceTierMetadata(meta, []byte(`{"service_tier":"default"}`))
|
|
|
|
if gotServiceTier := meta[coreexecutor.ServiceTierMetadataKey]; gotServiceTier != "default" {
|
|
t.Fatalf("ServiceTierMetadataKey = %v, want %q", gotServiceTier, "default")
|
|
}
|
|
}
|
|
|
|
func TestSetGenerateMetadataDefaultsWhenMissing(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setGenerateMetadata(meta, []byte(`{"model":"gpt-5.4"}`))
|
|
|
|
if got := meta[coreexecutor.GenerateMetadataKey]; got != true {
|
|
t.Fatalf("GenerateMetadataKey = %v, want true", got)
|
|
}
|
|
}
|
|
|
|
func TestSetGenerateMetadataPreservesTrue(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setGenerateMetadata(meta, []byte(`{"generate":true}`))
|
|
|
|
if got := meta[coreexecutor.GenerateMetadataKey]; got != true {
|
|
t.Fatalf("GenerateMetadataKey = %v, want true", got)
|
|
}
|
|
}
|
|
|
|
func TestSetGenerateMetadataHonorsExplicitFalse(t *testing.T) {
|
|
meta := make(map[string]any)
|
|
|
|
setGenerateMetadata(meta, []byte(`{"generate":false}`))
|
|
|
|
if got := meta[coreexecutor.GenerateMetadataKey]; got != false {
|
|
t.Fatalf("GenerateMetadataKey = %v, want false", got)
|
|
}
|
|
}
|