1
0

client_renewal_write_validation_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  8. )
  9. func TestClientRenewalWriteValidation(t *testing.T) {
  10. for _, operation := range []string{"add inbound", "update inbound", "add inbound client", "update inbound client", "sync inbound", "client delta", "add stat", "update stat", "import stat"} {
  11. for _, schedule := range []struct {
  12. name string
  13. reset, day, weekday int
  14. errorMessage string
  15. }{
  16. {"weekly and interval", 7, 0, 3, "client weekly renewal cannot be combined with reset or resetDay"},
  17. {"weekly and monthly", 0, 1, 3, "client weekly renewal cannot be combined with reset or resetDay"},
  18. {"invalid weekday", 0, 0, 8, "client resetWeekday must be between 0 and 7, got: 8"},
  19. } {
  20. t.Run(operation+"/"+schedule.name, func(t *testing.T) {
  21. setupConflictDB(t)
  22. nodeID, fake := setupNodeRuntime(t)
  23. svc, inboundSvc := &ClientService{}, &InboundService{}
  24. client := model.Client{Email: "renewal-boundary", ID: "11111111-1111-1111-1111-111111111111", SubID: "renewal-boundary-sub", Enable: true, ResetWeekday: 3, ResetMax: 4, ExpiryTime: time.Now().Add(time.Hour).UnixMilli()}
  25. inbound := nodeInbound(t, nodeID, 41759, []model.Client{client})
  26. if err := inboundSvc.AddClientStat(database.GetDB(), inbound.Id, &client); err != nil {
  27. t.Fatal(err)
  28. }
  29. record, err := svc.GetRecordByEmail(nil, client.Email)
  30. if err != nil {
  31. t.Fatal(err)
  32. }
  33. beforeRecord := *record
  34. beforeTraffic := readTraffic(t, database.GetDB(), client.Email)
  35. beforeSettings := inbound.Settings
  36. client.Reset, client.ResetDay, client.ResetWeekday = schedule.reset, schedule.day, schedule.weekday
  37. update := *inbound
  38. update.Settings = clientsSettings(t, []model.Client{client})
  39. switch operation {
  40. case "add inbound", "import stat":
  41. update.Id, update.Port, update.Tag = 0, 41760, "renewal-boundary-new"
  42. if operation == "import stat" {
  43. update.Settings = beforeSettings
  44. update.ClientStats = []xray.ClientTraffic{{Email: "invalid-import-stat", Reset: schedule.reset, ResetDay: schedule.day, ResetWeekday: schedule.weekday}}
  45. }
  46. _, _, err = inboundSvc.AddInbound(&update)
  47. case "update inbound":
  48. _, _, err = inboundSvc.UpdateInbound(&update)
  49. case "add inbound client":
  50. client.Email = "invalid-new-client"
  51. update.Settings = clientsSettings(t, []model.Client{client})
  52. _, err = svc.AddInboundClient(inboundSvc, &update)
  53. case "update inbound client":
  54. _, err = svc.UpdateInboundClient(inboundSvc, &update, client.Email)
  55. case "sync inbound":
  56. err = svc.SyncInbound(nil, inbound.Id, []model.Client{client})
  57. case "client delta":
  58. err = svc.ApplyInboundClientDelta(nil, inbound.Id, []model.Client{client}, nil)
  59. case "add stat":
  60. err = inboundSvc.AddClientStat(database.GetDB(), inbound.Id, &client)
  61. case "update stat":
  62. err = inboundSvc.UpdateClientStat(database.GetDB(), client.Email, &client)
  63. }
  64. if err == nil || err.Error() != schedule.errorMessage+"\n" {
  65. t.Fatalf("write error = %v, want %q", err, schedule.errorMessage)
  66. }
  67. var persisted model.Inbound
  68. if err := database.GetDB().First(&persisted, inbound.Id).Error; err != nil {
  69. t.Fatal(err)
  70. }
  71. if persisted.Settings != beforeSettings {
  72. t.Fatal("rejected write changed inbound settings")
  73. }
  74. record, err = svc.GetRecordByEmail(nil, beforeRecord.Email)
  75. if err != nil || *record != beforeRecord || readTraffic(t, database.GetDB(), beforeRecord.Email) != beforeTraffic {
  76. t.Fatalf("rejected write changed client/traffic: record=%+v error=%v", record, err)
  77. }
  78. for _, table := range []string{"inbounds", "clients", "client_traffics"} {
  79. var count int64
  80. if err := database.GetDB().Table(table).Count(&count).Error; err != nil || count != 1 {
  81. t.Fatalf("%s count/error = %d/%v, want 1/nil", table, count, err)
  82. }
  83. }
  84. if fake.addInbound.Load() != 0 || fake.updateInbound.Load() != 0 || fake.delInbound.Load() != 0 || fake.addClient.Load() != 0 || fake.updateUser.Load() != 0 {
  85. t.Fatal("rejected write dispatched to the runtime")
  86. }
  87. })
  88. }
  89. }
  90. }
  91. func TestInboundRenewalModesRemainEditable(t *testing.T) {
  92. for _, weekly := range []bool{false, true} {
  93. name := "legacy monthly with interval"
  94. if weekly {
  95. name = "weekly"
  96. }
  97. t.Run(name, func(t *testing.T) {
  98. setupConflictDB(t)
  99. nodeID, _ := setupNodeRuntime(t)
  100. svc, inboundSvc := &ClientService{}, &InboundService{}
  101. client := model.Client{Email: "renewal-editable", ID: "11111111-1111-1111-1111-111111111111", Enable: true, Reset: 7, ResetDay: 1, ResetMax: 4, ExpiryTime: time.Now().Add(time.Hour).UnixMilli()}
  102. if weekly {
  103. client.Reset, client.ResetDay, client.ResetWeekday = 0, 0, 3
  104. }
  105. inbound := &model.Inbound{Tag: "renewal-editable", NodeID: &nodeID, Port: 41761, Protocol: model.VLESS, Enable: true, Settings: clientsSettings(t, []model.Client{client})}
  106. if _, _, err := inboundSvc.AddInbound(inbound); err != nil {
  107. t.Fatal(err)
  108. }
  109. if _, _, err := inboundSvc.UpdateInbound(inbound); err != nil {
  110. t.Fatal(err)
  111. }
  112. record, err := svc.GetRecordByEmail(nil, client.Email)
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. toggle := *record.ToClient()
  117. toggle.Enable = false
  118. if _, err := svc.Update(inboundSvc, record.Id, toggle, 0); err != nil {
  119. t.Fatalf("valid inbound client could not be toggled: %v", err)
  120. }
  121. record, err = svc.GetRecordByEmail(nil, client.Email)
  122. if err != nil || record.Enable || record.Reset != client.Reset || record.ResetDay != client.ResetDay || record.ResetWeekday != client.ResetWeekday || record.ResetMax != 4 {
  123. t.Fatalf("valid schedule/toggle not preserved: %+v/%v", record, err)
  124. }
  125. })
  126. }
  127. }