client_group_node_sync_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package service
  2. import (
  3. "path/filepath"
  4. "strings"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  10. )
  11. func TestSetRemoteTraffic_PreservesPanelLocalGroupAndComment(t *testing.T) {
  12. dbDir := t.TempDir()
  13. t.Setenv("XUI_DB_FOLDER", dbDir)
  14. dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
  15. db := database.GetDB()
  16. const nodeID = 1
  17. const email = "[email protected]"
  18. const uid = "ce8d33df-3a64-4f10-8f9b-91c3a8e0c003"
  19. const wantGroup = "vip"
  20. const wantComment = "renewed manually"
  21. id := nodeID
  22. central := &model.Inbound{
  23. UserId: 1,
  24. NodeID: &id,
  25. Tag: "n1-vless",
  26. Enable: true,
  27. Port: 20001,
  28. Protocol: model.VLESS,
  29. Settings: `{"clients":[{"email":"` + email + `","id":"` + uid + `","enable":true,"group":"` + wantGroup + `","comment":"` + wantComment + `"}]}`,
  30. }
  31. if err := db.Create(central).Error; err != nil {
  32. t.Fatalf("create node inbound: %v", err)
  33. }
  34. if err := db.Create(&model.ClientRecord{
  35. Email: email,
  36. UUID: uid,
  37. Enable: true,
  38. Group: wantGroup,
  39. Comment: wantComment,
  40. }).Error; err != nil {
  41. t.Fatalf("create client record: %v", err)
  42. }
  43. snap := &runtime.TrafficSnapshot{
  44. Inbounds: []*model.Inbound{
  45. {
  46. Tag: "n1-vless",
  47. Enable: true,
  48. Port: 20001,
  49. Protocol: model.VLESS,
  50. Settings: `{"clients":[{"email":"` + email + `","id":"` + uid + `","enable":true}]}`,
  51. },
  52. },
  53. }
  54. svc := InboundService{}
  55. if _, err := svc.setRemoteTrafficLocked(nodeID, snap, false, false); err != nil {
  56. t.Fatalf("setRemoteTrafficLocked: %v", err)
  57. }
  58. var row model.ClientRecord
  59. if err := db.Where("email = ?", email).First(&row).Error; err != nil {
  60. t.Fatalf("lookup client row after sync: %v", err)
  61. }
  62. if row.Group != wantGroup {
  63. t.Errorf("group was wiped by node snapshot sync: got %q, want %q", row.Group, wantGroup)
  64. }
  65. if row.Comment != wantComment {
  66. t.Errorf("comment was wiped by node snapshot sync: got %q, want %q", row.Comment, wantComment)
  67. }
  68. }
  69. func TestSyncInbound_KeepsGroupWhenIncomingEmpty(t *testing.T) {
  70. dbDir := t.TempDir()
  71. t.Setenv("XUI_DB_FOLDER", dbDir)
  72. dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
  73. db := database.GetDB()
  74. ib := &model.Inbound{Tag: "vless-grp", Enable: true, Port: 20002, Protocol: model.VLESS}
  75. if err := db.Create(ib).Error; err != nil {
  76. t.Fatalf("create inbound: %v", err)
  77. }
  78. svc := ClientService{}
  79. const email = "[email protected]"
  80. const uid = "ce8d33df-3a64-4f10-8f9b-91c3a8e0c004"
  81. const wantGroup = "vip"
  82. withGroup := model.Client{Email: email, ID: uid, Enable: true, Group: wantGroup}
  83. if err := svc.SyncInbound(nil, ib.Id, []model.Client{withGroup}); err != nil {
  84. t.Fatalf("SyncInbound (set group): %v", err)
  85. }
  86. noGroup := model.Client{Email: email, ID: uid, Enable: true, Group: ""}
  87. if err := svc.SyncInbound(nil, ib.Id, []model.Client{noGroup}); err != nil {
  88. t.Fatalf("SyncInbound (group-less rebuild): %v", err)
  89. }
  90. var row model.ClientRecord
  91. if err := db.Where("email = ?", email).First(&row).Error; err != nil {
  92. t.Fatalf("lookup client row: %v", err)
  93. }
  94. if row.Group != wantGroup {
  95. t.Errorf("group must survive a group-less settings rebuild (it is managed via the Groups page, not Xray settings): got %q, want %q", row.Group, wantGroup)
  96. }
  97. }
  98. // Removing the group in the client editor and saving must clear group_name and
  99. // drop the settings "group" key, even though SyncInbound preserves a group on a
  100. // group-less rebuild. The editor round-trips the field, so ClientService.Update
  101. // applies it explicitly.
  102. func TestClientUpdate_ClearsGroup(t *testing.T) {
  103. dbDir := t.TempDir()
  104. t.Setenv("XUI_DB_FOLDER", dbDir)
  105. dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
  106. db := database.GetDB()
  107. const email = "[email protected]"
  108. const uid = "ce8d33df-3a64-4f10-8f9b-91c3a8e0c005"
  109. const wantGroup = "vip"
  110. ib := &model.Inbound{
  111. UserId: 1,
  112. Tag: "vless-clear",
  113. Enable: true,
  114. Port: 20003,
  115. Protocol: model.VLESS,
  116. Settings: `{"clients":[{"email":"` + email + `","id":"` + uid + `","enable":true,"group":"` + wantGroup + `"}]}`,
  117. }
  118. if err := db.Create(ib).Error; err != nil {
  119. t.Fatalf("create inbound: %v", err)
  120. }
  121. svc := ClientService{}
  122. inboundSvc := &InboundService{}
  123. // Seed the client record + inbound link from the settings.
  124. seedClients, err := inboundSvc.GetClients(ib)
  125. if err != nil {
  126. t.Fatalf("GetClients: %v", err)
  127. }
  128. if err := svc.SyncInbound(nil, ib.Id, seedClients); err != nil {
  129. t.Fatalf("seed SyncInbound: %v", err)
  130. }
  131. var rec model.ClientRecord
  132. if err := db.Where("email = ?", email).First(&rec).Error; err != nil {
  133. t.Fatalf("lookup seeded record: %v", err)
  134. }
  135. if rec.Group != wantGroup {
  136. t.Fatalf("setup: group not seeded, got %q", rec.Group)
  137. }
  138. // Edit the client and remove the group.
  139. updated := *rec.ToClient()
  140. updated.Group = ""
  141. if _, err := svc.Update(inboundSvc, rec.Id, updated, 0); err != nil {
  142. t.Fatalf("Update (clear group): %v", err)
  143. }
  144. var after model.ClientRecord
  145. if err := db.Where("email = ?", email).First(&after).Error; err != nil {
  146. t.Fatalf("lookup record after update: %v", err)
  147. }
  148. if after.Group != "" {
  149. t.Errorf("group not cleared after editor removed it: got %q, want empty", after.Group)
  150. }
  151. var ibAfter model.Inbound
  152. if err := db.First(&ibAfter, ib.Id).Error; err != nil {
  153. t.Fatalf("lookup inbound after update: %v", err)
  154. }
  155. if strings.Contains(ibAfter.Settings, `"group"`) {
  156. t.Errorf("inbound settings still carry a group key after removal: %s", ibAfter.Settings)
  157. }
  158. }