Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

381 строка
9.8 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.ErrorIs(t, err, ErrSyncedUserRedeemDenied)
  209. assert.Equal(t, 0, quota)
  210. // 同步用户不能兑换,回调不应触发
  211. assert.False(t, callbackCalled)
  212. // 验证用户额度和同步额度均未更新
  213. var updatedUser User
  214. require.NoError(t, db.First(&updatedUser, 100).Error)
  215. assert.Equal(t, 100000, updatedUser.Quota)
  216. assert.Equal(t, 100000, updatedUser.SyncedQuota)
  217. // 验证兑换码仍保持可用,不能被同步用户消耗
  218. var updatedRedemption Redemption
  219. require.NoError(t, db.First(&updatedRedemption, 1).Error)
  220. assert.Equal(t, common.RedemptionCodeStatusEnabled, updatedRedemption.Status)
  221. assert.Equal(t, 0, updatedRedemption.UsedUserId)
  222. assert.Equal(t, int64(0), updatedRedemption.RedeemedTime)
  223. }
  224. func TestRedeem_LocalUserStillWorks(t *testing.T) {
  225. db := setupRedemptionDB(t)
  226. user := User{
  227. Id: 101,
  228. Username: "local_redeem_test",
  229. Password: "hashed_password",
  230. Quota: 100000,
  231. Source: common.UserSourceLocal,
  232. }
  233. require.NoError(t, db.Create(&user).Error)
  234. redemption := Redemption{
  235. Id: 2,
  236. UserId: 1,
  237. Key: "local-redeem-key",
  238. Status: common.RedemptionCodeStatusEnabled,
  239. Quota: 50000,
  240. CreatedTime: common.GetTimestamp(),
  241. }
  242. require.NoError(t, db.Create(&redemption).Error)
  243. quota, err := Redeem("local-redeem-key", 101)
  244. require.NoError(t, err)
  245. assert.Equal(t, 50000, quota)
  246. var updatedUser User
  247. require.NoError(t, db.First(&updatedUser, 101).Error)
  248. assert.Equal(t, 150000, updatedUser.Quota)
  249. }
  250. func TestRedemptionUpdateRemarkPersistsRemark(t *testing.T) {
  251. db := setupRedemptionDB(t)
  252. redemption := Redemption{
  253. Id: 1,
  254. UserId: 1,
  255. Key: "remark-update-key",
  256. Status: common.RedemptionCodeStatusEnabled,
  257. Name: "starter",
  258. Remark: "initial-note",
  259. Quota: 100,
  260. CreatedTime: common.GetTimestamp(),
  261. }
  262. require.NoError(t, db.Create(&redemption).Error)
  263. redemption.Remark = "vip-updated"
  264. require.NoError(t, redemption.Update())
  265. var updated Redemption
  266. require.NoError(t, db.First(&updated, redemption.Id).Error)
  267. assert.Equal(t, "vip-updated", updated.Remark)
  268. }
  269. func TestSearchRedemptionsByRemark(t *testing.T) {
  270. db := setupRedemptionDB(t)
  271. require.NoError(t, db.Create(&Redemption{
  272. Id: 1,
  273. UserId: 1,
  274. Key: "remark-search-key",
  275. Status: common.RedemptionCodeStatusEnabled,
  276. Name: "starter-pack",
  277. Remark: "vip benefit",
  278. Quota: 100,
  279. CreatedTime: common.GetTimestamp(),
  280. }).Error)
  281. require.NoError(t, db.Create(&Redemption{
  282. Id: 2,
  283. UserId: 1,
  284. Key: "remark-search-other",
  285. Status: common.RedemptionCodeStatusEnabled,
  286. Name: "basic-pack",
  287. Remark: "standard benefit",
  288. Quota: 100,
  289. CreatedTime: common.GetTimestamp(),
  290. }).Error)
  291. results, total, err := SearchRedemptions("vip", 0, 10)
  292. require.NoError(t, err)
  293. require.Equal(t, int64(1), total)
  294. require.Len(t, results, 1)
  295. assert.Equal(t, 1, results[0].Id)
  296. assert.Equal(t, "vip benefit", results[0].Remark)
  297. }
  298. func TestSearchRedemptionsByRemarkWithNumericKeyword(t *testing.T) {
  299. db := setupRedemptionDB(t)
  300. require.NoError(t, db.Create(&Redemption{
  301. Id: 1,
  302. UserId: 1,
  303. Key: "remark-search-numeric",
  304. Status: common.RedemptionCodeStatusEnabled,
  305. Name: "starter-pack",
  306. Remark: "123-vip benefit",
  307. Quota: 100,
  308. CreatedTime: common.GetTimestamp(),
  309. }).Error)
  310. require.NoError(t, db.Create(&Redemption{
  311. Id: 2,
  312. UserId: 1,
  313. Key: "remark-search-other-numeric",
  314. Status: common.RedemptionCodeStatusEnabled,
  315. Name: "basic-pack",
  316. Remark: "standard benefit",
  317. Quota: 100,
  318. CreatedTime: common.GetTimestamp(),
  319. }).Error)
  320. results, total, err := SearchRedemptions("123", 0, 10)
  321. require.NoError(t, err)
  322. require.Equal(t, int64(1), total)
  323. require.Len(t, results, 1)
  324. assert.Equal(t, 1, results[0].Id)
  325. assert.Equal(t, "123-vip benefit", results[0].Remark)
  326. }