1
0

mtproto_quota_reset_test.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package service
  2. import (
  3. "slices"
  4. "strings"
  5. "testing"
  6. "time"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. "github.com/mhsanaei/3x-ui/v3/internal/mtproto"
  10. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  11. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  12. )
  13. // startQuotaSidecar runs a local MTProto inbound for mtga and mtgb under the fake
  14. // mtg and returns its API log once the sidecar answers.
  15. func startQuotaSidecar(t *testing.T, port int, mtga model.Client) (*model.Inbound, string) {
  16. t.Helper()
  17. setupConflictDB(t)
  18. pidFile, logPath := installFakeMtgAPI(t)
  19. runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
  20. t.Cleanup(func() { runtime.SetManager(nil) })
  21. mtga.Email, mtga.Secret = "mtga", mtprotoTestSecretA
  22. clients := []model.Client{mtga, {Email: "mtgb", Secret: mtprotoTestSecretB, Enable: true}}
  23. ib := &model.Inbound{Tag: "mt-quota", Enable: true, Port: port, Protocol: model.MTProto, Settings: clientsSettings(t, clients)}
  24. if err := database.GetDB().Create(ib).Error; err != nil {
  25. t.Fatalf("create inbound: %v", err)
  26. }
  27. if err := (&ClientService{}).SyncInbound(nil, ib.Id, clients); err != nil {
  28. t.Fatalf("SyncInbound: %v", err)
  29. }
  30. for _, c := range clients {
  31. row := xray.ClientTraffic{InboundId: ib.Id, Email: c.Email, Enable: true, Up: 5, Total: c.TotalGB, ExpiryTime: c.ExpiryTime, Reset: c.Reset}
  32. if err := database.GetDB().Create(&row).Error; err != nil {
  33. t.Fatalf("seed traffic: %v", err)
  34. }
  35. }
  36. // A running sidecar needs a served client, so prime with the healthy set.
  37. inst, ok := mtproto.InstanceFromInbound(&model.Inbound{
  38. Id: ib.Id, Tag: ib.Tag, Port: port, Protocol: model.MTProto,
  39. Settings: clientsSettings(t, []model.Client{{Email: "mtgb", Secret: mtprotoTestSecretB, Enable: true}}),
  40. })
  41. if !ok {
  42. t.Fatal("seed inbound must produce an mtg instance")
  43. }
  44. if err := mtproto.GetManager().Ensure(inst); err != nil {
  45. t.Fatalf("start mtg: %v", err)
  46. }
  47. t.Cleanup(func() { mtproto.GetManager().Remove(ib.Id) })
  48. waitForSpawns(t, pidFile, 1)
  49. waitFakeMtgLog(t, logPath, "ready")
  50. return ib, logPath
  51. }
  52. func quotaResets(t *testing.T, logPath string) []string {
  53. t.Helper()
  54. var out []string
  55. for _, line := range fakeMtgLog(t, logPath) {
  56. if name, ok := strings.CutPrefix(line, "reset:"); ok {
  57. out = append(out, name)
  58. }
  59. }
  60. slices.Sort(out)
  61. return out
  62. }
  63. // Every path that zeroes a client's panel counters must zero the sidecar's own
  64. // quota counter too, or the sidecar keeps refusing the client.
  65. func TestPanelResetsZeroSidecarQuota(t *testing.T) {
  66. t.Run("bulk reset", func(t *testing.T) {
  67. _, logPath := startQuotaSidecar(t, 46201, model.Client{Enable: true})
  68. if _, err := (&ClientService{}).BulkResetTraffic(&InboundService{}, []string{"mtga"}); err != nil {
  69. t.Fatalf("BulkResetTraffic: %v", err)
  70. }
  71. if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga"}) {
  72. t.Fatalf("sidecar quota resets %v, want [mtga]", got)
  73. }
  74. })
  75. t.Run("inbound clients", func(t *testing.T) {
  76. ib, logPath := startQuotaSidecar(t, 46207, model.Client{Enable: true})
  77. if err := (&ClientService{}).ResetAllClientTraffics(&InboundService{}, ib.Id); err != nil {
  78. t.Fatalf("ResetAllClientTraffics: %v", err)
  79. }
  80. if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga", "mtgb"}) {
  81. t.Fatalf("sidecar quota resets %v, want [mtga mtgb]", got)
  82. }
  83. })
  84. t.Run("reset all", func(t *testing.T) {
  85. _, logPath := startQuotaSidecar(t, 46202, model.Client{Enable: true})
  86. if _, err := (&ClientService{}).ResetAllTraffics(); err != nil {
  87. t.Fatalf("ResetAllTraffics: %v", err)
  88. }
  89. if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga", "mtgb"}) {
  90. t.Fatalf("sidecar quota resets %v, want [mtga mtgb]", got)
  91. }
  92. })
  93. t.Run("auto renew", func(t *testing.T) {
  94. expired := time.Now().Add(-time.Hour).UnixMilli()
  95. _, logPath := startQuotaSidecar(t, 46203, model.Client{Enable: true, Reset: 30, ExpiryTime: expired})
  96. if _, _, err := (&InboundService{}).AddTraffic(nil, nil); err != nil {
  97. t.Fatalf("AddTraffic: %v", err)
  98. }
  99. if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga"}) {
  100. t.Fatalf("sidecar quota resets %v, want [mtga]", got)
  101. }
  102. })
  103. }
  104. // Resetting inbound counters leaves every client's usage in place, so the
  105. // sidecar's quota counters must stay too or clients get their quota again free.
  106. func TestInboundResetAllKeepsSidecarQuota(t *testing.T) {
  107. _, logPath := startQuotaSidecar(t, 46204, model.Client{Enable: true})
  108. if err := (&InboundService{}).ResetAllTraffics(); err != nil {
  109. t.Fatalf("ResetAllTraffics: %v", err)
  110. }
  111. if got := quotaResets(t, logPath); len(got) != 0 {
  112. t.Fatalf("inbound reset zeroed sidecar quotas %v, want none", got)
  113. }
  114. }
  115. // Resetting one inbound's clients zeroes only their sidecar quotas, not those
  116. // of MTProto clients whose usage the reset left in place.
  117. func TestInboundClientResetKeepsOtherSidecarQuotas(t *testing.T) {
  118. _, logPath := startQuotaSidecar(t, 46205, model.Client{Enable: true})
  119. other := mkInbound(t, 46206, model.VLESS, clientsSettings(t, []model.Client{{Email: "vless-only", ID: "11111111-1111-1111-1111-1111111111ab", Enable: true}}))
  120. if err := (&ClientService{}).SyncInbound(nil, other.Id, []model.Client{{Email: "vless-only", ID: "11111111-1111-1111-1111-1111111111ab", Enable: true}}); err != nil {
  121. t.Fatalf("SyncInbound: %v", err)
  122. }
  123. if err := (&ClientService{}).ResetAllClientTraffics(&InboundService{}, other.Id); err != nil {
  124. t.Fatalf("ResetAllClientTraffics: %v", err)
  125. }
  126. if got := quotaResets(t, logPath); len(got) != 0 {
  127. t.Fatalf("resetting another inbound zeroed sidecar quotas %v, want none", got)
  128. }
  129. }