node_inbound_replace_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  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/web/runtime"
  6. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  7. )
  8. // A snapshot tag that matches neither the central tag nor an alias replaces the
  9. // inbound in one tick; the client's accumulated usage must survive the swap.
  10. func TestSetRemoteTraffic_InboundReplacedKeepsClientHistory(t *testing.T) {
  11. db := initTrafficTestDB(t)
  12. const nodeID = 2
  13. if err := db.Create(&model.Node{Id: nodeID, Name: "node", Address: "10.0.0.2", Port: 2053, ApiToken: "t", Guid: "node-guid"}).Error; err != nil {
  14. t.Fatalf("create node: %v", err)
  15. }
  16. const email = "baba"
  17. settings := `{"clients":[{"email":"baba","enable":true}]}`
  18. createNodeInboundWithClient(t, db, nodeID, "n2-in-2053-tcp", 2053, email)
  19. svc := &InboundService{}
  20. sync := func(up, down int64) {
  21. t.Helper()
  22. snap := &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{{
  23. Tag: "in-2053-tcp", OriginNodeGuid: "node-guid", Enable: true, Port: 2053, Protocol: model.VLESS,
  24. Settings: settings, ClientStats: []xray.ClientTraffic{{Email: email, Up: up, Down: down, Enable: true}},
  25. }}}
  26. if _, err := svc.setRemoteTrafficLocked(nodeID, snap, false, false); err != nil {
  27. t.Fatalf("setRemoteTrafficLocked: %v", err)
  28. }
  29. }
  30. sync(100, 200)
  31. sync(600, 1200)
  32. assertUpDown(t, readTraffic(t, db, email), 500, 1000, "before the replace")
  33. // Desync the central row so the next snapshot neither tag-matches nor aliases it.
  34. if err := db.Model(&model.Inbound{}).Where("node_id = ?", nodeID).
  35. Updates(map[string]any{"tag": "n2-legacy", "origin_node_guid": "stale-guid"}).Error; err != nil {
  36. t.Fatalf("desync central inbound: %v", err)
  37. }
  38. sync(650, 1300)
  39. assertUpDown(t, readTraffic(t, db, email), 550, 1100, "replace tick")
  40. sync(700, 1400)
  41. assertUpDown(t, readTraffic(t, db, email), 600, 1200, "tick after the replace")
  42. var ib model.Inbound
  43. if err := db.Where("node_id = ?", nodeID).First(&ib).Error; err != nil {
  44. t.Fatalf("read node inbound: %v", err)
  45. }
  46. if ib.Tag != "in-2053-tcp" {
  47. t.Fatalf("fixture did not replace the inbound: surviving tag %q", ib.Tag)
  48. }
  49. if ct := readTraffic(t, db, email); ct.InboundId != ib.Id {
  50. t.Errorf("client_traffics.inbound_id = %d, want the surviving inbound %d", ct.InboundId, ib.Id)
  51. }
  52. }
  53. // A node inbound created on the master has no origin until its first tag match;
  54. // that empty origin is still this node, so a renamed tag aliases instead of replacing.
  55. func TestSetRemoteTraffic_AliasesInboundWithEmptyOrigin(t *testing.T) {
  56. db := initTrafficTestDB(t)
  57. const nodeID = 2
  58. if err := db.Create(&model.Node{Id: nodeID, Name: "node", Address: "10.0.0.2", Port: 2053, ApiToken: "t", Guid: "node-guid"}).Error; err != nil {
  59. t.Fatalf("create node: %v", err)
  60. }
  61. createNodeInbound(t, db, nodeID, "n2-in-2053-tcp", 2053)
  62. var before model.Inbound
  63. if err := db.Where("node_id = ?", nodeID).First(&before).Error; err != nil {
  64. t.Fatalf("read node inbound: %v", err)
  65. }
  66. snap := &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{{
  67. Tag: "in-2053-tcp-2", OriginNodeGuid: "node-guid", Enable: true, Port: 2053, Protocol: model.VLESS,
  68. Settings: `{"clients":[]}`,
  69. }}}
  70. if _, err := (&InboundService{}).setRemoteTrafficLocked(nodeID, snap, false, false); err != nil {
  71. t.Fatalf("setRemoteTrafficLocked: %v", err)
  72. }
  73. var rows []model.Inbound
  74. if err := db.Where("node_id = ?", nodeID).Find(&rows).Error; err != nil {
  75. t.Fatalf("list node inbounds: %v", err)
  76. }
  77. if len(rows) != 1 || rows[0].Id != before.Id {
  78. t.Fatalf("node inbounds = %#v, want only the original id %d", rows, before.Id)
  79. }
  80. }