external_config_test.go 5.4 KB

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