1
0

inbound_autorenew_weekly_test.go 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  8. )
  9. func TestAutoRenewClients_WeeklyMode(t *testing.T) {
  10. for _, tt := range []struct {
  11. name, zone string
  12. weekday, max, count int
  13. inclusive, manual bool
  14. }{
  15. {name: "UTC Monday catch-up across shared inbounds", zone: "UTC", weekday: 1},
  16. {name: "New York Sunday across daylight saving", zone: "America/New_York", weekday: 7},
  17. {name: "capped catch-up stays expired", zone: "UTC", weekday: 3, max: 3, count: 2},
  18. {name: "inclusive last second spends one allowance", zone: "Asia/Taipei", weekday: 1, max: 1, inclusive: true},
  19. {name: "operator-disabled settings stay disabled", zone: "UTC", weekday: 5, manual: true},
  20. } {
  21. t.Run(tt.name, func(t *testing.T) {
  22. setupBulkDB(t)
  23. db := database.GetDB()
  24. zone := pinPanelZone(t, tt.zone)
  25. now := time.Now().In(zone)
  26. boundary := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, zone)
  27. for int(boundary.Weekday()+6)%7+1 != tt.weekday || !boundary.Before(now) {
  28. boundary = boundary.AddDate(0, 0, -1)
  29. }
  30. past := boundary.AddDate(0, 0, -21)
  31. if tt.zone == "America/New_York" {
  32. past = time.Date(2026, time.March, 1, 0, 0, 0, 0, zone)
  33. }
  34. if tt.inclusive {
  35. past = boundary.Add(-time.Second)
  36. }
  37. client := model.Client{
  38. Email: "weekly@x", ID: "11111111-1111-1111-1111-111111111111",
  39. ResetWeekday: tt.weekday, ResetMax: tt.max, ExpiryTime: past.UnixMilli(),
  40. }
  41. svc := &InboundService{}
  42. for _, port := range []int{30241, 30242} {
  43. ib := mkInbound(t, port, model.VLESS, clientsSettings(t, []model.Client{client}))
  44. if err := svc.clientService.SyncInbound(nil, ib.Id, []model.Client{client}); err != nil {
  45. t.Fatal(err)
  46. }
  47. }
  48. traffic := xray.ClientTraffic{
  49. Email: client.Email, ResetWeekday: tt.weekday, ResetMax: tt.max, ResetCount: tt.count,
  50. ExpiryTime: past.UnixMilli(), Up: 111, Down: 222, Enable: tt.manual,
  51. }
  52. if err := db.Create(&traffic).Error; err != nil {
  53. t.Fatal(err)
  54. }
  55. want, steps := past, 0
  56. if tt.inclusive {
  57. want = boundary
  58. }
  59. for !want.After(now) && (tt.max == 0 || tt.count+steps < tt.max) {
  60. want = want.AddDate(0, 0, 7)
  61. steps++
  62. }
  63. if _, _, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
  64. t.Fatal(err)
  65. }
  66. var got xray.ClientTraffic
  67. if err := db.Where("email = ?", client.Email).First(&got).Error; err != nil {
  68. t.Fatal(err)
  69. }
  70. if got.ExpiryTime != want.UnixMilli() || got.ResetCount != tt.count+steps || got.ResetWeekday != tt.weekday {
  71. t.Fatalf("expiry/count/weekday = %d/%d/%d, want %d/%d/%d", got.ExpiryTime, got.ResetCount, got.ResetWeekday, want.UnixMilli(), tt.count+steps, tt.weekday)
  72. }
  73. if want.After(now) {
  74. if !got.Enable || got.Up != 0 || got.Down != 0 {
  75. t.Fatalf("renewed enable/up/down = %v/%d/%d, want true/0/0", got.Enable, got.Up, got.Down)
  76. }
  77. } else if got.Enable || got.Up != 111 || got.Down != 222 {
  78. t.Fatalf("capped enable/up/down = %v/%d/%d, want false/111/222", got.Enable, got.Up, got.Down)
  79. }
  80. var record model.ClientRecord
  81. if err := db.Where("email = ?", client.Email).First(&record).Error; err != nil {
  82. t.Fatal(err)
  83. }
  84. if record.ResetWeekday != tt.weekday || record.ExpiryTime != got.ExpiryTime || record.Enable != (want.After(now) && !tt.manual) {
  85. t.Fatalf("client record lost weekly schedule or operator enable state: %+v", record)
  86. }
  87. if _, count, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil || count != 0 {
  88. t.Fatalf("repeat tick count/error = %d/%v, want 0/nil", count, err)
  89. }
  90. })
  91. }
  92. }