| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- package service
- import (
- "testing"
- "github.com/mhsanaei/3x-ui/v3/internal/database/model"
- "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
- )
- func TestNormalizeBasePath(t *testing.T) {
- cases := []struct {
- in string
- want string
- }{
- {"", "/"},
- {" ", "/"},
- {"/", "/"},
- {"/panel", "/panel/"},
- {"panel", "/panel/"},
- {"panel/", "/panel/"},
- {"/panel/", "/panel/"},
- {" /panel ", "/panel/"},
- {"/a/b/c", "/a/b/c/"},
- }
- for _, c := range cases {
- t.Run(c.in, func(t *testing.T) {
- got := normalizeBasePath(c.in)
- if got != c.want {
- t.Fatalf("normalizeBasePath(%q) = %q, want %q", c.in, got, c.want)
- }
- })
- }
- }
- func TestNodeMetricKey(t *testing.T) {
- cases := []struct {
- id int
- metric string
- want string
- }{
- {1, "cpu", "node:1:cpu"},
- {42, "mem", "node:42:mem"},
- {0, "anything", "node:0:anything"},
- }
- for _, c := range cases {
- got := nodeMetricKey(c.id, c.metric)
- if got != c.want {
- t.Fatalf("nodeMetricKey(%d, %q) = %q, want %q", c.id, c.metric, got, c.want)
- }
- }
- }
- func TestHeartbeatPatch_ToUI_OnlineCopiesFields(t *testing.T) {
- p := HeartbeatPatch{
- Status: "ignored-source",
- LatencyMs: 42,
- XrayVersion: "1.8.4",
- PanelVersion: "3.0.0",
- CpuPct: 12.5,
- MemPct: 33.3,
- UptimeSecs: 12345,
- LastError: "",
- }
- ui := p.ToUI(true)
- if ui.Status != "online" {
- t.Fatalf("Status = %q, want online", ui.Status)
- }
- if ui.LatencyMs != 42 || ui.XrayVersion != "1.8.4" || ui.PanelVersion != "3.0.0" {
- t.Fatalf("scalar copy mismatch: %+v", ui)
- }
- if ui.CpuPct != 12.5 || ui.MemPct != 33.3 || ui.UptimeSecs != 12345 {
- t.Fatalf("metric copy mismatch: %+v", ui)
- }
- if ui.Error != "" {
- t.Fatalf("Error = %q, want empty", ui.Error)
- }
- }
- func TestHeartbeatPatch_ToUI_OfflinePreservesError(t *testing.T) {
- p := HeartbeatPatch{LastError: "connection refused"}
- ui := p.ToUI(false)
- if ui.Status != "offline" {
- t.Fatalf("Status = %q, want offline", ui.Status)
- }
- if ui.Error != "connection refused" {
- t.Fatalf("Error = %q, want %q", ui.Error, "connection refused")
- }
- }
- func TestNodeService_Normalize_Valid(t *testing.T) {
- s := &NodeService{}
- n := &model.Node{
- Name: " primary ",
- ApiToken: " abc ",
- Address: "example.com",
- Port: 8443,
- Scheme: "",
- BasePath: "panel",
- }
- if err := s.normalize(n); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if n.Name != "primary" {
- t.Fatalf("Name not trimmed: %q", n.Name)
- }
- if n.ApiToken != "abc" {
- t.Fatalf("ApiToken not trimmed: %q", n.ApiToken)
- }
- if n.Scheme != "https" {
- t.Fatalf("empty Scheme should default to https, got %q", n.Scheme)
- }
- if n.BasePath != "/panel/" {
- t.Fatalf("BasePath = %q, want /panel/", n.BasePath)
- }
- }
- func TestNodeService_Normalize_KeepsValidScheme(t *testing.T) {
- s := &NodeService{}
- n := &model.Node{Name: "n", Address: "example.com", Port: 80, Scheme: "http"}
- if err := s.normalize(n); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if n.Scheme != "http" {
- t.Fatalf("Scheme = %q, want http", n.Scheme)
- }
- }
- func TestNodeService_Normalize_RejectsEmptyName(t *testing.T) {
- s := &NodeService{}
- n := &model.Node{Name: " ", Address: "example.com", Port: 443}
- if err := s.normalize(n); err == nil {
- t.Fatal("expected error for empty name")
- }
- }
- func TestNodeService_Normalize_RejectsBadHost(t *testing.T) {
- s := &NodeService{}
- n := &model.Node{Name: "n", Address: "bad host name with spaces", Port: 443}
- if err := s.normalize(n); err == nil {
- t.Fatal("expected error for invalid host")
- }
- }
- func TestNodeService_Normalize_RejectsOutOfRangePort(t *testing.T) {
- s := &NodeService{}
- for _, port := range []int{0, -1, 65536, 100000} {
- n := &model.Node{Name: "n", Address: "example.com", Port: port}
- if err := s.normalize(n); err == nil {
- t.Fatalf("expected error for port %d", port)
- }
- }
- }
- func TestNodeService_Normalize_OverridesUnknownScheme(t *testing.T) {
- s := &NodeService{}
- n := &model.Node{Name: "n", Address: "example.com", Port: 443, Scheme: "ftp"}
- if err := s.normalize(n); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if n.Scheme != "https" {
- t.Fatalf("Scheme = %q, want https", n.Scheme)
- }
- }
- func TestNodeService_NormalizeInboundSelection(t *testing.T) {
- s := &NodeService{}
- n := &model.Node{
- Name: "n",
- Address: "example.com",
- Port: 443,
- InboundSyncMode: "selected",
- InboundTags: []string{" alpha ", "", "beta", "alpha"},
- }
- if err := s.normalize(n); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if n.InboundSyncMode != "selected" {
- t.Fatalf("InboundSyncMode = %q, want selected", n.InboundSyncMode)
- }
- if len(n.InboundTags) != 2 || n.InboundTags[0] != "alpha" || n.InboundTags[1] != "beta" {
- t.Fatalf("InboundTags = %#v, want [alpha beta]", n.InboundTags)
- }
- }
- func TestFilterNodeSnapshot(t *testing.T) {
- snapshot := func() *runtime.TrafficSnapshot {
- return &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{
- {Tag: "alpha"},
- {Tag: "beta"},
- {Tag: "gamma"},
- }}
- }
- all := snapshot()
- FilterNodeSnapshot(&model.Node{InboundSyncMode: "all"}, all)
- if len(all.Inbounds) != 3 {
- t.Fatalf("all mode kept %d inbounds, want 3", len(all.Inbounds))
- }
- selected := snapshot()
- FilterNodeSnapshot(&model.Node{
- InboundSyncMode: "selected",
- InboundTags: []string{"beta"},
- }, selected)
- if len(selected.Inbounds) != 1 || selected.Inbounds[0].Tag != "beta" {
- t.Fatalf("selected mode produced %#v, want only beta", selected.Inbounds)
- }
- none := snapshot()
- FilterNodeSnapshot(&model.Node{InboundSyncMode: "selected"}, none)
- if len(none.Inbounds) != 0 {
- t.Fatalf("empty selection kept %d inbounds, want 0", len(none.Inbounds))
- }
- }
|