From 68d67b5c12450d0a89e82134454be857511ba1b2 Mon Sep 17 00:00:00 2001 From: alex xiang Date: Tue, 11 Aug 2026 17:10:18 +0800 Subject: [PATCH 1/2] fix(relay): return 400 for invalid request parameters --- controller/relay.go | 2 +- controller/relay_validation_test.go | 92 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 controller/relay_validation_test.go diff --git a/controller/relay.go b/controller/relay.go index 8dccfe76dddd..d34cfafda1dc 100644 --- a/controller/relay.go +++ b/controller/relay.go @@ -115,7 +115,7 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) { if common.IsRequestBodyTooLargeError(err) || errors.Is(err, common.ErrRequestBodyTooLarge) { newAPIError = types.NewErrorWithStatusCode(err, types.ErrorCodeReadRequestBodyFailed, http.StatusRequestEntityTooLarge, types.ErrOptionWithSkipRetry()) } else { - newAPIError = types.NewError(err, types.ErrorCodeInvalidRequest) + newAPIError = types.NewError(err, types.ErrorCodeInvalidRequest, types.ErrOptionWithStatusCode(http.StatusBadRequest), types.ErrOptionWithSkipRetry()) } return } diff --git a/controller/relay_validation_test.go b/controller/relay_validation_test.go new file mode 100644 index 000000000000..6f3348e23083 --- /dev/null +++ b/controller/relay_validation_test.go @@ -0,0 +1,92 @@ +package controller + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/relaykit/types" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" +) + +func TestRelayReturnsBadRequestForInvalidClientParameters(t *testing.T) { + gin.SetMode(gin.TestMode) + + tests := []struct { + name string + path string + format types.RelayFormat + body string + wantMessage string + claude bool + }{ + { + name: "chat completions requires messages", + path: "/v1/chat/completions", + format: types.RelayFormatOpenAI, + body: `{"model":"gpt-4o"}`, + wantMessage: "field messages is required", + }, + { + name: "embeddings requires input", + path: "/v1/embeddings", + format: types.RelayFormatEmbedding, + body: `{"model":"text-embedding-3-small"}`, + wantMessage: "input is empty", + }, + { + name: "responses requires input", + path: "/v1/responses", + format: types.RelayFormatOpenAIResponses, + body: `{"model":"gpt-4o"}`, + wantMessage: "input is required", + }, + { + name: "claude messages requires messages", + path: "/v1/messages", + format: types.RelayFormatClaude, + body: `{"model":"claude-sonnet-4"}`, + wantMessage: "field messages is required", + claude: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest(http.MethodPost, test.path, bytes.NewBufferString(test.body)) + ctx.Request.Header.Set("Content-Type", "application/json") + t.Cleanup(func() { common.CleanupBodyStorage(ctx) }) + + Relay(ctx, test.format) + + require.Equal(t, http.StatusBadRequest, recorder.Code) + if test.claude { + var response struct { + Type string `json:"type"` + Error struct { + Type string `json:"type"` + Message string `json:"message"` + } `json:"error"` + } + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response)) + require.Equal(t, "error", response.Type) + require.Equal(t, "new_api_error", response.Error.Type) + require.Contains(t, response.Error.Message, test.wantMessage) + return + } + + var response struct { + Error types.OpenAIError `json:"error"` + } + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response)) + require.Equal(t, string(types.ErrorCodeInvalidRequest), response.Error.Code) + require.Contains(t, response.Error.Message, test.wantMessage) + }) + } +} From ad05be38ec1cb9889f01e34da45680c5357524dc Mon Sep 17 00:00:00 2001 From: Calcium-Ion Date: Sat, 29 Aug 2026 19:20:58 +0800 Subject: [PATCH 2/2] Delete controller/relay_validation_test.go --- controller/relay_validation_test.go | 92 ----------------------------- 1 file changed, 92 deletions(-) delete mode 100644 controller/relay_validation_test.go diff --git a/controller/relay_validation_test.go b/controller/relay_validation_test.go deleted file mode 100644 index 6f3348e23083..000000000000 --- a/controller/relay_validation_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package controller - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/QuantumNous/new-api/common" - "github.com/QuantumNous/new-api/relaykit/types" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/require" -) - -func TestRelayReturnsBadRequestForInvalidClientParameters(t *testing.T) { - gin.SetMode(gin.TestMode) - - tests := []struct { - name string - path string - format types.RelayFormat - body string - wantMessage string - claude bool - }{ - { - name: "chat completions requires messages", - path: "/v1/chat/completions", - format: types.RelayFormatOpenAI, - body: `{"model":"gpt-4o"}`, - wantMessage: "field messages is required", - }, - { - name: "embeddings requires input", - path: "/v1/embeddings", - format: types.RelayFormatEmbedding, - body: `{"model":"text-embedding-3-small"}`, - wantMessage: "input is empty", - }, - { - name: "responses requires input", - path: "/v1/responses", - format: types.RelayFormatOpenAIResponses, - body: `{"model":"gpt-4o"}`, - wantMessage: "input is required", - }, - { - name: "claude messages requires messages", - path: "/v1/messages", - format: types.RelayFormatClaude, - body: `{"model":"claude-sonnet-4"}`, - wantMessage: "field messages is required", - claude: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - recorder := httptest.NewRecorder() - ctx, _ := gin.CreateTestContext(recorder) - ctx.Request = httptest.NewRequest(http.MethodPost, test.path, bytes.NewBufferString(test.body)) - ctx.Request.Header.Set("Content-Type", "application/json") - t.Cleanup(func() { common.CleanupBodyStorage(ctx) }) - - Relay(ctx, test.format) - - require.Equal(t, http.StatusBadRequest, recorder.Code) - if test.claude { - var response struct { - Type string `json:"type"` - Error struct { - Type string `json:"type"` - Message string `json:"message"` - } `json:"error"` - } - require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response)) - require.Equal(t, "error", response.Type) - require.Equal(t, "new_api_error", response.Error.Type) - require.Contains(t, response.Error.Message, test.wantMessage) - return - } - - var response struct { - Error types.OpenAIError `json:"error"` - } - require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response)) - require.Equal(t, string(types.ErrorCodeInvalidRequest), response.Error.Code) - require.Contains(t, response.Error.Message, test.wantMessage) - }) - } -}