Files
3x-ui-web-src/internal/sub/external_config_test.go
T
Sanaei f727d04f65
CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

179 lines
5.4 KiB
Go

package sub
import (
"encoding/base64"
"net/url"
"strings"
"testing"
"time"
"github.com/goccy/go-json"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func TestApplyRemarkToLinkRewritesFragment(t *testing.T) {
link := "vless://uuid@example.com:443?security=reality&pbk=abc&sid=12#old-name"
out := applyRemarkToLink(link, "DE-Provider")
u, err := url.Parse(out)
if err != nil {
t.Fatalf("parse: %v", err)
}
if u.Fragment != "DE-Provider" {
t.Fatalf("fragment = %q, want DE-Provider", u.Fragment)
}
// Everything before the fragment must be byte-for-byte preserved.
if !strings.HasPrefix(out, "vless://uuid@example.com:443?security=reality&pbk=abc&sid=12#") {
t.Fatalf("link body altered: %s", out)
}
}
func TestApplyRemarkToLinkVmessSetsPs(t *testing.T) {
payload := map[string]any{"v": "2", "ps": "old", "add": "1.2.3.4", "port": "443", "id": "uuid"}
b, _ := json.Marshal(payload)
link := "vmess://" + base64.StdEncoding.EncodeToString(b)
out := applyRemarkToLink(link, "NL-Node")
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(out, "vmess://"))
if err != nil {
t.Fatalf("decode: %v", err)
}
var got map[string]any
if err := json.Unmarshal(raw, &got); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if got["ps"] != "NL-Node" {
t.Fatalf("ps = %v, want NL-Node", got["ps"])
}
if got["id"] != "uuid" {
t.Fatalf("credentials lost: %v", got)
}
}
func TestApplyRemarkEmptyKeepsLinkVerbatim(t *testing.T) {
link := "trojan://pass@1.2.3.4:8443?security=tls#orig"
if out := applyRemarkToLink(link, ""); out != link {
t.Fatalf("empty remark altered link: %s", out)
}
}
func TestParsedExternalOutboundTagsProxy(t *testing.T) {
link := "vless://uuid@example.com:443?type=tcp&security=reality&pbk=abc&sid=12&fp=chrome#srv"
data := parsedExternalOutbound(link)
if data == nil {
t.Fatal("expected an outbound, got nil")
}
var ob map[string]any
if err := json.Unmarshal(data, &ob); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if ob["tag"] != "proxy" {
t.Fatalf("tag = %v, want proxy", ob["tag"])
}
if ob["protocol"] != "vless" {
t.Fatalf("protocol = %v, want vless", ob["protocol"])
}
}
func TestDecodeSubscriptionBodyBase64(t *testing.T) {
plain := "vless://uuid@a.com:443#one\ntrojan://pw@b.com:8443#two\n"
body := []byte(base64.StdEncoding.EncodeToString([]byte(plain)))
links := decodeSubscriptionBody(body)
if len(links) != 2 || links[0] != "vless://uuid@a.com:443#one" || links[1] != "trojan://pw@b.com:8443#two" {
t.Fatalf("decoded links = %#v", links)
}
}
func TestDecodeSubscriptionBodyPlainSkipsComments(t *testing.T) {
body := []byte("# header\nvmess://abc\n\nnot-a-link\nss://def#x\n")
links := decodeSubscriptionBody(body)
if len(links) != 2 || links[0] != "vmess://abc" || links[1] != "ss://def#x" {
t.Fatalf("decoded links = %#v", links)
}
}
func TestExpandEntryLinkAppliesRemark(t *testing.T) {
got := expandEntry(externalLinkEntry{Kind: model.ExternalLinkKindLink, Value: "trojan://pw@b.com:8443#orig", Remark: "DE"})
if len(got) != 1 || got[0].Name != "DE" {
t.Fatalf("expandEntry = %#v", got)
}
}
func TestExpandEntrySubscriptionAppliesNamePrefix(t *testing.T) {
const subURL = "https://provider.example/sub-prefix"
subscriptionCache.Lock()
subscriptionCache.m[subURL] = subscriptionCacheEntry{
links: []string{"trojan://pw@b.com:8443#HK-01"},
fetchedAt: time.Now(),
}
subscriptionCache.Unlock()
t.Cleanup(func() {
subscriptionCache.Lock()
delete(subscriptionCache.m, subURL)
subscriptionCache.Unlock()
})
got := expandEntry(externalLinkEntry{
Kind: model.ExternalLinkKindSubscription,
Value: subURL,
NamePrefix: "[zjh] ",
Email: "zjh",
})
if len(got) != 1 || got[0].Name != "[zjh] HK-01" {
t.Fatalf("expandEntry = %#v", got)
}
}
func TestExpandEntryLinkFallsBackToOriginalName(t *testing.T) {
got := expandEntry(externalLinkEntry{Kind: model.ExternalLinkKindLink, Value: "trojan://pw@b.com:8443#orig", Remark: ""})
if len(got) != 1 || got[0].Name != "orig" {
t.Fatalf("expandEntry empty remark = %#v, want Name=orig", got)
}
}
func TestLinkDisplayName(t *testing.T) {
payload := map[string]any{"v": "2", "ps": "NL-Node", "add": "1.2.3.4", "port": "443", "id": "uuid"}
b, _ := json.Marshal(payload)
vmess := "vmess://" + base64.StdEncoding.EncodeToString(b)
cases := []struct {
link string
want string
}{
{"vless://uuid@a.com:443#one", "one"},
{"trojan://pw@b.com:8443#" + url.PathEscape("DE Node"), "DE Node"},
{vmess, "NL-Node"},
{"ss://def", ""},
{"", ""},
}
for _, c := range cases {
if got := linkDisplayName(c.link); got != c.want {
t.Errorf("linkDisplayName(%q) = %q, want %q", c.link, got, c.want)
}
}
}
func TestClashProxyFromExternalTrojanReality(t *testing.T) {
link := "trojan://provider-pass@37.27.201.56:8443?type=tcp&security=reality&sni=aws.amazon.com&pbk=PBK&sid=298b44&fp=chrome#srv"
svc := NewSubClashService(false, "", NewSubService(""))
proxy := svc.clashProxyFromExternal(link, "DE-Provider")
if proxy == nil {
t.Fatal("expected a clash proxy, got nil")
}
if proxy["type"] != "trojan" {
t.Fatalf("type = %v, want trojan", proxy["type"])
}
if proxy["server"] != "37.27.201.56" {
t.Fatalf("server = %v", proxy["server"])
}
if proxy["password"] != "provider-pass" {
t.Fatalf("password = %v", proxy["password"])
}
if proxy["name"] != "DE-Provider" {
t.Fatalf("name = %v", proxy["name"])
}
if proxy["tls"] != true {
t.Fatalf("expected reality→tls true, got %v", proxy["tls"])
}
}