Files
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

374 lines
13 KiB
Go

package integrations
import (
"bytes"
"context"
"mime/multipart"
"testing"
"github.com/bytedance/sonic"
"github.com/maximhq/bifrost/core/providers/openai"
"github.com/maximhq/bifrost/core/schemas"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParsePassthroughBody_MultipartExtractsModelAfterFilePart(t *testing.T) {
var body bytes.Buffer
writer := multipart.NewWriter(&body)
fileWriter, err := writer.CreateFormFile("file", "sample.mp3")
require.NoError(t, err)
_, err = fileWriter.Write([]byte("audio-bytes"))
require.NoError(t, err)
require.NoError(t, writer.WriteField("model", "openai/whisper-1"))
require.NoError(t, writer.WriteField("stream", "true"))
require.NoError(t, writer.Close())
model, stream := parsePassthroughBody(writer.FormDataContentType(), body.Bytes())
assert.Equal(t, "openai/whisper-1", model)
assert.True(t, stream)
}
func TestRequestWithSettableExtraParams_OpenAIChatRequest(t *testing.T) {
t.Run("SetExtraParams populates both standalone and embedded ExtraParams", func(t *testing.T) {
req := &openai.OpenAIChatRequest{}
extra := map[string]interface{}{
"guardrailConfig": map[string]interface{}{
"guardrailIdentifier": "xxx",
"guardrailVersion": "1",
},
}
rws, ok := interface{}(req).(RequestWithSettableExtraParams)
require.True(t, ok, "OpenAIChatRequest should implement RequestWithSettableExtraParams")
rws.SetExtraParams(extra)
assert.Equal(t, extra, req.GetExtraParams())
assert.Equal(t, extra, req.ChatParameters.ExtraParams, "embedded ChatParameters.ExtraParams should also be set")
})
t.Run("extra params propagate through ToBifrostChatRequest", func(t *testing.T) {
req := &openai.OpenAIChatRequest{
Model: "bedrock/claude-4-5-sonnet-global",
Messages: []openai.OpenAIMessage{},
}
extra := map[string]interface{}{
"guardrailConfig": map[string]interface{}{
"guardrailIdentifier": "test-id",
"guardrailVersion": "1",
},
}
rws := interface{}(req).(RequestWithSettableExtraParams)
rws.SetExtraParams(extra)
ctx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
bifrostReq := req.ToBifrostChatRequest(ctx)
require.NotNil(t, bifrostReq)
require.NotNil(t, bifrostReq.Params)
assert.Contains(t, bifrostReq.Params.ExtraParams, "guardrailConfig")
})
}
func TestRequestWithSettableExtraParams_AllOpenAIRequestTypes(t *testing.T) {
tests := []struct {
name string
req interface{}
}{
{"OpenAIChatRequest", &openai.OpenAIChatRequest{}},
{"OpenAITextCompletionRequest", &openai.OpenAITextCompletionRequest{}},
{"OpenAIResponsesRequest", &openai.OpenAIResponsesRequest{}},
{"OpenAIEmbeddingRequest", &openai.OpenAIEmbeddingRequest{}},
{"OpenAISpeechRequest", &openai.OpenAISpeechRequest{}},
{"OpenAIImageGenerationRequest", &openai.OpenAIImageGenerationRequest{}},
{"OpenAIImageEditRequest", &openai.OpenAIImageEditRequest{}},
{"OpenAIImageVariationRequest", &openai.OpenAIImageVariationRequest{}},
}
for _, tt := range tests {
t.Run(tt.name+" implements RequestWithSettableExtraParams", func(t *testing.T) {
rws, ok := tt.req.(RequestWithSettableExtraParams)
require.True(t, ok, "%s should implement RequestWithSettableExtraParams", tt.name)
extra := map[string]interface{}{"test_key": "test_value"}
rws.SetExtraParams(extra)
getter, ok := tt.req.(interface{ GetExtraParams() map[string]interface{} })
require.True(t, ok, "%s should implement GetExtraParams", tt.name)
assert.Equal(t, extra, getter.GetExtraParams())
})
}
}
func TestExtraParamsRequiresPassthroughHeader(t *testing.T) {
handlerStore := &mockHandlerStore{allowDirectKeys: true}
routes := CreateOpenAIRouteConfigs("/openai", handlerStore)
var chatRoute *RouteConfig
for i := range routes {
if routes[i].Path == "/openai/v1/chat/completions" {
chatRoute = &routes[i]
break
}
}
require.NotNil(t, chatRoute, "should find /openai/v1/chat/completions route")
rawBody := []byte(`{
"model": "bedrock/claude-4-5-sonnet-global",
"messages": [{"role": "user", "content": [{"type": "text", "text": "hello"}]}],
"extra_params": {
"guardrailConfig": {
"guardrailIdentifier": "my-guardrail",
"guardrailVersion": "1",
"trace": "disabled"
}
}
}`)
t.Run("extra_params NOT extracted without passthrough header", func(t *testing.T) {
req := chatRoute.GetRequestTypeInstance(context.Background())
err := sonic.Unmarshal(rawBody, req)
require.NoError(t, err)
bifrostCtx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
// Header not set -- simulate router logic
if bifrostCtx.Value(schemas.BifrostContextKeyPassthroughExtraParams) == true {
if rws, ok := req.(RequestWithSettableExtraParams); ok {
var wrapper struct {
ExtraParams map[string]interface{} `json:"extra_params"`
}
if err := sonic.Unmarshal(rawBody, &wrapper); err == nil && len(wrapper.ExtraParams) > 0 {
rws.SetExtraParams(wrapper.ExtraParams)
}
_ = rws
}
}
openaiReq, ok := req.(*openai.OpenAIChatRequest)
require.True(t, ok)
assert.Empty(t, openaiReq.ChatParameters.ExtraParams,
"ExtraParams should be empty when passthrough header is not set")
})
t.Run("extra_params extracted with passthrough header", func(t *testing.T) {
req := chatRoute.GetRequestTypeInstance(context.Background())
err := sonic.Unmarshal(rawBody, req)
require.NoError(t, err)
bifrostCtx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
bifrostCtx.SetValue(schemas.BifrostContextKeyPassthroughExtraParams, true)
if bifrostCtx.Value(schemas.BifrostContextKeyPassthroughExtraParams) == true {
if rws, ok := req.(RequestWithSettableExtraParams); ok {
var wrapper struct {
ExtraParams map[string]interface{} `json:"extra_params"`
}
if err := sonic.Unmarshal(rawBody, &wrapper); err == nil && len(wrapper.ExtraParams) > 0 {
rws.SetExtraParams(wrapper.ExtraParams)
}
}
}
openaiReq, ok := req.(*openai.OpenAIChatRequest)
require.True(t, ok)
require.Contains(t, openaiReq.ChatParameters.ExtraParams, "guardrailConfig",
"guardrailConfig should be in ExtraParams when passthrough header is set")
gc, ok := openaiReq.ChatParameters.ExtraParams["guardrailConfig"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, "my-guardrail", gc["guardrailIdentifier"])
assert.Equal(t, "1", gc["guardrailVersion"])
assert.Equal(t, "disabled", gc["trace"])
})
}
func TestExtraParamsPassthrough_NestedStructures(t *testing.T) {
rawBody := []byte(`{
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": [{"type": "text", "text": "hello"}]}],
"extra_params": {
"custom_param": "value",
"another_param": 123,
"nested": {
"deep_field": "deep_value",
"deeper": {"level": 3}
}
}
}`)
req := &openai.OpenAIChatRequest{}
err := sonic.Unmarshal(rawBody, req)
require.NoError(t, err)
bifrostCtx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
bifrostCtx.SetValue(schemas.BifrostContextKeyPassthroughExtraParams, true)
if bifrostCtx.Value(schemas.BifrostContextKeyPassthroughExtraParams) == true {
if rws, ok := interface{}(req).(RequestWithSettableExtraParams); ok {
var wrapper struct {
ExtraParams map[string]interface{} `json:"extra_params"`
}
if err := sonic.Unmarshal(rawBody, &wrapper); err == nil && len(wrapper.ExtraParams) > 0 {
rws.SetExtraParams(wrapper.ExtraParams)
}
}
}
require.Len(t, req.ChatParameters.ExtraParams, 3)
assert.Equal(t, "value", req.ChatParameters.ExtraParams["custom_param"])
assert.Equal(t, float64(123), req.ChatParameters.ExtraParams["another_param"])
nested, ok := req.ChatParameters.ExtraParams["nested"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, "deep_value", nested["deep_field"])
}
func TestExtraParamsPassthrough_EndToEnd(t *testing.T) {
rawJSON := []byte(`{
"model": "bedrock/claude-4-5-sonnet-global",
"messages": [{"role": "user", "content": [{"type": "text", "text": "hello"}]}],
"stream": false,
"temperature": 0.7,
"extra_params": {
"guardrailConfig": {
"guardrailIdentifier": "my-guardrail",
"guardrailVersion": "1",
"trace": "disabled"
}
}
}`)
req := &openai.OpenAIChatRequest{}
err := sonic.Unmarshal(rawJSON, req)
require.NoError(t, err)
assert.Equal(t, "bedrock/claude-4-5-sonnet-global", req.Model)
bifrostCtx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
bifrostCtx.SetValue(schemas.BifrostContextKeyPassthroughExtraParams, true)
if bifrostCtx.Value(schemas.BifrostContextKeyPassthroughExtraParams) == true {
if rws, ok := interface{}(req).(RequestWithSettableExtraParams); ok {
var wrapper struct {
ExtraParams map[string]interface{} `json:"extra_params"`
}
if err := sonic.Unmarshal(rawJSON, &wrapper); err == nil && len(wrapper.ExtraParams) > 0 {
rws.SetExtraParams(wrapper.ExtraParams)
}
}
}
bifrostReq := req.ToBifrostChatRequest(bifrostCtx)
require.NotNil(t, bifrostReq)
require.NotNil(t, bifrostReq.Params)
require.Contains(t, bifrostReq.Params.ExtraParams, "guardrailConfig")
gc, ok := bifrostReq.Params.ExtraParams["guardrailConfig"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, "my-guardrail", gc["guardrailIdentifier"])
assert.Equal(t, "1", gc["guardrailVersion"])
assert.Equal(t, "disabled", gc["trace"])
assert.NotContains(t, bifrostReq.Params.ExtraParams, "model")
assert.NotContains(t, bifrostReq.Params.ExtraParams, "messages")
assert.NotContains(t, bifrostReq.Params.ExtraParams, "stream")
assert.NotContains(t, bifrostReq.Params.ExtraParams, "temperature")
}
func TestExtraParamsPassthrough_NoExtraParamsKey(t *testing.T) {
rawBody := []byte(`{
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": [{"type": "text", "text": "hello"}]}]
}`)
req := &openai.OpenAIChatRequest{}
err := sonic.Unmarshal(rawBody, req)
require.NoError(t, err)
bifrostCtx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
bifrostCtx.SetValue(schemas.BifrostContextKeyPassthroughExtraParams, true)
if bifrostCtx.Value(schemas.BifrostContextKeyPassthroughExtraParams) == true {
if rws, ok := interface{}(req).(RequestWithSettableExtraParams); ok {
var wrapper struct {
ExtraParams map[string]interface{} `json:"extra_params"`
}
if err := sonic.Unmarshal(rawBody, &wrapper); err == nil && len(wrapper.ExtraParams) > 0 {
rws.SetExtraParams(wrapper.ExtraParams)
}
_ = rws
}
}
assert.Empty(t, req.ChatParameters.ExtraParams,
"ExtraParams should be empty when extra_params key is absent from JSON")
}
// TestExtraParamsSetViaInterfaceMutatesOriginalReq verifies that setting extra
// params through the RequestWithSettableExtraParams interface assertion mutates
// the original req (interface{}) value. This matters because createHandler
// passes req to config.RequestConverter after the extra params block -- both
// variables must reference the same underlying struct via pointer semantics.
func TestExtraParamsSetViaInterfaceMutatesOriginalReq(t *testing.T) {
handlerStore := &mockHandlerStore{allowDirectKeys: true}
routes := CreateOpenAIRouteConfigs("/openai", handlerStore)
var chatRoute *RouteConfig
for i := range routes {
if routes[i].Path == "/openai/v1/chat/completions" {
chatRoute = &routes[i]
break
}
}
require.NotNil(t, chatRoute)
rawBody := []byte(`{
"model": "bedrock/claude-4-5-sonnet-global",
"messages": [{"role": "user", "content": [{"type": "text", "text": "hello"}]}],
"extra_params": {
"guardrailConfig": {
"guardrailIdentifier": "my-guardrail",
"guardrailVersion": "1"
}
}
}`)
// Simulate the exact flow in createHandler:
// 1. req is created via GetRequestTypeInstance (returns interface{})
// 2. JSON is unmarshalled into req
// 3. rws type assertion is used to call SetExtraParams
// 4. req (not rws) is passed to RequestConverter downstream
req := chatRoute.GetRequestTypeInstance(context.Background()) // returns interface{}
err := sonic.Unmarshal(rawBody, req)
require.NoError(t, err)
// Type-assert and set extra params (same as router code)
if rws, ok := req.(RequestWithSettableExtraParams); ok {
var wrapper struct {
ExtraParams map[string]interface{} `json:"extra_params"`
}
if err := sonic.Unmarshal(rawBody, &wrapper); err == nil && len(wrapper.ExtraParams) > 0 {
rws.SetExtraParams(wrapper.ExtraParams)
}
}
// Verify that req (the original interface{} variable) was mutated
openaiReq, ok := req.(*openai.OpenAIChatRequest)
require.True(t, ok)
require.Contains(t, openaiReq.ChatParameters.ExtraParams, "guardrailConfig",
"original req should be mutated via pointer semantics")
// Verify the full downstream path: RequestConverter uses req
bifrostCtx := schemas.NewBifrostContext(nil, schemas.NoDeadline)
bifrostReq, err := chatRoute.RequestConverter(bifrostCtx, req)
require.NoError(t, err)
require.NotNil(t, bifrostReq)
require.NotNil(t, bifrostReq.ChatRequest)
require.NotNil(t, bifrostReq.ChatRequest.Params)
assert.Contains(t, bifrostReq.ChatRequest.Params.ExtraParams, "guardrailConfig",
"extra params should propagate through RequestConverter to BifrostChatRequest")
}