online_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package xray
  2. import (
  3. "slices"
  4. "testing"
  5. )
  6. func newOnlineTestProcess() *Process {
  7. return &Process{newProcess(nil)}
  8. }
  9. func assertSameSet(t *testing.T, label string, got, want []string) {
  10. t.Helper()
  11. g := append([]string(nil), got...)
  12. w := append([]string(nil), want...)
  13. slices.Sort(g)
  14. slices.Sort(w)
  15. if !slices.Equal(g, w) {
  16. t.Errorf("%s = %v, want %v", label, got, want)
  17. }
  18. }
  19. // TestGetOnlineClientsByNodeScopesPerNode pins the fix for issue #4809: a
  20. // client online on one node must not be reported online on any other node.
  21. func TestGetOnlineClientsByNodeScopesPerNode(t *testing.T) {
  22. p := newOnlineTestProcess()
  23. p.RefreshLocalOnline([]string{"user1"}, nil, 1000, 20000)
  24. p.SetNodeOnlineClients(3, []string{"user1", "user2"})
  25. p.SetNodeOnlineClients(5, []string{"user3"})
  26. byNode := p.GetOnlineClientsByNode()
  27. assertSameSet(t, "local (key 0)", byNode[localNodeKey], []string{"user1"})
  28. assertSameSet(t, "node 3", byNode[3], []string{"user1", "user2"})
  29. assertSameSet(t, "node 5", byNode[5], []string{"user3"})
  30. if slices.Contains(byNode[5], "user1") {
  31. t.Errorf("user1 leaked onto node 5: %v", byNode[5])
  32. }
  33. if slices.Contains(byNode[localNodeKey], "user3") || slices.Contains(byNode[3], "user3") {
  34. t.Errorf("user3 leaked off node 5: local=%v node3=%v", byNode[localNodeKey], byNode[3])
  35. }
  36. }
  37. // TestGetOnlineClientsByNodeOmitsEmptyGroups keeps the payload small: a node
  38. // with no online clients (e.g. just cleared) must not appear as an empty key.
  39. func TestGetOnlineClientsByNodeOmitsEmptyGroups(t *testing.T) {
  40. p := newOnlineTestProcess()
  41. p.SetNodeOnlineClients(3, []string{"user1"})
  42. p.SetNodeOnlineClients(7, []string{})
  43. byNode := p.GetOnlineClientsByNode()
  44. if _, ok := byNode[7]; ok {
  45. t.Errorf("node 7 has no online clients but is present: %v", byNode)
  46. }
  47. if _, ok := byNode[localNodeKey]; ok {
  48. t.Errorf("no local clients online but key 0 is present: %v", byNode)
  49. }
  50. }
  51. // TestGetOnlineClientsUnionDedupes confirms the flat union (used by the
  52. // client-centric / total-count views) still merges every node and dedupes.
  53. func TestGetOnlineClientsUnionDedupes(t *testing.T) {
  54. p := newOnlineTestProcess()
  55. p.RefreshLocalOnline([]string{"user1"}, nil, 1000, 20000)
  56. p.SetNodeOnlineClients(3, []string{"user1", "user2"})
  57. assertSameSet(t, "union", p.GetOnlineClients(), []string{"user1", "user2"})
  58. }
  59. // TestRefreshLocalOnlineGraceWindow checks the in-memory local set honours the
  60. // grace window: idle-but-recent clients stay online, stale ones age out, and
  61. // the set is derived only from local activity (never the shared DB column).
  62. func TestRefreshLocalOnlineGraceWindow(t *testing.T) {
  63. p := newOnlineTestProcess()
  64. const grace = 20000
  65. p.RefreshLocalOnline([]string{"user1"}, nil, 1000, grace)
  66. if got := p.GetOnlineClientsByNode()[localNodeKey]; !slices.Contains(got, "user1") {
  67. t.Fatalf("user1 should be online right after activity, got %v", got)
  68. }
  69. p.RefreshLocalOnline([]string{"user2"}, nil, 11000, grace)
  70. got := p.GetOnlineClientsByNode()[localNodeKey]
  71. if !slices.Contains(got, "user1") || !slices.Contains(got, "user2") {
  72. t.Fatalf("both within grace window, got %v", got)
  73. }
  74. p.RefreshLocalOnline(nil, nil, 22000, grace)
  75. got = p.GetOnlineClientsByNode()[localNodeKey]
  76. if slices.Contains(got, "user1") {
  77. t.Errorf("user1 (idle 21s, past grace) should have aged out, got %v", got)
  78. }
  79. if !slices.Contains(got, "user2") {
  80. t.Errorf("user2 (idle 11s, within grace) should still be online, got %v", got)
  81. }
  82. }
  83. // TestGetActiveInboundsByNodeTracksGraceWindow pins the fix for issue #4859: a
  84. // multi-inbound client must only count as online on inbounds that actually
  85. // carried traffic. The active-inbound signal honours the same grace window as
  86. // the online-email signal, and only this panel's tags report under key 0.
  87. func TestGetActiveInboundsByNodeTracksGraceWindow(t *testing.T) {
  88. p := newOnlineTestProcess()
  89. const grace = 20000
  90. p.RefreshLocalOnline([]string{"alice"}, []string{"inbound-a"}, 1000, grace)
  91. got := p.GetActiveInboundsByNode()[localNodeKey]
  92. assertSameSet(t, "active after first poll", got, []string{"inbound-a"})
  93. p.RefreshLocalOnline([]string{"alice"}, []string{"inbound-b"}, 11000, grace)
  94. got = p.GetActiveInboundsByNode()[localNodeKey]
  95. assertSameSet(t, "both within grace", got, []string{"inbound-a", "inbound-b"})
  96. p.RefreshLocalOnline(nil, nil, 22000, grace)
  97. got = p.GetActiveInboundsByNode()[localNodeKey]
  98. assertSameSet(t, "inbound-a (idle 21s, past grace) aged out, inbound-b kept", got, []string{"inbound-b"})
  99. p.RefreshLocalOnline(nil, nil, 40000, grace)
  100. if _, ok := p.GetActiveInboundsByNode()[localNodeKey]; ok {
  101. t.Errorf("all inbounds idle past grace, key 0 should be absent: %v", p.GetActiveInboundsByNode())
  102. }
  103. }
  104. // TestClearNodeOnlineClientsDropsNode mirrors a failed node probe: the node's
  105. // clients must disappear from the per-node map immediately.
  106. func TestClearNodeOnlineClientsDropsNode(t *testing.T) {
  107. p := newOnlineTestProcess()
  108. p.SetNodeOnlineClients(3, []string{"user1"})
  109. p.ClearNodeOnlineClients(3)
  110. if _, ok := p.GetOnlineClientsByNode()[3]; ok {
  111. t.Errorf("node 3 should be absent after ClearNodeOnlineClients")
  112. }
  113. }