您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

136 行
4.4 KiB

  1. package controller
  2. import (
  3. "bytes"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/model"
  9. "github.com/QuantumNous/new-api/setting/ratio_setting"
  10. "github.com/QuantumNous/new-api/types"
  11. "github.com/gin-gonic/gin"
  12. "github.com/glebarez/sqlite"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. "gorm.io/gorm"
  16. )
  17. func setupModelPricingControllerTest(t *testing.T) *gin.Engine {
  18. t.Helper()
  19. gin.SetMode(gin.TestMode)
  20. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  21. require.NoError(t, err)
  22. require.NoError(t, db.AutoMigrate(&model.Option{}))
  23. sqlDB, err := db.DB()
  24. require.NoError(t, err)
  25. sqlDB.SetMaxOpenConns(1)
  26. origDB := model.DB
  27. origUsingSQLite := common.UsingSQLite
  28. origRedisEnabled := common.RedisEnabled
  29. model.DB = db
  30. common.UsingSQLite = true
  31. common.RedisEnabled = false
  32. model.InitOptionMap()
  33. require.NoError(t, ratio_setting.UpdateModelPricingRulesByJSONString(`{}`))
  34. require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{"video-test":0.25}`))
  35. t.Cleanup(func() {
  36. model.DB = origDB
  37. common.UsingSQLite = origUsingSQLite
  38. common.RedisEnabled = origRedisEnabled
  39. require.NoError(t, ratio_setting.UpdateModelPricingRulesByJSONString(`{}`))
  40. require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{}`))
  41. require.NoError(t, sqlDB.Close())
  42. })
  43. r := gin.New()
  44. r.GET("/api/option/model_pricing", GetModelPricingRules)
  45. r.GET("/api/option/model_pricing/:model", GetModelPricingRule)
  46. r.PUT("/api/option/model_pricing/:model", UpdateModelPricingRule)
  47. r.DELETE("/api/option/model_pricing/:model", DeleteModelPricingRule)
  48. return r
  49. }
  50. func TestModelPricingController_CRUD(t *testing.T) {
  51. r := setupModelPricingControllerTest(t)
  52. cfg := validControllerPricingConfig()
  53. w := performJSONRequest(t, r, http.MethodPut, "/api/option/model_pricing/video-test", cfg)
  54. require.Equal(t, http.StatusOK, w.Code)
  55. assert.True(t, responseSuccess(t, w.Body.Bytes()))
  56. w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing/video-test", nil)
  57. require.Equal(t, http.StatusOK, w.Code)
  58. assert.True(t, responseSuccess(t, w.Body.Bytes()))
  59. assert.Contains(t, w.Body.String(), `"billing_unit":"per_call"`)
  60. w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing", nil)
  61. require.Equal(t, http.StatusOK, w.Code)
  62. assert.True(t, responseSuccess(t, w.Body.Bytes()))
  63. assert.Contains(t, w.Body.String(), "video-test")
  64. w = performJSONRequest(t, r, http.MethodDelete, "/api/option/model_pricing/video-test", nil)
  65. require.Equal(t, http.StatusOK, w.Code)
  66. assert.True(t, responseSuccess(t, w.Body.Bytes()))
  67. w = performJSONRequest(t, r, http.MethodGet, "/api/option/model_pricing/video-test", nil)
  68. assert.Equal(t, http.StatusNotFound, w.Code)
  69. legacyPrice, ok := ratio_setting.GetModelPrice("video-test", false)
  70. require.True(t, ok)
  71. assert.Equal(t, 0.25, legacyPrice)
  72. }
  73. func TestModelPricingController_PutRejectsBadConfig(t *testing.T) {
  74. r := setupModelPricingControllerTest(t)
  75. cfg := validControllerPricingConfig()
  76. cfg.Table = nil
  77. w := performJSONRequest(t, r, http.MethodPut, "/api/option/model_pricing/video-test", cfg)
  78. assert.Equal(t, http.StatusBadRequest, w.Code)
  79. assert.Contains(t, w.Body.String(), "table must not be empty")
  80. }
  81. func performJSONRequest(t *testing.T, r *gin.Engine, method, path string, body any) *httptest.ResponseRecorder {
  82. t.Helper()
  83. var payload []byte
  84. if body != nil {
  85. var err error
  86. payload, err = common.Marshal(body)
  87. require.NoError(t, err)
  88. }
  89. req := httptest.NewRequest(method, path, bytes.NewReader(payload))
  90. req.Header.Set("Content-Type", "application/json")
  91. w := httptest.NewRecorder()
  92. r.ServeHTTP(w, req)
  93. return w
  94. }
  95. func responseSuccess(t *testing.T, payload []byte) bool {
  96. t.Helper()
  97. var body struct {
  98. Success bool `json:"success"`
  99. }
  100. require.NoError(t, common.Unmarshal(payload, &body))
  101. return body.Success
  102. }
  103. func validControllerPricingConfig() types.PricingConfig {
  104. return types.PricingConfig{
  105. SchemaVersion: 1,
  106. Scope: types.PricingScopeModel,
  107. BillingUnit: types.BillingUnitPerCall,
  108. PreconsumeStrategy: types.PreconsumeStrategyExact,
  109. Dimensions: []types.PricingDimension{
  110. {Key: "resolution", Source: "request.resolution", Type: "string"},
  111. },
  112. Table: []types.PricingRow{
  113. {"resolution": "720P", "price": 0.1, "source": types.PricingRowSourceManual},
  114. },
  115. Fallback: types.PricingFallback{Strategy: types.PricingFallbackReject},
  116. }
  117. }