Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

312 Zeilen
8.8 KiB

  1. package controller
  2. import (
  3. "encoding/json"
  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. // setupPricingTestDB 初始化测试数据库
  18. func setupPricingTestDB(t *testing.T) *gorm.DB {
  19. t.Helper()
  20. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  21. require.NoError(t, err)
  22. sqlDB, _ := db.DB()
  23. sqlDB.SetMaxOpenConns(1)
  24. origDB := model.DB
  25. model.DB = db
  26. common.UsingSQLite = true
  27. common.RedisEnabled = false
  28. require.NoError(t, db.AutoMigrate(&model.User{}))
  29. t.Cleanup(func() {
  30. model.DB = origDB
  31. sqlDB.Close()
  32. })
  33. return db
  34. }
  35. // setupPricingTestRouter 创建无认证路由(未登录场景)
  36. func setupPricingTestRouter() *gin.Engine {
  37. gin.SetMode(gin.TestMode)
  38. r := gin.New()
  39. r.GET("/api/pricing/user/:model", GetUserPricing)
  40. return r
  41. }
  42. // setupAuthRouter 创建带用户 ID 注入的路由(已登录场景)
  43. func setupAuthRouter(userID int) *gin.Engine {
  44. gin.SetMode(gin.TestMode)
  45. r := gin.New()
  46. r.GET("/api/pricing/user/:model", func(c *gin.Context) {
  47. c.Set("id", userID)
  48. c.Next()
  49. }, GetUserPricing)
  50. return r
  51. }
  52. // setPricingCache 直接设置定价缓存用于测试
  53. func setPricingCache(pricing []model.Pricing) {
  54. model.SetTestPricing(pricing)
  55. }
  56. // withGroupRatio 临时设置分组倍率,测试结束后恢复
  57. func withGroupRatio(t *testing.T, jsonStr string) {
  58. t.Helper()
  59. original := ratio_setting.GetGroupRatioCopy()
  60. ratio_setting.UpdateGroupRatioByJSONString(jsonStr)
  61. t.Cleanup(func() {
  62. origJSON, _ := json.Marshal(original)
  63. ratio_setting.UpdateGroupRatioByJSONString(string(origJSON))
  64. })
  65. }
  66. // ---- 测试用例 ----
  67. // TestGetUserPricing_ModelNotFound 模型不存在时应返回错误
  68. func TestGetUserPricing_ModelNotFound(t *testing.T) {
  69. setupPricingTestDB(t)
  70. router := setupPricingTestRouter()
  71. setPricingCache([]model.Pricing{})
  72. w := httptest.NewRecorder()
  73. req, _ := http.NewRequest("GET", "/api/pricing/user/nonexistent-model", nil)
  74. router.ServeHTTP(w, req)
  75. var resp map[string]interface{}
  76. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  77. assert.False(t, resp["success"].(bool))
  78. assert.Contains(t, resp["message"], "未找到")
  79. }
  80. // TestGetUserPricing_NotLoggedIn 未登录用户应只返回原价
  81. func TestGetUserPricing_NotLoggedIn(t *testing.T) {
  82. setupPricingTestDB(t)
  83. router := setupPricingTestRouter()
  84. setPricingCache([]model.Pricing{
  85. {
  86. ModelName: "gpt-4o",
  87. QuotaType: 0,
  88. ModelRatio: 15,
  89. CompletionRatio: 4,
  90. EnableGroup: []string{"default", "vip"},
  91. },
  92. })
  93. w := httptest.NewRecorder()
  94. req, _ := http.NewRequest("GET", "/api/pricing/user/gpt-4o", nil)
  95. router.ServeHTTP(w, req)
  96. var resp map[string]interface{}
  97. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  98. assert.True(t, resp["success"].(bool))
  99. assert.Equal(t, false, resp["logged_in"])
  100. assert.Equal(t, "gpt-4o", resp["model_name"])
  101. assert.Equal(t, float64(0), resp["quota_type"])
  102. // 验证原价: model_ratio * 2 = 15 * 2 = 30
  103. assert.Equal(t, float64(30), resp["original_input"])
  104. // 输出原价: model_ratio * completion_ratio * 2 = 15 * 4 * 2 = 120
  105. assert.Equal(t, float64(120), resp["original_output"])
  106. // 不应有用户价字段
  107. _, hasUserInput := resp["user_input"]
  108. assert.False(t, hasUserInput)
  109. }
  110. // TestGetUserPricing_NotLoggedIn_PerCall 按次计费模型,未登录
  111. func TestGetUserPricing_NotLoggedIn_PerCall(t *testing.T) {
  112. setupPricingTestDB(t)
  113. router := setupPricingTestRouter()
  114. setPricingCache([]model.Pricing{
  115. {
  116. ModelName: "dall-e-3",
  117. QuotaType: 1,
  118. ModelPrice: 0.04,
  119. EnableGroup: []string{"default"},
  120. },
  121. })
  122. w := httptest.NewRecorder()
  123. req, _ := http.NewRequest("GET", "/api/pricing/user/dall-e-3", nil)
  124. router.ServeHTTP(w, req)
  125. var resp map[string]interface{}
  126. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  127. assert.True(t, resp["success"].(bool))
  128. assert.Equal(t, false, resp["logged_in"])
  129. assert.Equal(t, float64(0.04), resp["original_price"])
  130. }
  131. func TestGetUserPricingIncludesDisplayPricingWithoutChangingComputedPrice(t *testing.T) {
  132. setupPricingTestDB(t)
  133. modelName := "display-pricing-user-model"
  134. router := gin.New()
  135. router.GET("/api/pricing/user/*model", GetUserPricing)
  136. setPricingCache([]model.Pricing{
  137. {
  138. ModelName: modelName,
  139. QuotaType: model.QuotaTypeByCall,
  140. ModelPrice: 0.5,
  141. EnableGroup: []string{"default"},
  142. DisplayPricing: []types.ModelDisplayPricingItem{
  143. {Specification: "768p-6s", Price: 0.33333, Unit: "second", SortOrder: 1, DiscountRate: 1},
  144. },
  145. },
  146. })
  147. w := httptest.NewRecorder()
  148. req, _ := http.NewRequest("GET", "/api/pricing/user/"+modelName, nil)
  149. router.ServeHTTP(w, req)
  150. var resp map[string]interface{}
  151. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  152. require.Equal(t, http.StatusOK, w.Code)
  153. require.Contains(t, resp, "display_pricing")
  154. require.Equal(t, float64(0.5), resp["original_price"])
  155. require.NotEqual(t, float64(0.33333), resp["original_price"])
  156. }
  157. // TestGetUserPricing_LoggedIn_NoDiscount 已登录但无折扣(分组倍率=1,无个人倍率)
  158. func TestGetUserPricing_LoggedIn_NoDiscount(t *testing.T) {
  159. db := setupPricingTestDB(t)
  160. setPricingCache([]model.Pricing{
  161. {
  162. ModelName: "gpt-4o",
  163. QuotaType: 0,
  164. ModelRatio: 15,
  165. CompletionRatio: 4,
  166. EnableGroup: []string{"default", "vip"},
  167. },
  168. })
  169. // 创建测试用户(default 分组,默认倍率为 1)
  170. user := &model.User{Id: 100, Group: "default", Username: "testuser", Status: 1}
  171. require.NoError(t, db.Create(user).Error)
  172. router := setupAuthRouter(100)
  173. w := httptest.NewRecorder()
  174. req, _ := http.NewRequest("GET", "/api/pricing/user/gpt-4o", nil)
  175. router.ServeHTTP(w, req)
  176. var resp map[string]interface{}
  177. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  178. assert.True(t, resp["success"].(bool))
  179. assert.Equal(t, true, resp["logged_in"])
  180. // default 分组默认倍率为 1,无折扣
  181. assert.Equal(t, float64(0), resp["savings_percent"])
  182. }
  183. // TestGetUserPricing_LoggedIn_GroupDiscount 已登录,有分组折扣
  184. func TestGetUserPricing_LoggedIn_GroupDiscount(t *testing.T) {
  185. db := setupPricingTestDB(t)
  186. setPricingCache([]model.Pricing{
  187. {
  188. ModelName: "gpt-4o",
  189. QuotaType: 0,
  190. ModelRatio: 15,
  191. CompletionRatio: 4,
  192. EnableGroup: []string{"default", "vip"},
  193. },
  194. })
  195. // 创建 VIP 用户
  196. user := &model.User{Id: 200, Group: "vip", Username: "vipuser", Status: 1}
  197. require.NoError(t, db.Create(user).Error)
  198. // 设置 VIP 分组倍率为 0.8
  199. withGroupRatio(t, `{"default":1,"vip":0.8}`)
  200. router := setupAuthRouter(200)
  201. w := httptest.NewRecorder()
  202. req, _ := http.NewRequest("GET", "/api/pricing/user/gpt-4o", nil)
  203. router.ServeHTTP(w, req)
  204. var resp map[string]interface{}
  205. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  206. assert.True(t, resp["success"].(bool))
  207. assert.Equal(t, true, resp["logged_in"])
  208. assert.Equal(t, "vip", resp["group"])
  209. assert.Equal(t, float64(0.8), resp["group_ratio"])
  210. // 用户价 = 原价 * 0.8
  211. // 输入: 30 * 0.8 = 24
  212. assert.Equal(t, float64(24), resp["user_input"])
  213. // 输出: 120 * 0.8 = 96
  214. assert.Equal(t, float64(96), resp["user_output"])
  215. assert.Equal(t, float64(20), resp["savings_percent"])
  216. assert.Equal(t, "8折", resp["discount"])
  217. }
  218. // TestGetUserPricing_PerCall_WithDiscount 按次计费 + 折扣
  219. func TestGetUserPricing_PerCall_WithDiscount(t *testing.T) {
  220. db := setupPricingTestDB(t)
  221. setPricingCache([]model.Pricing{
  222. {
  223. ModelName: "dall-e-3",
  224. QuotaType: 1,
  225. ModelPrice: 0.04,
  226. EnableGroup: []string{"default", "vip"},
  227. },
  228. })
  229. user := &model.User{Id: 500, Group: "vip", Username: "percallvip", Status: 1}
  230. require.NoError(t, db.Create(user).Error)
  231. withGroupRatio(t, `{"default":1,"vip":0.5}`)
  232. router := setupAuthRouter(500)
  233. w := httptest.NewRecorder()
  234. req, _ := http.NewRequest("GET", "/api/pricing/user/dall-e-3", nil)
  235. router.ServeHTTP(w, req)
  236. var resp map[string]interface{}
  237. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  238. assert.True(t, resp["success"].(bool))
  239. assert.Equal(t, float64(0.04), resp["original_price"])
  240. assert.Equal(t, float64(0.02), resp["user_price"])
  241. assert.Equal(t, float64(50), resp["savings_percent"])
  242. assert.Equal(t, "5折", resp["discount"])
  243. }
  244. // TestFormatDiscount 折扣格式化测试
  245. func TestFormatDiscount(t *testing.T) {
  246. tests := []struct {
  247. ratio float64
  248. expected string
  249. }{
  250. {0.5, "5折"},
  251. {0.8, "8折"},
  252. {0.9, "9折"},
  253. {0.85, "8.5折"},
  254. {0.75, "7.5折"},
  255. {0.95, "9.5折"},
  256. {1.0, ""},
  257. {0.0, "免费"},
  258. }
  259. for _, tt := range tests {
  260. result := formatDiscount(tt.ratio)
  261. assert.Equal(t, tt.expected, result, "ratio=%.2f", tt.ratio)
  262. }
  263. }