1
0

inbound_autorenew_maxcount_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package service
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  9. )
  10. // A prepaid plan must stop itself: once as many renewals have fired as the
  11. // operator allowed, the client expires like any other (#5804).
  12. func TestAutoRenewClients_StopsAtMaxCount(t *testing.T) {
  13. setupBulkDB(t)
  14. svc := &InboundService{}
  15. db := database.GetDB()
  16. past := time.Now().Add(-48 * time.Hour).UnixMilli()
  17. clients := []model.Client{
  18. {Email: "spent@x", ID: "11111111-1111-1111-1111-111111111111", Enable: false, Reset: 30, ResetMax: 2, ExpiryTime: past},
  19. {Email: "left@x", ID: "22222222-2222-2222-2222-222222222222", Enable: false, Reset: 30, ResetMax: 2, ExpiryTime: past},
  20. }
  21. ib := mkInbound(t, 30101, model.VLESS, clientsSettings(t, clients))
  22. if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
  23. t.Fatalf("SyncInbound: %v", err)
  24. }
  25. rows := []xray.ClientTraffic{
  26. {InboundId: ib.Id, Email: "spent@x", Enable: false, Reset: 30, ResetMax: 2, ResetCount: 2, ExpiryTime: past},
  27. {InboundId: ib.Id, Email: "left@x", Enable: false, Reset: 30, ResetMax: 2, ResetCount: 1, ExpiryTime: past},
  28. }
  29. if err := db.Create(&rows).Error; err != nil {
  30. t.Fatalf("seed client_traffics: %v", err)
  31. }
  32. if _, count, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
  33. t.Fatalf("autoRenewClients: %v", err)
  34. } else if count != 1 {
  35. t.Fatalf("renewed count = %d, want 1: only the client with an allowance left", count)
  36. }
  37. var spent xray.ClientTraffic
  38. if err := db.Where("email = ?", "spent@x").First(&spent).Error; err != nil {
  39. t.Fatal(err)
  40. }
  41. if spent.ExpiryTime != past {
  42. t.Fatalf("a client that used its allowance was renewed anyway: expiry %d", spent.ExpiryTime)
  43. }
  44. var left xray.ClientTraffic
  45. if err := db.Where("email = ?", "left@x").First(&left).Error; err != nil {
  46. t.Fatal(err)
  47. }
  48. if left.ExpiryTime <= past {
  49. t.Fatal("a client with an allowance left was not renewed")
  50. }
  51. if left.ResetCount != 2 {
  52. t.Fatalf("reset count = %d after one renewal, want 2", left.ResetCount)
  53. }
  54. }
  55. // Catching up several missed periods spends one allowance per period: a client
  56. // that was away for three cycles must not receive three of them for free.
  57. func TestAutoRenewClients_CatchUpSpendsOneAllowancePerPeriod(t *testing.T) {
  58. setupBulkDB(t)
  59. svc := &InboundService{}
  60. db := database.GetDB()
  61. // Three whole 30-day periods behind.
  62. past := time.Now().Add(-95 * 24 * time.Hour).UnixMilli()
  63. clients := []model.Client{
  64. {Email: "away@x", ID: "33333333-3333-3333-3333-333333333333", Enable: false, Reset: 30, ResetMax: 2, ExpiryTime: past},
  65. }
  66. ib := mkInbound(t, 30102, model.VLESS, clientsSettings(t, clients))
  67. if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
  68. t.Fatalf("SyncInbound: %v", err)
  69. }
  70. if err := db.Create(&xray.ClientTraffic{
  71. InboundId: ib.Id, Email: "away@x", Enable: false, Reset: 30, ResetMax: 2, ExpiryTime: past,
  72. }).Error; err != nil {
  73. t.Fatalf("seed client_traffics: %v", err)
  74. }
  75. if _, _, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
  76. t.Fatalf("autoRenewClients: %v", err)
  77. }
  78. var row xray.ClientTraffic
  79. if err := db.Where("email = ?", "away@x").First(&row).Error; err != nil {
  80. t.Fatal(err)
  81. }
  82. if row.ResetCount != 2 {
  83. t.Fatalf("reset count = %d, want the 2 the cap allowed", row.ResetCount)
  84. }
  85. // Two periods granted, three needed: the client stays expired rather than
  86. // silently receiving the third.
  87. want := past + 2*30*86400000
  88. if row.ExpiryTime != want {
  89. t.Fatalf("expiry = %d, want %d: exactly the periods the cap paid for", row.ExpiryTime, want)
  90. }
  91. if row.ExpiryTime > time.Now().UnixMilli() {
  92. t.Fatal("the capped catch-up handed out a future expiry it had not paid for")
  93. }
  94. }
  95. // No cap set is the existing behaviour: renew for as long as the client keeps
  96. // expiring.
  97. func TestAutoRenewClients_NoCapRenewsAsBefore(t *testing.T) {
  98. setupBulkDB(t)
  99. svc := &InboundService{}
  100. db := database.GetDB()
  101. past := time.Now().Add(-48 * time.Hour).UnixMilli()
  102. clients := []model.Client{
  103. {Email: "forever@x", ID: "44444444-4444-4444-4444-444444444444", Enable: false, Reset: 30, ExpiryTime: past},
  104. }
  105. ib := mkInbound(t, 30103, model.VLESS, clientsSettings(t, clients))
  106. if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
  107. t.Fatalf("SyncInbound: %v", err)
  108. }
  109. if err := db.Create(&xray.ClientTraffic{
  110. InboundId: ib.Id, Email: "forever@x", Enable: false, Reset: 30, ResetCount: 99, ExpiryTime: past,
  111. }).Error; err != nil {
  112. t.Fatalf("seed client_traffics: %v", err)
  113. }
  114. if _, count, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
  115. t.Fatalf("autoRenewClients: %v", err)
  116. } else if count != 1 {
  117. t.Fatalf("renewed count = %d, want 1: a client without a cap keeps renewing", count)
  118. }
  119. }
  120. // The cap has to survive the clients table, not just the settings JSON: an
  121. // ordinary edit rebuilds the client from the record and writes it back (#5804).
  122. func TestClientEditKeepsTheRenewalCap(t *testing.T) {
  123. setupBulkDB(t)
  124. svc := &InboundService{}
  125. db := database.GetDB()
  126. clients := []model.Client{
  127. {Email: "cap@x", ID: "44444444-4444-4444-4444-444444444444", Enable: true, Reset: 30, ResetMax: 3, ExpiryTime: time.Now().Add(24 * time.Hour).UnixMilli()},
  128. }
  129. ib := mkInbound(t, 30104, model.VLESS, clientsSettings(t, clients))
  130. if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
  131. t.Fatalf("SyncInbound: %v", err)
  132. }
  133. mkTraffic(t, ib.Id, "cap@x", 10, 20, 0, 0, true)
  134. rec, err := svc.clientService.GetRecordByEmail(nil, "cap@x")
  135. if err != nil {
  136. t.Fatalf("GetRecordByEmail: %v", err)
  137. }
  138. if rec.ResetMax != 3 {
  139. t.Fatalf("clients.reset_max = %d, want the 3 the client was created with", rec.ResetMax)
  140. }
  141. // What the edit dialog does: hydrate the record, change something else, save.
  142. edited := rec.ToClient()
  143. edited.Comment = "renamed"
  144. if _, err := svc.clientService.Update(svc, rec.Id, *edited, rec.LimitHwid); err != nil {
  145. t.Fatalf("Update: %v", err)
  146. }
  147. var stored model.Inbound
  148. if err := db.Where("id = ?", ib.Id).First(&stored).Error; err != nil {
  149. t.Fatal(err)
  150. }
  151. var settings struct {
  152. Clients []model.Client `json:"clients"`
  153. }
  154. if err := json.Unmarshal([]byte(stored.Settings), &settings); err != nil {
  155. t.Fatalf("parse inbound settings: %v", err)
  156. }
  157. if len(settings.Clients) != 1 {
  158. t.Fatalf("inbound holds %d clients, want 1", len(settings.Clients))
  159. }
  160. if settings.Clients[0].ResetMax != 3 {
  161. t.Fatalf("inbound settings resetMax = %d after an unrelated edit, want 3: the cap was silently lifted", settings.Clients[0].ResetMax)
  162. }
  163. rec, err = svc.clientService.GetRecordByEmail(nil, "cap@x")
  164. if err != nil {
  165. t.Fatalf("GetRecordByEmail after edit: %v", err)
  166. }
  167. if rec.ResetMax != 3 {
  168. t.Fatalf("clients.reset_max = %d after an unrelated edit, want 3", rec.ResetMax)
  169. }
  170. }
  171. // A cap that runs out mid-catch-up leaves the client expired, so the renewal
  172. // side effects must not fire: disableInvalidClients would undo them at once.
  173. func TestAutoRenewClients_TruncatedCatchUpLeavesTheClientDisabled(t *testing.T) {
  174. setupBulkDB(t)
  175. svc := &InboundService{}
  176. db := database.GetDB()
  177. // Five periods behind with one allowance left: one 30-day step cannot reach
  178. // the present, so the client stays expired.
  179. past := time.Now().Add(-150 * 24 * time.Hour).UnixMilli()
  180. clients := []model.Client{
  181. {Email: "short@x", ID: "55555555-5555-5555-5555-555555555555", Enable: false, Reset: 30, ResetMax: 3, ExpiryTime: past},
  182. }
  183. ib := mkInbound(t, 30105, model.VLESS, clientsSettings(t, clients))
  184. if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
  185. t.Fatalf("SyncInbound: %v", err)
  186. }
  187. if err := db.Create(&xray.ClientTraffic{
  188. InboundId: ib.Id, Email: "short@x", Enable: false, Reset: 30, ResetMax: 3, ResetCount: 2,
  189. Up: 111, Down: 222, ExpiryTime: past,
  190. }).Error; err != nil {
  191. t.Fatalf("seed client_traffics: %v", err)
  192. }
  193. if _, _, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
  194. t.Fatalf("autoRenewClients: %v", err)
  195. }
  196. var row xray.ClientTraffic
  197. if err := db.Where("email = ?", "short@x").First(&row).Error; err != nil {
  198. t.Fatal(err)
  199. }
  200. if row.ExpiryTime >= time.Now().UnixMilli() {
  201. t.Fatalf("expiry %d reached the present: the cap did not truncate the catch-up", row.ExpiryTime)
  202. }
  203. if row.Enable {
  204. t.Fatal("a client still expired after a truncated catch-up was enabled: xray gains a user only to lose it again")
  205. }
  206. if row.Up != 111 || row.Down != 222 {
  207. t.Fatalf("counters zeroed for periods the client can never use: up=%d down=%d", row.Up, row.Down)
  208. }
  209. }
  210. // The cap is useless if it can only be chosen once. The test above passes even
  211. // without the record write, because nothing overwrites the value it checks.
  212. func TestClientEditChangesTheRenewalCap(t *testing.T) {
  213. setupBulkDB(t)
  214. svc := &InboundService{}
  215. clients := []model.Client{
  216. {
  217. Email: "chg@x", ID: "77777777-7777-7777-7777-777777777777", Enable: true, Reset: 30, ResetMax: 3,
  218. ExpiryTime: time.Now().Add(24 * time.Hour).UnixMilli(),
  219. },
  220. }
  221. ib := mkInbound(t, 30106, model.VLESS, clientsSettings(t, clients))
  222. if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
  223. t.Fatalf("SyncInbound: %v", err)
  224. }
  225. mkTraffic(t, ib.Id, "chg@x", 0, 0, 0, 0, true)
  226. rec, err := svc.clientService.GetRecordByEmail(nil, "chg@x")
  227. if err != nil {
  228. t.Fatalf("GetRecordByEmail: %v", err)
  229. }
  230. // The customer buys another block of periods, which is the whole point of
  231. // the field being editable.
  232. edited := rec.ToClient()
  233. edited.ResetMax = 6
  234. if _, err := svc.clientService.Update(svc, rec.Id, *edited, rec.LimitHwid); err != nil {
  235. t.Fatalf("Update: %v", err)
  236. }
  237. rec, err = svc.clientService.GetRecordByEmail(nil, "chg@x")
  238. if err != nil {
  239. t.Fatalf("GetRecordByEmail after edit: %v", err)
  240. }
  241. if rec.ResetMax != 6 {
  242. t.Fatalf("clients.reset_max = %d after the operator raised the cap to 6", rec.ResetMax)
  243. }
  244. // Lifting the cap entirely has to work too.
  245. edited = rec.ToClient()
  246. edited.ResetMax = 0
  247. if _, err := svc.clientService.Update(svc, rec.Id, *edited, rec.LimitHwid); err != nil {
  248. t.Fatalf("Update to uncapped: %v", err)
  249. }
  250. rec, err = svc.clientService.GetRecordByEmail(nil, "chg@x")
  251. if err != nil {
  252. t.Fatalf("GetRecordByEmail after lifting the cap: %v", err)
  253. }
  254. if rec.ResetMax != 0 {
  255. t.Fatalf("clients.reset_max = %d after the operator lifted the cap", rec.ResetMax)
  256. }
  257. }