client_bulk_reset_query_count_test.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. package service
  2. import (
  3. "fmt"
  4. "sync/atomic"
  5. "testing"
  6. "gorm.io/gorm"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. )
  10. // countClientTableQueries runs fn with a callback counting SELECTs against the
  11. // clients table, so a per-email lookup shows up as growth with the batch size.
  12. func countClientTableQueries(t *testing.T, name string, fn func()) int {
  13. t.Helper()
  14. db := database.GetDB()
  15. var n int64
  16. cb := "test:count_clients_query_" + name
  17. if err := db.Callback().Query().Before("gorm:query").Register(cb, func(tx *gorm.DB) {
  18. if tx.Statement != nil && tx.Statement.Table == "clients" {
  19. atomic.AddInt64(&n, 1)
  20. }
  21. }); err != nil {
  22. t.Fatalf("register query callback: %v", err)
  23. }
  24. defer func() {
  25. if err := db.Callback().Query().Remove(cb); err != nil {
  26. t.Errorf("remove query callback: %v", err)
  27. }
  28. }()
  29. fn()
  30. return int(atomic.LoadInt64(&n))
  31. }
  32. func seedEnabledClientsForReset(t *testing.T, svc *ClientService, port int, n int, prefix string) []string {
  33. t.Helper()
  34. clients := make([]model.Client, 0, n)
  35. for i := range n {
  36. email := fmt.Sprintf("%s-%d@x", prefix, i)
  37. clients = append(clients, model.Client{
  38. Email: email,
  39. ID: fmt.Sprintf("%08d-1111-1111-1111-111111111111", i),
  40. SubID: email,
  41. Enable: true,
  42. })
  43. }
  44. ib := mkInbound(t, port, model.VLESS, clientsSettings(t, clients))
  45. if err := svc.SyncInbound(nil, ib.Id, clients); err != nil {
  46. t.Fatalf("seed linkage: %v", err)
  47. }
  48. emails := make([]string, 0, n)
  49. for _, c := range clients {
  50. mkTraffic(t, ib.Id, c.Email, 100, 200, 0, 0, true)
  51. emails = append(emails, c.Email)
  52. }
  53. return emails
  54. }
  55. // TestBulkResetTraffic_DoesNotQueryPerEmail pins BulkResetTraffic's client
  56. // lookup to a batched read: the number of SELECTs on clients must not grow
  57. // with the number of emails reset.
  58. func TestBulkResetTraffic_DoesNotQueryPerEmail(t *testing.T) {
  59. setupBulkDB(t)
  60. svc := &ClientService{}
  61. inboundSvc := &InboundService{}
  62. few := seedEnabledClientsForReset(t, svc, 53010, 3, "few")
  63. many := seedEnabledClientsForReset(t, svc, 53011, 30, "many")
  64. fewCount := countClientTableQueries(t, "few", func() {
  65. if _, err := svc.BulkResetTraffic(inboundSvc, few); err != nil {
  66. t.Fatalf("BulkResetTraffic(few): %v", err)
  67. }
  68. })
  69. manyCount := countClientTableQueries(t, "many", func() {
  70. if _, err := svc.BulkResetTraffic(inboundSvc, many); err != nil {
  71. t.Fatalf("BulkResetTraffic(many): %v", err)
  72. }
  73. })
  74. if manyCount != fewCount {
  75. t.Fatalf("clients SELECTs: %d emails -> %d, %d emails -> %d; want the same batched count",
  76. len(few), fewCount, len(many), manyCount)
  77. }
  78. }