package sub import ( "strings" "testing" "github.com/mhsanaei/3x-ui/v3/internal/database" "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) // Socks-family inbounds produce a per-client socks:// link: the account // name is the client email, the password is the client's password — the // same pair the generated core config carries in settings.accounts. func TestGetLink_SocksFamily(t *testing.T) { seedSubDB(t) db := database.GetDB() settings := `{"auth":"password","accounts":[{"user":"fixed@landing","pass":"fixed-pass"}],"udp":true,` + `"clients":[{"email":"user1@x","password":"pass-1","subId":"sub1","enable":true}]}` ib := &model.Inbound{ UserId: 1, Tag: "sk5", Enable: true, Listen: "203.0.113.9", Port: 32080, Protocol: model.Mixed, Remark: "sk5-mixed", Settings: settings, } if err := db.Create(ib).Error; err != nil { t.Fatalf("seed inbound: %v", err) } svc := NewSubService("") svc.PrepareForRequest("req.example.com") link := svc.GetLink(ib, "user1@x") wantPrefix := "socks://user1%40x:pass-1@203.0.113.9:32080" if !strings.HasPrefix(link, wantPrefix) { t.Fatalf("socks link = %q, want prefix %q", link, wantPrefix) } if strings.Contains(link, "fixed@landing") || strings.Contains(link, "fixed-pass") { t.Fatalf("the inbound's fixed account must never leak into a client link: %q", link) } if got := svc.GetLink(ib, "nobody@x"); got != "" { t.Fatalf("unknown client must produce no link, got %q", got) } links := svc.inboundLinks(ib) if len(links) != 1 || !strings.HasPrefix(links[0], wantPrefix) { t.Fatalf("inboundLinks must enumerate the socks client link, got %v", links) } } // The full subscription path must resolve a socks-family inbound too: // getInboundsBySubId gates on a protocol list that historically omitted // socks/mixed, which made such subscriptions 404 even with a link // generator in place. func TestGetSubs_SocksFamily(t *testing.T) { seedSubDB(t) db := database.GetDB() settings := `{"auth":"password","accounts":[{"user":"fixed@landing","pass":"fixed-pass"}],"udp":true,` + `"clients":[{"email":"user1@x","password":"pass-1","subId":"sub1","enable":true}]}` ib := &model.Inbound{ UserId: 1, Tag: "sk5", Enable: true, Listen: "203.0.113.9", Port: 32080, Protocol: model.Mixed, Remark: "sk5-mixed", Settings: settings, } if err := db.Create(ib).Error; err != nil { t.Fatalf("seed inbound: %v", err) } rec := &model.ClientRecord{Email: "user1@x", SubID: "sub1", Password: "pass-1", Enable: true} if err := db.Create(rec).Error; err != nil { t.Fatalf("seed client: %v", err) } if err := db.Create(&model.ClientInbound{ClientId: rec.Id, InboundId: ib.Id}).Error; err != nil { t.Fatalf("seed link: %v", err) } svc := NewSubService("") links, _, _, _, err := svc.GetSubs("sub1", "req.example.com") if err != nil { t.Fatalf("GetSubs: %v", err) } if len(links) != 1 || !strings.HasPrefix(links[0], "socks://user1%40x:pass-1@203.0.113.9:32080") { t.Fatalf("GetSubs must return the socks link, got %v", links) } } func TestGetLink_LegacySocksProtocol(t *testing.T) { seedSubDB(t) db := database.GetDB() settings := `{"auth":"password","accounts":[],"clients":[{"email":"user1@x","password":"pass-1","subId":"sub1","enable":true}]}` ib := &model.Inbound{ UserId: 1, Tag: "sk5-old", Enable: true, Listen: "203.0.113.9", Port: 32081, Protocol: model.Protocol("socks"), Remark: "sk5-old", Settings: settings, } if err := db.Create(ib).Error; err != nil { t.Fatalf("seed inbound: %v", err) } svc := NewSubService("") svc.PrepareForRequest("req.example.com") link := svc.GetLink(ib, "user1@x") if !strings.HasPrefix(link, "socks://user1%40x:pass-1@203.0.113.9:32081") { t.Fatalf("legacy socks link = %q", link) } }