node_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package service
  2. import (
  3. "testing"
  4. "github.com/mhsanaei/3x-ui/v3/database/model"
  5. )
  6. func TestNormalizeBasePath(t *testing.T) {
  7. cases := []struct {
  8. in string
  9. want string
  10. }{
  11. {"", "/"},
  12. {" ", "/"},
  13. {"/", "/"},
  14. {"/panel", "/panel/"},
  15. {"panel", "/panel/"},
  16. {"panel/", "/panel/"},
  17. {"/panel/", "/panel/"},
  18. {" /panel ", "/panel/"},
  19. {"/a/b/c", "/a/b/c/"},
  20. }
  21. for _, c := range cases {
  22. t.Run(c.in, func(t *testing.T) {
  23. got := normalizeBasePath(c.in)
  24. if got != c.want {
  25. t.Fatalf("normalizeBasePath(%q) = %q, want %q", c.in, got, c.want)
  26. }
  27. })
  28. }
  29. }
  30. func TestNodeMetricKey(t *testing.T) {
  31. cases := []struct {
  32. id int
  33. metric string
  34. want string
  35. }{
  36. {1, "cpu", "node:1:cpu"},
  37. {42, "mem", "node:42:mem"},
  38. {0, "anything", "node:0:anything"},
  39. }
  40. for _, c := range cases {
  41. got := nodeMetricKey(c.id, c.metric)
  42. if got != c.want {
  43. t.Fatalf("nodeMetricKey(%d, %q) = %q, want %q", c.id, c.metric, got, c.want)
  44. }
  45. }
  46. }
  47. func TestHeartbeatPatch_ToUI_OnlineCopiesFields(t *testing.T) {
  48. p := HeartbeatPatch{
  49. Status: "ignored-source",
  50. LatencyMs: 42,
  51. XrayVersion: "1.8.4",
  52. PanelVersion: "3.0.0",
  53. CpuPct: 12.5,
  54. MemPct: 33.3,
  55. UptimeSecs: 12345,
  56. LastError: "",
  57. }
  58. ui := p.ToUI(true)
  59. if ui.Status != "online" {
  60. t.Fatalf("Status = %q, want online", ui.Status)
  61. }
  62. if ui.LatencyMs != 42 || ui.XrayVersion != "1.8.4" || ui.PanelVersion != "3.0.0" {
  63. t.Fatalf("scalar copy mismatch: %+v", ui)
  64. }
  65. if ui.CpuPct != 12.5 || ui.MemPct != 33.3 || ui.UptimeSecs != 12345 {
  66. t.Fatalf("metric copy mismatch: %+v", ui)
  67. }
  68. if ui.Error != "" {
  69. t.Fatalf("Error = %q, want empty", ui.Error)
  70. }
  71. }
  72. func TestHeartbeatPatch_ToUI_OfflinePreservesError(t *testing.T) {
  73. p := HeartbeatPatch{LastError: "connection refused"}
  74. ui := p.ToUI(false)
  75. if ui.Status != "offline" {
  76. t.Fatalf("Status = %q, want offline", ui.Status)
  77. }
  78. if ui.Error != "connection refused" {
  79. t.Fatalf("Error = %q, want %q", ui.Error, "connection refused")
  80. }
  81. }
  82. func TestNodeService_Normalize_Valid(t *testing.T) {
  83. s := &NodeService{}
  84. n := &model.Node{
  85. Name: " primary ",
  86. ApiToken: " abc ",
  87. Address: "example.com",
  88. Port: 8443,
  89. Scheme: "",
  90. BasePath: "panel",
  91. }
  92. if err := s.normalize(n); err != nil {
  93. t.Fatalf("unexpected error: %v", err)
  94. }
  95. if n.Name != "primary" {
  96. t.Fatalf("Name not trimmed: %q", n.Name)
  97. }
  98. if n.ApiToken != "abc" {
  99. t.Fatalf("ApiToken not trimmed: %q", n.ApiToken)
  100. }
  101. if n.Scheme != "https" {
  102. t.Fatalf("empty Scheme should default to https, got %q", n.Scheme)
  103. }
  104. if n.BasePath != "/panel/" {
  105. t.Fatalf("BasePath = %q, want /panel/", n.BasePath)
  106. }
  107. }
  108. func TestNodeService_Normalize_KeepsValidScheme(t *testing.T) {
  109. s := &NodeService{}
  110. n := &model.Node{Name: "n", Address: "example.com", Port: 80, Scheme: "http"}
  111. if err := s.normalize(n); err != nil {
  112. t.Fatalf("unexpected error: %v", err)
  113. }
  114. if n.Scheme != "http" {
  115. t.Fatalf("Scheme = %q, want http", n.Scheme)
  116. }
  117. }
  118. func TestNodeService_Normalize_RejectsEmptyName(t *testing.T) {
  119. s := &NodeService{}
  120. n := &model.Node{Name: " ", Address: "example.com", Port: 443}
  121. if err := s.normalize(n); err == nil {
  122. t.Fatal("expected error for empty name")
  123. }
  124. }
  125. func TestNodeService_Normalize_RejectsBadHost(t *testing.T) {
  126. s := &NodeService{}
  127. n := &model.Node{Name: "n", Address: "bad host name with spaces", Port: 443}
  128. if err := s.normalize(n); err == nil {
  129. t.Fatal("expected error for invalid host")
  130. }
  131. }
  132. func TestNodeService_Normalize_RejectsOutOfRangePort(t *testing.T) {
  133. s := &NodeService{}
  134. for _, port := range []int{0, -1, 65536, 100000} {
  135. n := &model.Node{Name: "n", Address: "example.com", Port: port}
  136. if err := s.normalize(n); err == nil {
  137. t.Fatalf("expected error for port %d", port)
  138. }
  139. }
  140. }
  141. func TestNodeService_Normalize_OverridesUnknownScheme(t *testing.T) {
  142. s := &NodeService{}
  143. n := &model.Node{Name: "n", Address: "example.com", Port: 443, Scheme: "ftp"}
  144. if err := s.normalize(n); err != nil {
  145. t.Fatalf("unexpected error: %v", err)
  146. }
  147. if n.Scheme != "https" {
  148. t.Fatalf("Scheme = %q, want https", n.Scheme)
  149. }
  150. }