|
- package controller
-
- import (
- "bytes"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/model"
- "github.com/QuantumNous/new-api/setting/ratio_setting"
- "github.com/QuantumNous/new-api/types"
- "github.com/gin-gonic/gin"
- "github.com/glebarez/sqlite"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "gorm.io/gorm"
- )
-
- func setupModelPricingControllerTest(t *testing.T) *gin.Engine {
- t.Helper()
- gin.SetMode(gin.TestMode)
-
- db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
- require.NoError(t, err)
- require.NoError(t, db.AutoMigrate(&model.Option{}))
- sqlDB, err := db.DB()
- require.NoError(t, err)
- sqlDB.SetMaxOpenConns(1)
-
- origDB := model.DB
- origUsingSQLite := common.UsingSQLite
- origRedisEnabled := common.RedisEnabled
- model.DB = db
- common.UsingSQLite = true
- common.RedisEnabled = false
- model.InitOptionMap()
- require.NoError(t, ratio_setting.UpdateModelPricingRulesByJSONString(`{}`))
- require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{"video-test":0.25}`))
-
- t.Cleanup(func() {
- model.DB = origDB
- common.UsingSQLite = origUsingSQLite
- common.RedisEnabled = origRedisEnabled
- require.NoError(t, ratio_setting.UpdateModelPricingRulesByJSONString(`{}`))
- require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{}`))
- require.NoError(t, sqlDB.Close())
- })
-
- r := gin.New()
- r.GET("/api/option/model_pricing", GetModelPricingRules)
- r.GET("/api/option/model_pricing/*model", GetModelPricingRule)
- r.PUT("/api/option/model_pricing/*model", UpdateModelPricingRule)
- r.DELETE("/api/option/model_pricing/*model", DeleteModelPricingRule)
- return r
- }
-
- func TestModelPricingController_CRUD(t *testing.T) {
- r := setupModelPricingControllerTest(t)
- cfg := validControllerPricingConfig()
-
- w := performJSONRequest(t, r, http.MethodPut, "/api/option/model_pricing/video-test", cfg)
- require.Equal(t, http.StatusOK, w.Code)
- assert.True(t, responseSuccess(t, w.Body.Bytes()))
-
- w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing/video-test", nil)
- require.Equal(t, http.StatusOK, w.Code)
- assert.True(t, responseSuccess(t, w.Body.Bytes()))
- assert.Contains(t, w.Body.String(), `"billing_unit":"per_call"`)
-
- w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing", nil)
- require.Equal(t, http.StatusOK, w.Code)
- assert.True(t, responseSuccess(t, w.Body.Bytes()))
- assert.Contains(t, w.Body.String(), "video-test")
-
- w = performJSONRequest(t, r, http.MethodDelete, "/api/option/model_pricing/video-test", nil)
- require.Equal(t, http.StatusOK, w.Code)
- assert.True(t, responseSuccess(t, w.Body.Bytes()))
-
- w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing/video-test", nil)
- assert.Equal(t, http.StatusNotFound, w.Code)
- legacyPrice, ok := ratio_setting.GetModelPrice("video-test", false)
- require.True(t, ok)
- assert.Equal(t, 0.25, legacyPrice)
- }
-
- func TestModelPricingController_PutRejectsBadConfig(t *testing.T) {
- r := setupModelPricingControllerTest(t)
- cfg := validControllerPricingConfig()
- cfg.Table = nil
-
- w := performJSONRequest(t, r, http.MethodPut, "/api/option/model_pricing/video-test", cfg)
- assert.Equal(t, http.StatusBadRequest, w.Code)
- assert.Contains(t, w.Body.String(), "table must not be empty")
- }
-
- func TestModelPricingController_CRUDWithSlashModelName(t *testing.T) {
- r := setupModelPricingControllerTest(t)
- cfg := validControllerPricingConfig()
- modelName := "openai/gpt-4o"
-
- w := performJSONRequest(t, r, http.MethodPut, "/api/option/model_pricing/"+modelName, cfg)
- require.Equal(t, http.StatusOK, w.Code)
- require.NotNil(t, ratio_setting.GetPricingConfig(modelName))
-
- w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing/"+modelName, nil)
- require.Equal(t, http.StatusOK, w.Code)
- assert.Contains(t, w.Body.String(), `"billing_unit":"per_call"`)
-
- w = performJSONRequest(t, r, http.MethodDelete, "/api/option/model_pricing/"+modelName, nil)
- require.Equal(t, http.StatusOK, w.Code)
- assert.Nil(t, ratio_setting.GetPricingConfig(modelName))
- }
-
- func performJSONRequest(t *testing.T, r *gin.Engine, method, path string, body any) *httptest.ResponseRecorder {
- t.Helper()
- var payload []byte
- if body != nil {
- var err error
- payload, err = common.Marshal(body)
- require.NoError(t, err)
- }
- req := httptest.NewRequest(method, path, bytes.NewReader(payload))
- req.Header.Set("Content-Type", "application/json")
- w := httptest.NewRecorder()
- r.ServeHTTP(w, req)
- return w
- }
-
- func responseSuccess(t *testing.T, payload []byte) bool {
- t.Helper()
- var body struct {
- Success bool `json:"success"`
- }
- require.NoError(t, common.Unmarshal(payload, &body))
- return body.Success
- }
-
- func validControllerPricingConfig() types.PricingConfig {
- return types.PricingConfig{
- SchemaVersion: 1,
- Scope: types.PricingScopeModel,
- BillingUnit: types.BillingUnitPerCall,
- PreconsumeStrategy: types.PreconsumeStrategyExact,
- Dimensions: []types.PricingDimension{
- {Key: "resolution", Source: "request.resolution", Type: "string"},
- },
- Table: []types.PricingRow{
- {"resolution": "720P", "price": 0.1, "source": types.PricingRowSourceManual},
- },
- Fallback: types.PricingFallback{Strategy: types.PricingFallbackReject},
- }
- }
|