You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

342 regels
8.6 KiB

  1. package model
  2. import (
  3. "testing"
  4. "github.com/QuantumNous/new-api/common"
  5. "github.com/glebarez/sqlite"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. "gorm.io/gorm"
  9. )
  10. func setupRedemptionDB(t *testing.T) *gorm.DB {
  11. t.Helper()
  12. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  13. require.NoError(t, err)
  14. sqlDB, _ := db.DB()
  15. sqlDB.SetMaxOpenConns(1)
  16. origDB := DB
  17. origLogDB := LOG_DB
  18. DB = db
  19. LOG_DB = db
  20. common.UsingSQLite = true
  21. common.RedisEnabled = false
  22. require.NoError(t, db.AutoMigrate(&User{}, &Redemption{}, &Log{}))
  23. t.Cleanup(func() {
  24. DB = origDB
  25. LOG_DB = origLogDB
  26. sqlDB.Close()
  27. })
  28. return db
  29. }
  30. func TestRedeem_Success(t *testing.T) {
  31. db := setupRedemptionDB(t)
  32. // 创建测试用户
  33. user := User{
  34. Id: 100,
  35. Username: "redeem_test",
  36. Password: "hashed_password",
  37. Quota: 100000,
  38. }
  39. require.NoError(t, db.Create(&user).Error)
  40. // 创建兑换码
  41. redemption := Redemption{
  42. Id: 1,
  43. UserId: 1,
  44. Key: "test-key-123",
  45. Status: common.RedemptionCodeStatusEnabled,
  46. Quota: 50000,
  47. CreatedTime: common.GetTimestamp(),
  48. ExpiredTime: 0, // 永不过期
  49. }
  50. require.NoError(t, db.Create(&redemption).Error)
  51. // 设置回调
  52. callbackCalled := false
  53. var receivedUserId int
  54. var receivedQuota int
  55. SetQuotaUpdateCallback(func(userId int, quota int) {
  56. callbackCalled = true
  57. receivedUserId = userId
  58. receivedQuota = quota
  59. })
  60. defer SetQuotaUpdateCallback(nil)
  61. // 执行兑换
  62. quota, err := Redeem("test-key-123", 100)
  63. require.NoError(t, err)
  64. assert.Equal(t, 50000, quota)
  65. // 验证回调被触发
  66. assert.True(t, callbackCalled)
  67. assert.Equal(t, 100, receivedUserId)
  68. assert.Equal(t, 150000, receivedQuota) // 原始 100000 + 兑换 50000
  69. // 验证用户额度已更新
  70. var updatedUser User
  71. require.NoError(t, db.First(&updatedUser, 100).Error)
  72. assert.Equal(t, 150000, updatedUser.Quota)
  73. // 验证兑换码状态已更新
  74. var updatedRedemption Redemption
  75. require.NoError(t, db.First(&updatedRedemption, 1).Error)
  76. assert.Equal(t, common.RedemptionCodeStatusUsed, updatedRedemption.Status)
  77. assert.Equal(t, 100, updatedRedemption.UsedUserId)
  78. }
  79. func TestRedeem_InvalidKey(t *testing.T) {
  80. setupRedemptionDB(t)
  81. // 测试无效的兑换码
  82. quota, err := Redeem("invalid-key", 100)
  83. assert.Error(t, err)
  84. assert.Equal(t, 0, quota)
  85. }
  86. func TestRedeem_AlreadyUsed(t *testing.T) {
  87. db := setupRedemptionDB(t)
  88. // 创建测试用户
  89. user := User{
  90. Id: 100,
  91. Username: "redeem_used_test",
  92. Password: "hashed_password",
  93. Quota: 100000,
  94. }
  95. require.NoError(t, db.Create(&user).Error)
  96. // 创建已使用的兑换码
  97. redemption := Redemption{
  98. Id: 1,
  99. UserId: 1,
  100. Key: "used-key",
  101. Status: common.RedemptionCodeStatusUsed,
  102. Quota: 50000,
  103. CreatedTime: common.GetTimestamp(),
  104. RedeemedTime: common.GetTimestamp(),
  105. UsedUserId: 99,
  106. }
  107. require.NoError(t, db.Create(&redemption).Error)
  108. // 尝试兑换已使用的兑换码
  109. quota, err := Redeem("used-key", 100)
  110. assert.Error(t, err)
  111. assert.Equal(t, 0, quota)
  112. // 错误会被包装为 ErrRedeemFailed,但底层错误消息会记录在日志中
  113. assert.Equal(t, ErrRedeemFailed, err)
  114. }
  115. func TestRedeem_Expired(t *testing.T) {
  116. db := setupRedemptionDB(t)
  117. // 创建测试用户
  118. user := User{
  119. Id: 100,
  120. Username: "redeem_expired_test",
  121. Password: "hashed_password",
  122. Quota: 100000,
  123. }
  124. require.NoError(t, db.Create(&user).Error)
  125. // 创建已过期的兑换码
  126. redemption := Redemption{
  127. Id: 1,
  128. UserId: 1,
  129. Key: "expired-key",
  130. Status: common.RedemptionCodeStatusEnabled,
  131. Quota: 50000,
  132. CreatedTime: common.GetTimestamp() - 86400,
  133. ExpiredTime: common.GetTimestamp() - 3600, // 已过期
  134. }
  135. require.NoError(t, db.Create(&redemption).Error)
  136. // 尝试兑换已过期的兑换码
  137. quota, err := Redeem("expired-key", 100)
  138. assert.Error(t, err)
  139. assert.Equal(t, 0, quota)
  140. // 错误会被包装为 ErrRedeemFailed
  141. assert.Equal(t, ErrRedeemFailed, err)
  142. }
  143. func TestRedeem_CallbackPanic(t *testing.T) {
  144. db := setupRedemptionDB(t)
  145. // 创建测试用户
  146. user := User{
  147. Id: 100,
  148. Username: "redeem_panic_test",
  149. Password: "hashed_password",
  150. Quota: 100000,
  151. }
  152. require.NoError(t, db.Create(&user).Error)
  153. // 创建兑换码
  154. redemption := Redemption{
  155. Id: 1,
  156. UserId: 1,
  157. Key: "panic-key",
  158. Status: common.RedemptionCodeStatusEnabled,
  159. Quota: 50000,
  160. CreatedTime: common.GetTimestamp(),
  161. }
  162. require.NoError(t, db.Create(&redemption).Error)
  163. // 设置一个会 panic 的回调
  164. SetQuotaUpdateCallback(func(userId int, quota int) {
  165. panic("callback panic in redeem")
  166. })
  167. defer SetQuotaUpdateCallback(nil)
  168. // 执行兑换(回调 panic 不应影响兑换成功)
  169. quota, err := Redeem("panic-key", 100)
  170. require.NoError(t, err)
  171. assert.Equal(t, 50000, quota)
  172. // 验证用户额度已更新
  173. var updatedUser User
  174. require.NoError(t, db.First(&updatedUser, 100).Error)
  175. assert.Equal(t, 150000, updatedUser.Quota)
  176. }
  177. func TestRedeem_SyncedUser(t *testing.T) {
  178. db := setupRedemptionDB(t)
  179. // 创建同步用户(从 Master 节点同步过来的)
  180. user := User{
  181. Id: 100,
  182. Username: "synced_redeem_test",
  183. Password: "hashed_password",
  184. Quota: 100000,
  185. Source: common.UserSourceSynced,
  186. RemoteUserId: 1000,
  187. SyncedQuota: 100000,
  188. }
  189. require.NoError(t, db.Create(&user).Error)
  190. // 创建兑换码
  191. redemption := Redemption{
  192. Id: 1,
  193. UserId: 1,
  194. Key: "synced-redeem-key",
  195. Status: common.RedemptionCodeStatusEnabled,
  196. Quota: 50000,
  197. CreatedTime: common.GetTimestamp(),
  198. }
  199. require.NoError(t, db.Create(&redemption).Error)
  200. // 设置回调
  201. callbackCalled := false
  202. SetQuotaUpdateCallback(func(userId int, quota int) {
  203. callbackCalled = true
  204. })
  205. defer SetQuotaUpdateCallback(nil)
  206. // 执行兑换
  207. quota, err := Redeem("synced-redeem-key", 100)
  208. require.NoError(t, err)
  209. assert.Equal(t, 50000, quota)
  210. // 验证回调被触发(Master 节点需要同步到 Slave)
  211. assert.True(t, callbackCalled)
  212. // 验证用户额度已更新
  213. var updatedUser User
  214. require.NoError(t, db.First(&updatedUser, 100).Error)
  215. assert.Equal(t, 150000, updatedUser.Quota)
  216. }
  217. func TestRedemptionUpdateRemarkPersistsRemark(t *testing.T) {
  218. db := setupRedemptionDB(t)
  219. redemption := Redemption{
  220. Id: 1,
  221. UserId: 1,
  222. Key: "remark-update-key",
  223. Status: common.RedemptionCodeStatusEnabled,
  224. Name: "starter",
  225. Remark: "initial-note",
  226. Quota: 100,
  227. CreatedTime: common.GetTimestamp(),
  228. }
  229. require.NoError(t, db.Create(&redemption).Error)
  230. redemption.Remark = "vip-updated"
  231. require.NoError(t, redemption.Update())
  232. var updated Redemption
  233. require.NoError(t, db.First(&updated, redemption.Id).Error)
  234. assert.Equal(t, "vip-updated", updated.Remark)
  235. }
  236. func TestSearchRedemptionsByRemark(t *testing.T) {
  237. db := setupRedemptionDB(t)
  238. require.NoError(t, db.Create(&Redemption{
  239. Id: 1,
  240. UserId: 1,
  241. Key: "remark-search-key",
  242. Status: common.RedemptionCodeStatusEnabled,
  243. Name: "starter-pack",
  244. Remark: "vip benefit",
  245. Quota: 100,
  246. CreatedTime: common.GetTimestamp(),
  247. }).Error)
  248. require.NoError(t, db.Create(&Redemption{
  249. Id: 2,
  250. UserId: 1,
  251. Key: "remark-search-other",
  252. Status: common.RedemptionCodeStatusEnabled,
  253. Name: "basic-pack",
  254. Remark: "standard benefit",
  255. Quota: 100,
  256. CreatedTime: common.GetTimestamp(),
  257. }).Error)
  258. results, total, err := SearchRedemptions("vip", 0, 10)
  259. require.NoError(t, err)
  260. require.Equal(t, int64(1), total)
  261. require.Len(t, results, 1)
  262. assert.Equal(t, 1, results[0].Id)
  263. assert.Equal(t, "vip benefit", results[0].Remark)
  264. }
  265. func TestSearchRedemptionsByRemarkWithNumericKeyword(t *testing.T) {
  266. db := setupRedemptionDB(t)
  267. require.NoError(t, db.Create(&Redemption{
  268. Id: 1,
  269. UserId: 1,
  270. Key: "remark-search-numeric",
  271. Status: common.RedemptionCodeStatusEnabled,
  272. Name: "starter-pack",
  273. Remark: "123-vip benefit",
  274. Quota: 100,
  275. CreatedTime: common.GetTimestamp(),
  276. }).Error)
  277. require.NoError(t, db.Create(&Redemption{
  278. Id: 2,
  279. UserId: 1,
  280. Key: "remark-search-other-numeric",
  281. Status: common.RedemptionCodeStatusEnabled,
  282. Name: "basic-pack",
  283. Remark: "standard benefit",
  284. Quota: 100,
  285. CreatedTime: common.GetTimestamp(),
  286. }).Error)
  287. results, total, err := SearchRedemptions("123", 0, 10)
  288. require.NoError(t, err)
  289. require.Equal(t, int64(1), total)
  290. require.Len(t, results, 1)
  291. assert.Equal(t, 1, results[0].Id)
  292. assert.Equal(t, "123-vip benefit", results[0].Remark)
  293. }