Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

350 wiersze
10 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. func TestGetPricingIncludesDisplayPricingForModelSquare(t *testing.T) {
  158. setupPricingTestDB(t)
  159. modelName := "display-pricing-square-model"
  160. router := gin.New()
  161. router.GET("/api/pricing", GetPricing)
  162. setPricingCache([]model.Pricing{
  163. {
  164. ModelName: modelName,
  165. QuotaType: model.QuotaTypeByCall,
  166. ModelPrice: 0.5,
  167. EnableGroup: []string{"default"},
  168. DisplayPricing: []types.ModelDisplayPricingItem{
  169. {Specification: "768p-6s", Price: 0.33333, Unit: "second", SortOrder: 1, DiscountRate: 1},
  170. },
  171. },
  172. })
  173. withGroupRatio(t, `{"default":1}`)
  174. w := httptest.NewRecorder()
  175. req, _ := http.NewRequest("GET", "/api/pricing", nil)
  176. router.ServeHTTP(w, req)
  177. var resp map[string]interface{}
  178. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  179. require.Equal(t, http.StatusOK, w.Code)
  180. require.Equal(t, true, resp["success"])
  181. data := resp["data"].([]interface{})
  182. require.Len(t, data, 1)
  183. item := data[0].(map[string]interface{})
  184. require.Equal(t, modelName, item["model_name"])
  185. require.Contains(t, item, "display_pricing")
  186. displayPricing := item["display_pricing"].([]interface{})
  187. require.Len(t, displayPricing, 1)
  188. require.Equal(t, "768p-6s", displayPricing[0].(map[string]interface{})["specification"])
  189. }
  190. // TestGetUserPricing_LoggedIn_NoDiscount 已登录但无折扣(分组倍率=1,无个人倍率)
  191. func TestGetUserPricing_LoggedIn_NoDiscount(t *testing.T) {
  192. db := setupPricingTestDB(t)
  193. setPricingCache([]model.Pricing{
  194. {
  195. ModelName: "gpt-4o",
  196. QuotaType: 0,
  197. ModelRatio: 15,
  198. CompletionRatio: 4,
  199. EnableGroup: []string{"default", "vip"},
  200. },
  201. })
  202. // 创建测试用户(default 分组,默认倍率为 1)
  203. user := &model.User{Id: 100, Group: "default", Username: "testuser", Status: 1}
  204. require.NoError(t, db.Create(user).Error)
  205. router := setupAuthRouter(100)
  206. w := httptest.NewRecorder()
  207. req, _ := http.NewRequest("GET", "/api/pricing/user/gpt-4o", nil)
  208. router.ServeHTTP(w, req)
  209. var resp map[string]interface{}
  210. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  211. assert.True(t, resp["success"].(bool))
  212. assert.Equal(t, true, resp["logged_in"])
  213. // default 分组默认倍率为 1,无折扣
  214. assert.Equal(t, float64(0), resp["savings_percent"])
  215. }
  216. // TestGetUserPricing_LoggedIn_GroupDiscount 已登录,有分组折扣
  217. func TestGetUserPricing_LoggedIn_GroupDiscount(t *testing.T) {
  218. db := setupPricingTestDB(t)
  219. setPricingCache([]model.Pricing{
  220. {
  221. ModelName: "gpt-4o",
  222. QuotaType: 0,
  223. ModelRatio: 15,
  224. CompletionRatio: 4,
  225. EnableGroup: []string{"default", "vip"},
  226. },
  227. })
  228. // 创建 VIP 用户
  229. user := &model.User{Id: 200, Group: "vip", Username: "vipuser", Status: 1}
  230. require.NoError(t, db.Create(user).Error)
  231. // 设置 VIP 分组倍率为 0.8
  232. withGroupRatio(t, `{"default":1,"vip":0.8}`)
  233. router := setupAuthRouter(200)
  234. w := httptest.NewRecorder()
  235. req, _ := http.NewRequest("GET", "/api/pricing/user/gpt-4o", nil)
  236. router.ServeHTTP(w, req)
  237. var resp map[string]interface{}
  238. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  239. assert.True(t, resp["success"].(bool))
  240. assert.Equal(t, true, resp["logged_in"])
  241. assert.Equal(t, "vip", resp["group"])
  242. assert.Equal(t, float64(0.8), resp["group_ratio"])
  243. // 用户价 = 原价 * 0.8
  244. // 输入: 30 * 0.8 = 24
  245. assert.Equal(t, float64(24), resp["user_input"])
  246. // 输出: 120 * 0.8 = 96
  247. assert.Equal(t, float64(96), resp["user_output"])
  248. assert.Equal(t, float64(20), resp["savings_percent"])
  249. assert.Equal(t, "8折", resp["discount"])
  250. }
  251. // TestGetUserPricing_PerCall_WithDiscount 按次计费 + 折扣
  252. func TestGetUserPricing_PerCall_WithDiscount(t *testing.T) {
  253. db := setupPricingTestDB(t)
  254. setPricingCache([]model.Pricing{
  255. {
  256. ModelName: "dall-e-3",
  257. QuotaType: 1,
  258. ModelPrice: 0.04,
  259. EnableGroup: []string{"default", "vip"},
  260. },
  261. })
  262. user := &model.User{Id: 500, Group: "vip", Username: "percallvip", Status: 1}
  263. require.NoError(t, db.Create(user).Error)
  264. withGroupRatio(t, `{"default":1,"vip":0.5}`)
  265. router := setupAuthRouter(500)
  266. w := httptest.NewRecorder()
  267. req, _ := http.NewRequest("GET", "/api/pricing/user/dall-e-3", nil)
  268. router.ServeHTTP(w, req)
  269. var resp map[string]interface{}
  270. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
  271. assert.True(t, resp["success"].(bool))
  272. assert.Equal(t, float64(0.04), resp["original_price"])
  273. assert.Equal(t, float64(0.02), resp["user_price"])
  274. assert.Equal(t, float64(50), resp["savings_percent"])
  275. assert.Equal(t, "5折", resp["discount"])
  276. }
  277. // TestFormatDiscount 折扣格式化测试
  278. func TestFormatDiscount(t *testing.T) {
  279. tests := []struct {
  280. ratio float64
  281. expected string
  282. }{
  283. {0.5, "5折"},
  284. {0.8, "8折"},
  285. {0.9, "9折"},
  286. {0.85, "8.5折"},
  287. {0.75, "7.5折"},
  288. {0.95, "9.5折"},
  289. {1.0, ""},
  290. {0.0, "免费"},
  291. }
  292. for _, tt := range tests {
  293. result := formatDiscount(tt.ratio)
  294. assert.Equal(t, tt.expected, result, "ratio=%.2f", tt.ratio)
  295. }
  296. }