client_reverse_readd_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package service
  2. import (
  3. "context"
  4. "sync"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  9. )
  10. // reverseUserProbe records the account maps the panel pushes to the core: the
  11. // last place a stored reverse tag can be dropped before it reaches a listener.
  12. type reverseUserProbe struct {
  13. fakeNodeRuntime
  14. mu sync.Mutex
  15. users []map[string]any
  16. }
  17. func (p *reverseUserProbe) AddUser(_ context.Context, _ *model.Inbound, user map[string]any) error {
  18. p.mu.Lock()
  19. defer p.mu.Unlock()
  20. p.users = append(p.users, user)
  21. return nil
  22. }
  23. func (p *reverseUserProbe) recorded() []map[string]any {
  24. p.mu.Lock()
  25. defer p.mu.Unlock()
  26. return append([]map[string]any(nil), p.users...)
  27. }
  28. const reverseProbeID = "5f2eb9d6-3a2f-4a55-9812-6ea1e2f7a333"
  29. func reverseProbeClient(email string, enable bool) model.Client {
  30. return model.Client{Email: email, ID: reverseProbeID, Enable: enable, Reverse: &model.ClientReverse{Tag: "portal"}}
  31. }
  32. // seedReverseProbeInbound seeds one local vless inbound holding a single reverse
  33. // client, and the recording runtime every local apply of it lands on.
  34. func seedReverseProbeInbound(t *testing.T, tag string, port int, enable bool) (*model.Inbound, string, *reverseUserProbe) {
  35. t.Helper()
  36. setupConflictDB(t)
  37. mgr := useTestRuntimeManager(t)
  38. probe := &reverseUserProbe{}
  39. mgr.SetLocalRuntimeOverride(probe)
  40. email := tag + "@example.test"
  41. client := reverseProbeClient(email, enable)
  42. seedInboundConflict(t, tag, "0.0.0.0", port, model.VLESS, `{"network":"tcp"}`, clientsSettings(t, []model.Client{client}))
  43. inbound := loadInboundByTag(t, tag)
  44. if err := (&ClientService{}).SyncInbound(nil, inbound.Id, []model.Client{client}); err != nil {
  45. t.Fatalf("SyncInbound: %v", err)
  46. }
  47. return inbound, email, probe
  48. }
  49. // assertReverseReAdd fails unless the core was handed the client's own tag: the
  50. // handler is gone the moment RemoveUser runs, and only the tag rebuilds it.
  51. func assertReverseReAdd(t *testing.T, probe *reverseUserProbe, email string) {
  52. t.Helper()
  53. users := probe.recorded()
  54. if len(users) == 0 {
  55. t.Fatalf("%s was never re-added to the core, so its reverse tag was never checked", email)
  56. }
  57. found := false
  58. for _, user := range users {
  59. if got, _ := user["email"].(string); got != email {
  60. continue
  61. }
  62. found = true
  63. tag, _ := user["reverse"].(*model.ClientReverse)
  64. if tag == nil || tag.Tag != "portal" {
  65. t.Fatalf("the re-add of %s carries reverse %#v, want its stored tag portal", email, user["reverse"])
  66. }
  67. }
  68. if !found {
  69. t.Fatalf("no re-add of %s reached the core: %v", email, users)
  70. }
  71. }
  72. // The panel's most ordinary action on a reverse client: editing it removes the
  73. // account and adds it back, and the core rebuilds nothing without the tag.
  74. func TestClientEditKeepsTheReverseTag(t *testing.T) {
  75. _, email, probe := seedReverseProbeInbound(t, "rev-edit", 50071, true)
  76. rec := lookupClientRecord(t, email)
  77. edited := reverseProbeClient(email, true)
  78. edited.Comment = "edited after the tunnel was up"
  79. if _, err := (&ClientService{}).Update(&InboundService{}, rec.Id, edited, 0); err != nil {
  80. t.Fatalf("Update: %v", err)
  81. }
  82. assertReverseReAdd(t, probe, email)
  83. }
  84. func TestBulkReEnableKeepsTheReverseTag(t *testing.T) {
  85. _, email, probe := seedReverseProbeInbound(t, "rev-bulk", 50072, false)
  86. if _, _, err := (&ClientService{}).BulkSetEnable(&InboundService{}, []string{email}, true); err != nil {
  87. t.Fatalf("BulkSetEnable: %v", err)
  88. }
  89. assertReverseReAdd(t, probe, email)
  90. }
  91. // The route an operator hits most often: a client that exhausted its quota is
  92. // removed, then re-added by the reset that renews it.
  93. func TestTrafficResetKeepsTheReverseTag(t *testing.T) {
  94. inbound, email, probe := seedReverseProbeInbound(t, "rev-quota", 50073, true)
  95. depleteClientTraffic(t, inbound.Id, email)
  96. if _, err := (&InboundService{}).ResetClientTraffic(inbound.Id, email); err != nil {
  97. t.Fatalf("ResetClientTraffic: %v", err)
  98. }
  99. assertReverseReAdd(t, probe, email)
  100. }
  101. func TestAddingClientsKeepsTheReverseTag(t *testing.T) {
  102. inbound, _, probe := seedReverseProbeInbound(t, "rev-add", 50074, true)
  103. const added = "[email protected]"
  104. second := reverseProbeClient(added, true)
  105. second.ID = "7c3fad07-4b1c-4d66-9f83-7db2f3c8b444"
  106. if _, err := (&ClientService{}).AddInboundClient(&InboundService{}, &model.Inbound{
  107. Id: inbound.Id,
  108. Protocol: model.VLESS,
  109. Settings: clientsSettings(t, []model.Client{second}),
  110. }); err != nil {
  111. t.Fatalf("AddInboundClient: %v", err)
  112. }
  113. assertReverseReAdd(t, probe, added)
  114. }
  115. // depleteClientTraffic leaves the client enabled in settings but out of quota,
  116. // the state a traffic reset re-adds it from.
  117. func depleteClientTraffic(t *testing.T, inboundId int, email string) {
  118. t.Helper()
  119. db := database.GetDB()
  120. res := db.Model(&xray.ClientTraffic{}).Where("email = ?", email).
  121. Updates(map[string]any{"enable": false, "up": 1, "down": 1})
  122. if res.Error != nil {
  123. t.Fatalf("deplete traffic: %v", res.Error)
  124. }
  125. if res.RowsAffected == 0 {
  126. if err := db.Create(&xray.ClientTraffic{InboundId: inboundId, Email: email, Enable: false, Up: 1, Down: 1}).Error; err != nil {
  127. t.Fatalf("create depleted traffic: %v", err)
  128. }
  129. }
  130. }