ImportSubscription no longer requires only http(s): paste awg://, AWG .conf/JSON, or naive links; subscription parser also reads hosted AWG and Clash wireguard/naive. Site lists support emails and FAQ; Store listing texts/tools updated. Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
3.9 KiB
Go
147 lines
3.9 KiB
Go
package core
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"vpnclient/internal/config"
|
|
)
|
|
|
|
func newTestManager(t *testing.T) *Manager {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
cfgPath := filepath.Join(dir, "configs", "config.json")
|
|
cfg := config.Example()
|
|
if err := config.Save(cfgPath, cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
loaded, err := config.Load(cfgPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := NewManager(cfgPath, loaded, io.Discard)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
const testSubBody = "vless://7e1c0fbf-1758-4781-97f6-ae8715ed3f60@de.example.com:443?encryption=none&type=tcp&security=tls&sni=de.example.com#DE-1\n" +
|
|
"hysteria2://pass@nl.example.com:9000/?sni=nl.example.com#NL-1\n"
|
|
|
|
// A credential-less https URL pasted into the "add server link" field must be
|
|
// treated as a subscription URL and imported — never fail with
|
|
// "proxy URI missing username" (regression for the naive proxy-URI parser).
|
|
func TestSaveProfileRoutesSubscriptionURL(t *testing.T) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = io.WriteString(w, testSubBody)
|
|
}))
|
|
defer ts.Close()
|
|
|
|
m := newTestManager(t)
|
|
if err := m.SaveProfile("whatever", ts.URL+"/ZKszcVcC3xbWb8qj"); err != nil {
|
|
t.Fatalf("SaveProfile(subscription URL) failed: %v", err)
|
|
}
|
|
names := map[string]bool{}
|
|
for _, p := range m.Profiles() {
|
|
names[p.Name] = true
|
|
}
|
|
if !names["DE-1"] || !names["NL-1"] {
|
|
t.Fatalf("subscription profiles not imported, got %v", names)
|
|
}
|
|
if m.Config().SubscriptionURL == "" {
|
|
t.Fatal("subscription URL not persisted")
|
|
}
|
|
}
|
|
|
|
func TestSaveActiveProfileRoutesSubscriptionURL(t *testing.T) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = io.WriteString(w, testSubBody)
|
|
}))
|
|
defer ts.Close()
|
|
|
|
m := newTestManager(t)
|
|
if err := m.SaveActiveProfile("naive-main", ts.URL+"/short"); err != nil {
|
|
t.Fatalf("SaveActiveProfile(subscription URL) failed: %v", err)
|
|
}
|
|
if len(m.Profiles()) < 3 { // naive-main + 2 imported
|
|
t.Fatalf("expected imported profiles, got %d", len(m.Profiles()))
|
|
}
|
|
}
|
|
|
|
// A real naive share link (with credentials) must still be saved as a proxy.
|
|
func TestSaveProfileKeepsNaiveLink(t *testing.T) {
|
|
m := newTestManager(t)
|
|
if err := m.SaveProfile("naive-1", "https://user:pass@proxy.example.com:443"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p, err := m.Config().ActiveProfile()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p.Protocol != config.ProtocolNaive || !strings.Contains(p.Proxy, "user:pass@") {
|
|
t.Fatalf("naive profile broken: %+v", p)
|
|
}
|
|
}
|
|
|
|
// «Добавить конфигурацию» must accept pasted AWG 2.0 .conf / naive links,
|
|
// not only http(s) subscription URLs (regression: "нужен http(s) URL").
|
|
func TestImportSubscriptionDirectAWGAndNaive(t *testing.T) {
|
|
m := newTestManager(t)
|
|
awgBody := `[Interface]
|
|
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
|
Address = 10.8.0.2/32
|
|
Jc = 4
|
|
Jmin = 40
|
|
Jmax = 70
|
|
H1 = 1
|
|
H2 = 2
|
|
H3 = 3
|
|
H4 = 4
|
|
|
|
[Peer]
|
|
PublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEE=
|
|
Endpoint = 203.0.113.10:51820
|
|
AllowedIPs = 0.0.0.0/0
|
|
`
|
|
r, err := m.ImportSubscription(awgBody)
|
|
if err != nil {
|
|
t.Fatalf("AWG paste: %v", err)
|
|
}
|
|
if r.Imported < 1 {
|
|
t.Fatalf("AWG imported=%d", r.Imported)
|
|
}
|
|
foundAWG := false
|
|
for _, p := range m.Profiles() {
|
|
if config.Protocol(p.Protocol) == config.ProtocolAWG && strings.Contains(p.Proxy, "203.0.113.10:51820") {
|
|
foundAWG = true
|
|
break
|
|
}
|
|
}
|
|
if !foundAWG {
|
|
t.Fatal("AWG profile missing")
|
|
}
|
|
|
|
r2, err := m.ImportSubscription("https://user:secret@naive.example.com:443#NV")
|
|
if err != nil {
|
|
t.Fatalf("naive paste: %v", err)
|
|
}
|
|
if r2.Imported < 1 {
|
|
t.Fatalf("naive imported=%d", r2.Imported)
|
|
}
|
|
foundNaive := false
|
|
for _, p := range m.Profiles() {
|
|
if config.Protocol(p.Protocol) == config.ProtocolNaive && strings.Contains(p.Proxy, "naive.example.com") {
|
|
foundNaive = true
|
|
break
|
|
}
|
|
}
|
|
if !foundNaive {
|
|
t.Fatal("naive profile missing")
|
|
}
|
|
}
|