external_config_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package sub
  2. import (
  3. "encoding/base64"
  4. "net/url"
  5. "strings"
  6. "testing"
  7. "github.com/goccy/go-json"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. )
  10. func TestApplyRemarkToLinkRewritesFragment(t *testing.T) {
  11. link := "vless://[email protected]:443?security=reality&pbk=abc&sid=12#old-name"
  12. out := applyRemarkToLink(link, "DE-Provider")
  13. u, err := url.Parse(out)
  14. if err != nil {
  15. t.Fatalf("parse: %v", err)
  16. }
  17. if u.Fragment != "DE-Provider" {
  18. t.Fatalf("fragment = %q, want DE-Provider", u.Fragment)
  19. }
  20. // Everything before the fragment must be byte-for-byte preserved.
  21. if !strings.HasPrefix(out, "vless://[email protected]:443?security=reality&pbk=abc&sid=12#") {
  22. t.Fatalf("link body altered: %s", out)
  23. }
  24. }
  25. func TestApplyRemarkToLinkVmessSetsPs(t *testing.T) {
  26. payload := map[string]any{"v": "2", "ps": "old", "add": "1.2.3.4", "port": "443", "id": "uuid"}
  27. b, _ := json.Marshal(payload)
  28. link := "vmess://" + base64.StdEncoding.EncodeToString(b)
  29. out := applyRemarkToLink(link, "NL-Node")
  30. raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(out, "vmess://"))
  31. if err != nil {
  32. t.Fatalf("decode: %v", err)
  33. }
  34. var got map[string]any
  35. if err := json.Unmarshal(raw, &got); err != nil {
  36. t.Fatalf("unmarshal: %v", err)
  37. }
  38. if got["ps"] != "NL-Node" {
  39. t.Fatalf("ps = %v, want NL-Node", got["ps"])
  40. }
  41. if got["id"] != "uuid" {
  42. t.Fatalf("credentials lost: %v", got)
  43. }
  44. }
  45. func TestApplyRemarkEmptyKeepsLinkVerbatim(t *testing.T) {
  46. link := "trojan://[email protected]:8443?security=tls#orig"
  47. if out := applyRemarkToLink(link, ""); out != link {
  48. t.Fatalf("empty remark altered link: %s", out)
  49. }
  50. }
  51. func TestParsedExternalOutboundTagsProxy(t *testing.T) {
  52. link := "vless://[email protected]:443?type=tcp&security=reality&pbk=abc&sid=12&fp=chrome#srv"
  53. data := parsedExternalOutbound(link)
  54. if data == nil {
  55. t.Fatal("expected an outbound, got nil")
  56. }
  57. var ob map[string]any
  58. if err := json.Unmarshal(data, &ob); err != nil {
  59. t.Fatalf("unmarshal: %v", err)
  60. }
  61. if ob["tag"] != "proxy" {
  62. t.Fatalf("tag = %v, want proxy", ob["tag"])
  63. }
  64. if ob["protocol"] != "vless" {
  65. t.Fatalf("protocol = %v, want vless", ob["protocol"])
  66. }
  67. }
  68. func TestDecodeSubscriptionBodyBase64(t *testing.T) {
  69. plain := "vless://[email protected]:443#one\ntrojan://[email protected]:8443#two\n"
  70. body := []byte(base64.StdEncoding.EncodeToString([]byte(plain)))
  71. links := decodeSubscriptionBody(body)
  72. if len(links) != 2 || links[0] != "vless://[email protected]:443#one" || links[1] != "trojan://[email protected]:8443#two" {
  73. t.Fatalf("decoded links = %#v", links)
  74. }
  75. }
  76. func TestDecodeSubscriptionBodyPlainSkipsComments(t *testing.T) {
  77. body := []byte("# header\nvmess://abc\n\nnot-a-link\nss://def#x\n")
  78. links := decodeSubscriptionBody(body)
  79. if len(links) != 2 || links[0] != "vmess://abc" || links[1] != "ss://def#x" {
  80. t.Fatalf("decoded links = %#v", links)
  81. }
  82. }
  83. func TestExpandEntryLinkAppliesRemark(t *testing.T) {
  84. got := expandEntry(externalLinkEntry{Kind: model.ExternalLinkKindLink, Value: "trojan://[email protected]:8443#orig", Remark: "DE"})
  85. if len(got) != 1 || got[0].Name != "DE" {
  86. t.Fatalf("expandEntry = %#v", got)
  87. }
  88. }
  89. func TestExpandEntryLinkFallsBackToOriginalName(t *testing.T) {
  90. got := expandEntry(externalLinkEntry{Kind: model.ExternalLinkKindLink, Value: "trojan://[email protected]:8443#orig", Remark: ""})
  91. if len(got) != 1 || got[0].Name != "orig" {
  92. t.Fatalf("expandEntry empty remark = %#v, want Name=orig", got)
  93. }
  94. }
  95. func TestLinkDisplayName(t *testing.T) {
  96. payload := map[string]any{"v": "2", "ps": "NL-Node", "add": "1.2.3.4", "port": "443", "id": "uuid"}
  97. b, _ := json.Marshal(payload)
  98. vmess := "vmess://" + base64.StdEncoding.EncodeToString(b)
  99. cases := []struct {
  100. link string
  101. want string
  102. }{
  103. {"vless://[email protected]:443#one", "one"},
  104. {"trojan://[email protected]:8443#" + url.PathEscape("DE Node"), "DE Node"},
  105. {vmess, "NL-Node"},
  106. {"ss://def", ""},
  107. {"", ""},
  108. }
  109. for _, c := range cases {
  110. if got := linkDisplayName(c.link); got != c.want {
  111. t.Errorf("linkDisplayName(%q) = %q, want %q", c.link, got, c.want)
  112. }
  113. }
  114. }
  115. func TestClashProxyFromExternalTrojanReality(t *testing.T) {
  116. link := "trojan://[email protected]:8443?type=tcp&security=reality&sni=aws.amazon.com&pbk=PBK&sid=298b44&fp=chrome#srv"
  117. svc := NewSubClashService(false, "", NewSubService(""))
  118. proxy := svc.clashProxyFromExternal(link, "DE-Provider")
  119. if proxy == nil {
  120. t.Fatal("expected a clash proxy, got nil")
  121. }
  122. if proxy["type"] != "trojan" {
  123. t.Fatalf("type = %v, want trojan", proxy["type"])
  124. }
  125. if proxy["server"] != "37.27.201.56" {
  126. t.Fatalf("server = %v", proxy["server"])
  127. }
  128. if proxy["password"] != "provider-pass" {
  129. t.Fatalf("password = %v", proxy["password"])
  130. }
  131. if proxy["name"] != "DE-Provider" {
  132. t.Fatalf("name = %v", proxy["name"])
  133. }
  134. if proxy["tls"] != true {
  135. t.Fatalf("expected reality→tls true, got %v", proxy["tls"])
  136. }
  137. }