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) } }