| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- package handler
- import (
- "bytes"
- "context"
- "net/http"
- "net/http/httptest"
- "sync/atomic"
- "testing"
- "time"
- middleware "github.com/Wei-Shaw/sub2api/internal/server/middleware"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/require"
- "github.com/tidwall/gjson"
- )
- func TestChatCompletionsRejectsGPTImageModelsBeforeScheduling(t *testing.T) {
- gin.SetMode(gin.TestMode)
- for _, model := range []string{"gpt-image-1", "gpt-image-1.5", "gpt-image-2"} {
- for _, tc := range []struct {
- name string
- call func(*gin.Context)
- }{
- {
- name: "gateway",
- call: (&GatewayHandler{}).ChatCompletions,
- },
- {
- name: "openai_gateway",
- call: newOpenAIImageChatRejectionHandler(t).ChatCompletions,
- },
- } {
- t.Run(tc.name+"/"+model, func(t *testing.T) {
- recorder := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(recorder)
- body := []byte(`{"model":"` + model + `","messages":[{"role":"user","content":"draw"}]}`)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", bytes.NewReader(body))
- setImageChatTestAuth(c)
- tc.call(c)
- require.Equal(t, http.StatusBadRequest, recorder.Code)
- require.Equal(t, "invalid_request_error", gjson.Get(recorder.Body.String(), "error.type").String())
- require.Contains(t, gjson.Get(recorder.Body.String(), "error.message").String(), "Chat Completions")
- _, selected := c.Get(opsAccountIDKey)
- require.False(t, selected, "rejection must happen before account selection")
- })
- }
- }
- }
- func TestOpenAIChatCompletionsImageModelRejectionDoesNotAcquireConcurrency(t *testing.T) {
- var acquireCalls atomic.Int64
- cache := &concurrencyCacheMock{
- acquireUserSlotFn: func(context.Context, int64, int, string) (bool, error) {
- acquireCalls.Add(1)
- return true, nil
- },
- }
- h := newOpenAIImageChatRejectionHandlerWithCache(t, cache)
- recorder := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(recorder)
- c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", bytes.NewBufferString(
- `{"model":"gpt-image-2","messages":[{"role":"user","content":"draw"}]}`,
- ))
- setImageChatTestAuth(c)
- h.ChatCompletions(c)
- require.Equal(t, http.StatusBadRequest, recorder.Code)
- require.Zero(t, acquireCalls.Load(), "rejection must happen before user/account concurrency and scheduling")
- }
- func newOpenAIImageChatRejectionHandler(t *testing.T) *OpenAIGatewayHandler {
- t.Helper()
- return newOpenAIImageChatRejectionHandlerWithCache(t, &concurrencyCacheMock{})
- }
- func newOpenAIImageChatRejectionHandlerWithCache(t *testing.T, cache *concurrencyCacheMock) *OpenAIGatewayHandler {
- t.Helper()
- return &OpenAIGatewayHandler{
- gatewayService: &service.OpenAIGatewayService{},
- billingCacheService: &service.BillingCacheService{},
- apiKeyService: &service.APIKeyService{},
- concurrencyHelper: NewConcurrencyHelper(service.NewConcurrencyService(cache), SSEPingFormatNone, time.Second),
- }
- }
- func setImageChatTestAuth(c *gin.Context) {
- apiKey := &service.APIKey{ID: 4348, UserID: 4348, User: &service.User{ID: 4348}}
- c.Set(string(middleware.ContextKeyAPIKey), apiKey)
- c.Set(string(middleware.ContextKeyUser), middleware.AuthSubject{UserID: apiKey.UserID, Concurrency: 1})
- }
|