1
0

client_portable_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package service
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. // TestExportImportPreservesDisabledEnable covers #6478: ExportAll keeps the
  8. // real enable flag; ImportClients must not force enable=true.
  9. func TestExportImportPreservesDisabledEnable(t *testing.T) {
  10. setupBulkDB(t)
  11. svc := &ClientService{}
  12. inboundSvc := &InboundService{}
  13. ib := mkInbound(t, 26001, model.VLESS, `{"clients":[]}`)
  14. const email = "portable@disabled"
  15. const subID = "sub-portable-disabled"
  16. if _, err := svc.Create(inboundSvc, &ClientCreatePayload{
  17. Client: model.Client{
  18. Email: email, SubID: subID, Enable: true,
  19. ID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  20. },
  21. InboundIds: []int{ib.Id},
  22. }); err != nil {
  23. t.Fatalf("Create: %v", err)
  24. }
  25. rec := lookupClientRecord(t, email)
  26. updated := rec.ToClient()
  27. updated.Enable = false
  28. if _, err := svc.Update(inboundSvc, rec.Id, *updated, 0); err != nil {
  29. t.Fatalf("Update disable: %v", err)
  30. }
  31. assertEnableEverywhere(t, svc, inboundSvc, ib.Id, email, false)
  32. exported, err := svc.ExportAll()
  33. if err != nil {
  34. t.Fatalf("ExportAll: %v", err)
  35. }
  36. if len(exported) != 1 {
  37. t.Fatalf("ExportAll len=%d, want 1", len(exported))
  38. }
  39. if exported[0].Client.Enable {
  40. t.Fatal("ExportAll should carry enable=false for a disabled client")
  41. }
  42. raw, err := json.Marshal(exported)
  43. if err != nil {
  44. t.Fatalf("marshal export: %v", err)
  45. }
  46. var roundTrip []ClientCreatePayload
  47. if err := json.Unmarshal(raw, &roundTrip); err != nil {
  48. t.Fatalf("unmarshal export: %v", err)
  49. }
  50. if roundTrip[0].Client.Enable {
  51. t.Fatal("JSON round-trip lost enable=false")
  52. }
  53. if _, err := svc.Delete(inboundSvc, rec.Id, false); err != nil {
  54. t.Fatalf("Delete: %v", err)
  55. }
  56. res, _, err := svc.ImportClients(inboundSvc, roundTrip)
  57. if err != nil {
  58. t.Fatalf("ImportClients: %v", err)
  59. }
  60. if res.Created != 1 || len(res.Skipped) != 0 {
  61. t.Fatalf("ImportClients result=%+v", res)
  62. }
  63. assertEnableEverywhere(t, svc, inboundSvc, ib.Id, email, false)
  64. }
  65. func TestImportClientsPreservesOrphanDisabledEnable(t *testing.T) {
  66. setupBulkDB(t)
  67. svc := &ClientService{}
  68. items := []ClientCreatePayload{{
  69. Client: model.Client{
  70. Email: "orphan@disabled", SubID: "sub-orphan-disabled", Enable: false,
  71. ID: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  72. },
  73. InboundIds: nil,
  74. }}
  75. res, _, err := svc.ImportClients(&InboundService{}, items)
  76. if err != nil {
  77. t.Fatalf("ImportClients orphan: %v", err)
  78. }
  79. if res.Created != 1 {
  80. t.Fatalf("created=%d, want 1; skipped=%v", res.Created, res.Skipped)
  81. }
  82. if got := recordEnableOf(t, svc, "orphan@disabled"); got {
  83. t.Fatal("orphan import forced enable=true; want false")
  84. }
  85. }
  86. func TestBulkCreatePreservesExplicitDisable(t *testing.T) {
  87. setupBulkDB(t)
  88. svc := &ClientService{}
  89. inboundSvc := &InboundService{}
  90. ib := mkInbound(t, 26002, model.VLESS, `{"clients":[]}`)
  91. const email = "bulk@disabled"
  92. res, _, err := svc.BulkCreate(inboundSvc, []ClientCreatePayload{{
  93. Client: model.Client{
  94. Email: email, SubID: "sub-bulk-disabled", Enable: false,
  95. ID: "cccccccc-cccc-cccc-cccc-cccccccccccc",
  96. },
  97. InboundIds: []int{ib.Id},
  98. }})
  99. if err != nil {
  100. t.Fatalf("BulkCreate: %v", err)
  101. }
  102. if res.Created != 1 {
  103. t.Fatalf("BulkCreate result=%+v", res)
  104. }
  105. assertEnableEverywhere(t, svc, inboundSvc, ib.Id, email, false)
  106. }
  107. func TestClientCreatePayload_OmitEnableDefaultsTrue(t *testing.T) {
  108. raw := []byte(`{"client":{"email":"omit@x","id":"dddddddd-dddd-dddd-dddd-dddddddddddd","subId":"sub-omit"},"inboundIds":[1]}`)
  109. var p ClientCreatePayload
  110. if err := json.Unmarshal(raw, &p); err != nil {
  111. t.Fatalf("unmarshal: %v", err)
  112. }
  113. if !p.Client.Enable {
  114. t.Fatal("omitted enable must default to true")
  115. }
  116. rawFalse := []byte(`{"client":{"email":"off@x","id":"eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee","subId":"sub-off","enable":false},"inboundIds":[1]}`)
  117. var pFalse ClientCreatePayload
  118. if err := json.Unmarshal(rawFalse, &pFalse); err != nil {
  119. t.Fatalf("unmarshal false: %v", err)
  120. }
  121. if pFalse.Client.Enable {
  122. t.Fatal("explicit enable:false must stay false")
  123. }
  124. }
  125. func TestBulkCreate_DisabledOnNodeSkipsAddClient(t *testing.T) {
  126. setupBulkDB(t)
  127. nodeID, fake := setupNodeRuntime(t)
  128. ib := nodeInbound(t, nodeID, 26003, nil)
  129. svc := &ClientService{}
  130. inboundSvc := &InboundService{}
  131. const email = "node@disabled"
  132. res, _, err := svc.BulkCreate(inboundSvc, []ClientCreatePayload{{
  133. Client: model.Client{
  134. Email: email, SubID: "sub-node-disabled", Enable: false,
  135. ID: "ffffffff-ffff-ffff-ffff-ffffffffffff",
  136. },
  137. InboundIds: []int{ib.Id},
  138. }})
  139. if err != nil {
  140. t.Fatalf("BulkCreate: %v", err)
  141. }
  142. if res.Created != 1 {
  143. t.Fatalf("BulkCreate result=%+v", res)
  144. }
  145. if got := fake.addClient.Load(); got != 0 {
  146. t.Fatalf("AddClient RPCs = %d, want 0 for enable=false", got)
  147. }
  148. assertEnableEverywhere(t, svc, inboundSvc, ib.Id, email, false)
  149. if _, _, dirty, _, err := (&NodeService{}).NodeSyncState(nodeID); err != nil {
  150. t.Fatalf("NodeSyncState: %v", err)
  151. } else if !dirty {
  152. t.Fatal("disabled node create must leave node dirty for reconcile")
  153. }
  154. }