1
0

mtproto_quota_reset_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  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("reset all", func(t *testing.T) {
  76. _, logPath := startQuotaSidecar(t, 46202, model.Client{Enable: true})
  77. if _, err := (&ClientService{}).ResetAllTraffics(); err != nil {
  78. t.Fatalf("ResetAllTraffics: %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("auto renew", func(t *testing.T) {
  85. expired := time.Now().Add(-time.Hour).UnixMilli()
  86. _, logPath := startQuotaSidecar(t, 46203, model.Client{Enable: true, Reset: 30, ExpiryTime: expired})
  87. if _, _, err := (&InboundService{}).AddTraffic(nil, nil); err != nil {
  88. t.Fatalf("AddTraffic: %v", err)
  89. }
  90. if got := quotaResets(t, logPath); !slices.Equal(got, []string{"mtga"}) {
  91. t.Fatalf("sidecar quota resets %v, want [mtga]", got)
  92. }
  93. })
  94. }