1
0

node_client_traffic_sum_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package service
  2. import (
  3. "path/filepath"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/database"
  6. "github.com/mhsanaei/3x-ui/v3/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/web/runtime"
  8. "github.com/mhsanaei/3x-ui/v3/xray"
  9. "gorm.io/gorm"
  10. )
  11. func initTrafficTestDB(t *testing.T) *gorm.DB {
  12. t.Helper()
  13. dbDir := t.TempDir()
  14. t.Setenv("XUI_DB_FOLDER", dbDir)
  15. if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
  16. t.Fatalf("InitDB: %v", err)
  17. }
  18. t.Cleanup(func() { _ = database.CloseDB() })
  19. return database.GetDB()
  20. }
  21. func createNodeInbound(t *testing.T, db *gorm.DB, nodeID int, tag string, port int) {
  22. t.Helper()
  23. nid := nodeID
  24. ib := &model.Inbound{UserId: 1, Tag: tag, Enable: true, Port: port, Protocol: model.VLESS, NodeID: &nid}
  25. if err := db.Create(ib).Error; err != nil {
  26. t.Fatalf("create node inbound %q: %v", tag, err)
  27. }
  28. }
  29. func syncNode(t *testing.T, svc *InboundService, nodeID int, tag string, stats ...xray.ClientTraffic) {
  30. t.Helper()
  31. snap := &runtime.TrafficSnapshot{
  32. Inbounds: []*model.Inbound{{Tag: tag, ClientStats: stats}},
  33. }
  34. if _, err := svc.setRemoteTrafficLocked(nodeID, snap); err != nil {
  35. t.Fatalf("setRemoteTrafficLocked node %d: %v", nodeID, err)
  36. }
  37. }
  38. func readTraffic(t *testing.T, db *gorm.DB, email string) xray.ClientTraffic {
  39. t.Helper()
  40. var ct xray.ClientTraffic
  41. if err := db.Model(xray.ClientTraffic{}).Where("email = ?", email).First(&ct).Error; err != nil {
  42. t.Fatalf("read client_traffics %q: %v", email, err)
  43. }
  44. return ct
  45. }
  46. func assertUpDown(t *testing.T, ct xray.ClientTraffic, wantUp, wantDown int64, when string) {
  47. t.Helper()
  48. if ct.Up != wantUp || ct.Down != wantDown {
  49. t.Errorf("%s: up=%d down=%d, want %d/%d", when, ct.Up, ct.Down, wantUp, wantDown)
  50. }
  51. }
  52. func TestTwoNodesShareEmail_SumsCorrectly(t *testing.T) {
  53. db := initTrafficTestDB(t)
  54. createNodeInbound(t, db, 1, "n1-in", 41001)
  55. createNodeInbound(t, db, 2, "n2-in", 41002)
  56. svc := &InboundService{}
  57. const email = "shared"
  58. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 100, Down: 100, Enable: true})
  59. syncNode(t, svc, 2, "n2-in", xray.ClientTraffic{Email: email, Up: 200, Down: 200, Enable: true})
  60. assertUpDown(t, readTraffic(t, db, email), 100, 100, "after baselines")
  61. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 150, Down: 150, Enable: true})
  62. syncNode(t, svc, 2, "n2-in", xray.ClientTraffic{Email: email, Up: 260, Down: 260, Enable: true})
  63. assertUpDown(t, readTraffic(t, db, email), 210, 210, "after both nodes grow")
  64. }
  65. func TestSingleNode_MirrorsCorrectly(t *testing.T) {
  66. db := initTrafficTestDB(t)
  67. createNodeInbound(t, db, 1, "n1-in", 41001)
  68. svc := &InboundService{}
  69. const email = "solo"
  70. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 500, Down: 600, Enable: true})
  71. assertUpDown(t, readTraffic(t, db, email), 500, 600, "first sync")
  72. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 700, Down: 800, Enable: true})
  73. assertUpDown(t, readTraffic(t, db, email), 700, 800, "second sync mirrors cumulative")
  74. }
  75. func TestUpgrade_PreExistingRow_NoDoubleCount(t *testing.T) {
  76. db := initTrafficTestDB(t)
  77. createNodeInbound(t, db, 1, "n1-in", 41001)
  78. svc := &InboundService{}
  79. const email = "legacy"
  80. var ib model.Inbound
  81. if err := db.Where("tag = ?", "n1-in").First(&ib).Error; err != nil {
  82. t.Fatalf("load inbound: %v", err)
  83. }
  84. if err := db.Create(&xray.ClientTraffic{InboundId: ib.Id, Email: email, Up: 1000, Down: 2000, Enable: true}).Error; err != nil {
  85. t.Fatalf("seed pre-existing row: %v", err)
  86. }
  87. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 1000, Down: 2000, Enable: true})
  88. assertUpDown(t, readTraffic(t, db, email), 1000, 2000, "first snapshot must not double-count")
  89. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 1100, Down: 2100, Enable: true})
  90. assertUpDown(t, readTraffic(t, db, email), 1100, 2100, "growth after upgrade accrues")
  91. }
  92. func TestNodeCounterReset_Clamped(t *testing.T) {
  93. db := initTrafficTestDB(t)
  94. createNodeInbound(t, db, 1, "n1-in", 41001)
  95. svc := &InboundService{}
  96. const email = "restart"
  97. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 900, Down: 900, Enable: true})
  98. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 950, Down: 950, Enable: true})
  99. assertUpDown(t, readTraffic(t, db, email), 950, 950, "before node reset")
  100. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 50, Down: 50, Enable: true})
  101. ct := readTraffic(t, db, email)
  102. if ct.Up < 0 || ct.Down < 0 {
  103. t.Fatalf("row went negative after node reset: up=%d down=%d", ct.Up, ct.Down)
  104. }
  105. assertUpDown(t, ct, 1000, 1000, "after node counter reset (clamped)")
  106. }
  107. func TestCentralReset_NoReAdd(t *testing.T) {
  108. db := initTrafficTestDB(t)
  109. createNodeInbound(t, db, 1, "n1-in", 41001)
  110. createNodeInbound(t, db, 2, "n2-in", 41002)
  111. svc := &InboundService{}
  112. const email = "reset"
  113. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 100, Down: 100, Enable: true})
  114. syncNode(t, svc, 2, "n2-in", xray.ClientTraffic{Email: email, Up: 100, Down: 100, Enable: true})
  115. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 200, Down: 200, Enable: true})
  116. syncNode(t, svc, 2, "n2-in", xray.ClientTraffic{Email: email, Up: 200, Down: 200, Enable: true})
  117. if err := db.Model(xray.ClientTraffic{}).Where("email = ?", email).
  118. Updates(map[string]any{"up": 0, "down": 0}).Error; err != nil {
  119. t.Fatalf("simulate central reset: %v", err)
  120. }
  121. syncNode(t, svc, 1, "n1-in", xray.ClientTraffic{Email: email, Up: 210, Down: 210, Enable: true})
  122. syncNode(t, svc, 2, "n2-in", xray.ClientTraffic{Email: email, Up: 205, Down: 205, Enable: true})
  123. assertUpDown(t, readTraffic(t, db, email), 15, 15, "after central reset only increments accrue")
  124. }
  125. func TestDelClientStat_CleansNodeBaselines(t *testing.T) {
  126. db := initTrafficTestDB(t)
  127. svc := &InboundService{}
  128. const email = "gone"
  129. if err := db.Create(&xray.ClientTraffic{InboundId: 1, Email: email, Enable: true}).Error; err != nil {
  130. t.Fatalf("seed client_traffics: %v", err)
  131. }
  132. if err := db.Create(&model.NodeClientTraffic{NodeId: 1, Email: email, Up: 10, Down: 10}).Error; err != nil {
  133. t.Fatalf("seed node baseline 1: %v", err)
  134. }
  135. if err := db.Create(&model.NodeClientTraffic{NodeId: 2, Email: email, Up: 20, Down: 20}).Error; err != nil {
  136. t.Fatalf("seed node baseline 2: %v", err)
  137. }
  138. if err := svc.DelClientStat(db, email); err != nil {
  139. t.Fatalf("DelClientStat: %v", err)
  140. }
  141. var cnt int64
  142. if err := db.Model(&model.NodeClientTraffic{}).Where("email = ?", email).Count(&cnt).Error; err != nil {
  143. t.Fatalf("count baselines: %v", err)
  144. }
  145. if cnt != 0 {
  146. t.Errorf("expected node baselines cleaned, found %d", cnt)
  147. }
  148. }
  149. func TestNodeDelete_CleansNodeBaselines(t *testing.T) {
  150. db := initTrafficTestDB(t)
  151. nodeSvc := NodeService{}
  152. if err := db.Create(&model.NodeClientTraffic{NodeId: 7, Email: "a", Up: 1, Down: 1}).Error; err != nil {
  153. t.Fatalf("seed node 7 a: %v", err)
  154. }
  155. if err := db.Create(&model.NodeClientTraffic{NodeId: 7, Email: "b", Up: 2, Down: 2}).Error; err != nil {
  156. t.Fatalf("seed node 7 b: %v", err)
  157. }
  158. if err := db.Create(&model.NodeClientTraffic{NodeId: 8, Email: "c", Up: 3, Down: 3}).Error; err != nil {
  159. t.Fatalf("seed node 8 c: %v", err)
  160. }
  161. if err := nodeSvc.Delete(7); err != nil {
  162. t.Fatalf("NodeService.Delete(7): %v", err)
  163. }
  164. var sevenCnt, eightCnt int64
  165. db.Model(&model.NodeClientTraffic{}).Where("node_id = ?", 7).Count(&sevenCnt)
  166. db.Model(&model.NodeClientTraffic{}).Where("node_id = ?", 8).Count(&eightCnt)
  167. if sevenCnt != 0 {
  168. t.Errorf("node 7 baselines not cleaned: %d remain", sevenCnt)
  169. }
  170. if eightCnt != 1 {
  171. t.Errorf("node 8 baseline should survive, found %d", eightCnt)
  172. }
  173. }