package sub import ( "strings" "testing" "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) // shareLinkInbound builds a VLESS inbound with one client and the given stream // settings, mirroring flowTestInbound but without forcing a flow. func shareLinkInbound(streamSettings string) *model.Inbound { return &model.Inbound{ Listen: "203.0.113.1", Port: 443, Protocol: model.VLESS, Remark: "sharelink", Settings: `{"clients":[{"id":"11111111-2222-4333-8444-555555555555","email":"user"}],"decryption":"none","encryption":"none"}`, StreamSettings: streamSettings, } } // TestGenVlessLink_TLSParamsMapped locks every field that applyShareTLSParams // (service.go:1029) writes into a TLS share link. Without these assertions a mutant // that drops `sni`, swaps a key, or skips `pcs`/`alpn`/`fp` survives the whole suite — // the existing flow tests only check `flow=`. func TestGenVlessLink_TLSParamsMapped(t *testing.T) { stream := `{ "network":"tcp","security":"tls", "tcpSettings":{"header":{"type":"none"}}, "tlsSettings":{ "serverName":"sni.example.com", "alpn":["h2","http/1.1"], "settings":{"fingerprint":"chrome","pinnedPeerCertSha256":["YWJj"]} } }` s := &SubService{} link := s.genVlessLink(shareLinkInbound(stream), "user") // url.Values.Encode() percent-encodes values: "," -> %2C, "/" -> %2F. wants := []string{ "security=tls", "sni=sni.example.com", "fp=chrome", "alpn=h2%2Chttp%2F1.1", "pcs=YWJj", } for _, w := range wants { if !strings.Contains(link, w) { t.Fatalf("TLS link missing %q\n got: %s", w, link) } } } // Locks the reality field mapping of applyShareRealityParams; a configured // spiderX must round-trip verbatim (#5718), distinct pbk/sid catch a swap mutant. func TestGenVlessLink_RealityParamsMapped(t *testing.T) { stream := `{ "network":"tcp","security":"reality", "tcpSettings":{"header":{"type":"none"}}, "realitySettings":{ "serverNames":["reality.example.com"], "shortIds":["ab12cd"], "settings":{"publicKey":"PBKvalue","fingerprint":"firefox","spiderX":"/mypath"} } }` s := &SubService{} link := s.genVlessLink(shareLinkInbound(stream), "user") wants := []string{ "security=reality", "sni=reality.example.com", "pbk=PBKvalue", "sid=ab12cd", "fp=firefox", "spx=%2Fmypath", } for _, w := range wants { if !strings.Contains(link, w) { t.Fatalf("reality link missing %q\n got: %s", w, link) } } // A pbk<->sid swap must not silently pass: pbk must not carry the shortId. if strings.Contains(link, "pbk=ab12cd") || strings.Contains(link, "sid=PBKvalue") { t.Fatalf("reality pbk/sid mapping crossed: %s", link) } } // Without a configured spiderX, spx must still fall back to a random // "/"-prefixed value so clients always receive a plausible path. func TestGenVlessLink_RealitySpiderXFallsBackToRandom(t *testing.T) { stream := `{ "network":"tcp","security":"reality", "tcpSettings":{"header":{"type":"none"}}, "realitySettings":{ "serverNames":["reality.example.com"], "shortIds":["ab12cd"], "settings":{"publicKey":"PBKvalue","fingerprint":"firefox"} } }` s := &SubService{} link := s.genVlessLink(shareLinkInbound(stream), "user") if !strings.Contains(link, "spx=%2F") { t.Fatalf("reality link missing random spx fallback\n got: %s", link) } }