1
0

node_client_breakdown_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. package service
  2. import (
  3. "testing"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  7. )
  8. // The node-page client breakdown must classify by EMAIL, not by
  9. // client_traffics.inbound_id — that column goes stale after an inbound is
  10. // delete+recreated, so filtering by it drops almost every row and undercounts
  11. // active/disabled/ended. Here the traffic rows carry a bogus inbound_id (999)
  12. // yet must still be matched to the node's clients by email.
  13. func TestGetAll_ClientBreakdownMatchesByEmailNotStaleInboundId(t *testing.T) {
  14. setupConflictDB(t)
  15. db := database.GetDB()
  16. svc := NodeService{}
  17. if err := db.Create(&model.Node{Id: 1, Name: "n", Address: "10.0.0.1", Port: 2053, ApiToken: "t", Guid: "g"}).Error; err != nil {
  18. t.Fatalf("create node: %v", err)
  19. }
  20. nid := 1
  21. ib := &model.Inbound{Tag: "n1-in", Enable: true, Port: 443, Protocol: model.VLESS, Settings: `{"clients":[]}`, NodeID: &nid}
  22. if err := db.Create(ib).Error; err != nil {
  23. t.Fatalf("create inbound: %v", err)
  24. }
  25. mk := func(id int, email string, enable bool) {
  26. if err := db.Create(&model.ClientRecord{Id: id, Email: email, Enable: enable}).Error; err != nil {
  27. t.Fatalf("create client %s: %v", email, err)
  28. }
  29. if !enable {
  30. // Enable has gorm:"default:true", so a zero-value (false) create is
  31. // dropped and the DB applies true — force the disabled state explicitly.
  32. if err := db.Model(&model.ClientRecord{}).Where("id = ?", id).Update("enable", false).Error; err != nil {
  33. t.Fatalf("disable client %s: %v", email, err)
  34. }
  35. }
  36. if err := db.Create(&model.ClientInbound{ClientId: id, InboundId: ib.Id}).Error; err != nil {
  37. t.Fatalf("attach client %s: %v", email, err)
  38. }
  39. }
  40. mk(1, "active@x", true)
  41. mk(2, "disabled@x", false)
  42. mk(3, "depleted@x", true)
  43. // Traffic rows carry a STALE inbound_id (999), unrelated to the node inbound.
  44. const staleInboundID = 999
  45. rows := []*xray.ClientTraffic{
  46. {InboundId: staleInboundID, Email: "active@x", Enable: true},
  47. {InboundId: staleInboundID, Email: "disabled@x", Enable: false},
  48. {InboundId: staleInboundID, Email: "depleted@x", Enable: true, Total: 100, Up: 60, Down: 60}, // exhausted
  49. }
  50. for _, r := range rows {
  51. if err := db.Create(r).Error; err != nil {
  52. t.Fatalf("create traffic %s: %v", r.Email, err)
  53. }
  54. }
  55. nodes, err := svc.GetAll()
  56. if err != nil {
  57. t.Fatalf("GetAll: %v", err)
  58. }
  59. var n *model.Node
  60. for _, x := range nodes {
  61. if x.Id == 1 {
  62. n = x
  63. }
  64. }
  65. if n == nil {
  66. t.Fatal("node 1 not found")
  67. }
  68. if n.ClientCount != 3 {
  69. t.Errorf("ClientCount = %d, want 3", n.ClientCount)
  70. }
  71. if n.ActiveCount != 1 {
  72. t.Errorf("ActiveCount = %d, want 1 (matched by email despite stale inbound_id)", n.ActiveCount)
  73. }
  74. if n.DisabledCount != 1 {
  75. t.Errorf("DisabledCount = %d, want 1", n.DisabledCount)
  76. }
  77. if n.DepletedCount != 1 {
  78. t.Errorf("DepletedCount = %d, want 1", n.DepletedCount)
  79. }
  80. }