node_client_renew_sync_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package service
  2. import (
  3. "testing"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  5. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  6. )
  7. const (
  8. renewFirstExpiry = int64(1893456000000)
  9. renewSecondExpiry = renewFirstExpiry + int64(2592000000)
  10. renewPeriodDays = 30
  11. )
  12. // TestNodeRenew_ResetsMasterTraffic reproduces #5843: a node-side auto-renew
  13. // extends the deadline and resets the node's counters, and the master must
  14. // start a fresh quota window instead of keeping the old period's usage.
  15. func TestNodeRenew_ResetsMasterTraffic(t *testing.T) {
  16. db := initTrafficTestDB(t)
  17. createNodeInbound(t, db, 1, "n1-in", 41001)
  18. svc := &InboundService{}
  19. const email = "renew-reset"
  20. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 0, Down: 0, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  21. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 950, Down: 150, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  22. assertUpDown(t, readTraffic(t, db, email), 950, 150, "before renewal")
  23. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 5, Down: 2, ExpiryTime: renewSecondExpiry, Reset: renewPeriodDays, Enable: true})
  24. ct := readTraffic(t, db, email)
  25. assertUpDown(t, ct, 5, 2, "after renewal")
  26. if ct.ExpiryTime != renewSecondExpiry {
  27. t.Errorf("renewal expiry = %d, want %d", ct.ExpiryTime, renewSecondExpiry)
  28. }
  29. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 15, Down: 12, ExpiryTime: renewSecondExpiry, Reset: renewPeriodDays, Enable: true})
  30. assertUpDown(t, readTraffic(t, db, email), 15, 12, "accumulating in the new period")
  31. }
  32. // TestNodeRenew_ReenablesDepletedClient: the node re-enables a client it
  33. // renews; the master's depleted-disable must lift with the fresh window.
  34. func TestNodeRenew_ReenablesDepletedClient(t *testing.T) {
  35. db := initTrafficTestDB(t)
  36. createNodeInbound(t, db, 1, "n1-in", 41001)
  37. svc := &InboundService{}
  38. const email = "renew-enable"
  39. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 900, Down: 100, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  40. if err := db.Model(&xray.ClientTraffic{}).Where("email = ?", email).Update("enable", false).Error; err != nil {
  41. t.Fatalf("force-disable master row: %v", err)
  42. }
  43. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 0, Down: 0, ExpiryTime: renewSecondExpiry, Reset: renewPeriodDays, Enable: true})
  44. if ct := readTraffic(t, db, email); !ct.Enable {
  45. t.Error("renewal must re-enable the master row when the node reports the client enabled")
  46. }
  47. }
  48. // TestNodeRenew_ClearsGlobalTraffic mirrors autoRenewClients: stale cross-panel
  49. // pushes for the renewed email must not re-deplete the fresh window.
  50. func TestNodeRenew_ClearsGlobalTraffic(t *testing.T) {
  51. db := initTrafficTestDB(t)
  52. createNodeInbound(t, db, 1, "n1-in", 41001)
  53. svc := &InboundService{}
  54. const email = "renew-global"
  55. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 900, Down: 100, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  56. if err := db.Create(&model.ClientGlobalTraffic{MasterGuid: "m1", Email: email, Up: 800, Down: 90}).Error; err != nil {
  57. t.Fatalf("seed global traffic: %v", err)
  58. }
  59. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 3, Down: 1, ExpiryTime: renewSecondExpiry, Reset: renewPeriodDays, Enable: true})
  60. var cnt int64
  61. if err := db.Model(&model.ClientGlobalTraffic{}).Where("email = ?", email).Count(&cnt).Error; err != nil {
  62. t.Fatalf("count global traffic: %v", err)
  63. }
  64. if cnt != 0 {
  65. t.Errorf("renewal must clear stale global-traffic rows, found %d", cnt)
  66. }
  67. }
  68. // TestNodeCounterDip_SameExpiry_KeepsTraffic: a plain counter dip (#5456) with
  69. // no deadline movement stays on the clamp path even for a renewable client.
  70. func TestNodeCounterDip_SameExpiry_KeepsTraffic(t *testing.T) {
  71. db := initTrafficTestDB(t)
  72. createNodeInbound(t, db, 1, "n1-in", 41001)
  73. svc := &InboundService{}
  74. const email = "dip-only"
  75. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 0, Down: 0, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  76. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 950, Down: 150, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  77. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 50, Down: 10, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  78. assertUpDown(t, readTraffic(t, db, email), 950, 150, "dip without renewal")
  79. }
  80. // TestNodeExpiryAdvance_RisingCounters_Accumulates: a manual deadline extension
  81. // while traffic keeps flowing is not a renewal and must keep accumulating.
  82. func TestNodeExpiryAdvance_RisingCounters_Accumulates(t *testing.T) {
  83. db := initTrafficTestDB(t)
  84. createNodeInbound(t, db, 1, "n1-in", 41001)
  85. svc := &InboundService{}
  86. const email = "extend-only"
  87. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 0, Down: 0, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  88. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 100, Down: 50, ExpiryTime: renewFirstExpiry, Reset: renewPeriodDays, Enable: true})
  89. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 160, Down: 90, ExpiryTime: renewSecondExpiry, Reset: renewPeriodDays, Enable: true})
  90. assertUpDown(t, readTraffic(t, db, email), 160, 90, "extension while accumulating")
  91. }