Files
3x-ui/internal/sub/links_test.go
mrchatam 67addab343 fix(inbounds): serve fresh client UUIDs for list and allLinks (#6458)
* fix(inbounds): serve fresh client UUIDs for list and allLinks (#6436)

Resolve clients from the clients table in inboundLinks and
backfillClientStats so /inbounds/list ClientStats and allLinks match
the running Xray identity when embedded settings JSON is stale.

* fix(sub): keep WG/AWG settings identity in link exports (#6436)

clientsForLinkExport uses the clients table for UUID-bearing protocols and
the inbound settings JSON for WireGuard/AmneziaWG so allLinks and per-client
QR links stay consistent without collapsing per-inbound tunnel keys.

* fix(sub): fall back to settings clients for link export (#6458)

Prefer ListClientsForInbound for UUID protocols, but when the clients
table is empty or unavailable fall back to GetClients so settings-only
inbounds (and share-link unit tests) still produce links. Keep WG/AWG
on settings identity.

---------

Co-authored-by: mrchatam <mrchatam@users.noreply.github.com>
Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
2026-09-12 11:12:13 +02:00

100 lines
3.3 KiB
Go

package sub
import (
"reflect"
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func TestSplitLinkLines(t *testing.T) {
cases := []struct {
name string
in string
want []string
}{
{"single_line", "vless://abc", []string{"vless://abc"}},
{"two_lines", "vless://abc\nvmess://xyz", []string{"vless://abc", "vmess://xyz"}},
{"trims_each_line", " vless://abc \n\tvmess://xyz\t", []string{"vless://abc", "vmess://xyz"}},
{"skips_blank_lines", "vless://abc\n\n\nvmess://xyz\n", []string{"vless://abc", "vmess://xyz"}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := splitLinkLines(c.in)
if !reflect.DeepEqual(got, c.want) {
t.Fatalf("splitLinkLines(%q) = %#v, want %#v", c.in, got, c.want)
}
})
}
}
func TestSplitLinkLines_EmptyInputIsNil(t *testing.T) {
if got := splitLinkLines(""); got != nil {
t.Fatalf("splitLinkLines(\"\") = %#v, want nil", got)
}
}
func TestSplitLinkLines_WhitespaceOnlyHasNoEntries(t *testing.T) {
got := splitLinkLines(" \n\t \n")
if len(got) != 0 {
t.Fatalf("splitLinkLines(whitespace) = %#v, want empty slice", got)
}
}
func TestLinksForClient_UsesHostEndpoints(t *testing.T) {
seedSubDB(t)
inbound := seedSubInbound(t, "s-gate", "gate", 4431, 1, `{"network":"tcp","security":"none"}`)
seedHost(t, &model.Host{
InboundId: inbound.Id, Remark: "public", Address: "proxy.example.com",
Port: 443, Security: "same",
})
links := NewLinkProvider().LinksForClient("req.example.com", inbound, "gate@e")
if len(links) != 1 {
t.Fatalf("links = %d, want 1: %v", len(links), links)
}
if !strings.Contains(links[0], "proxy.example.com:443") {
t.Fatalf("link = %q, want the host endpoint proxy.example.com:443", links[0])
}
}
// LinksForClient (per-client QR / links API) must use the clients-table UUID
// when the inbound settings JSON still embeds a stale id — same source as
// /inbounds/list and allLinks (#6436).
func TestLinksForClient_UsesClientsTableUUIDWhenSettingsStale(t *testing.T) {
seedSubDB(t)
db := database.GetDB()
stale := "11111111-1111-1111-1111-111111111111"
fresh := "22222222-2222-2222-2222-222222222222"
settings := `{"clients":[{"id":"` + stale + `","email":"stale@e","subId":"subStale","enable":true}],"decryption":"none"}`
ib := &model.Inbound{
UserId: 1, Tag: "stale-uuid-qr", Enable: true, Listen: "203.0.113.5", Port: 4433,
Protocol: model.VLESS, Remark: "StaleQR", Settings: settings,
StreamSettings: `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"none"}}}`,
}
if err := db.Create(ib).Error; err != nil {
t.Fatalf("seed inbound: %v", err)
}
client := &model.ClientRecord{Email: "stale@e", SubID: "subStale", UUID: fresh, Enable: true}
if err := db.Create(client).Error; err != nil {
t.Fatalf("seed client: %v", err)
}
if err := db.Create(&model.ClientInbound{ClientId: client.Id, InboundId: ib.Id}).Error; err != nil {
t.Fatalf("seed client_inbound: %v", err)
}
links := NewLinkProvider().LinksForClient("req.example.com", ib, "stale@e")
if len(links) != 1 {
t.Fatalf("links = %d, want 1: %v", len(links), links)
}
if !strings.Contains(links[0], fresh) {
t.Fatalf("link missing fresh UUID %q: %s", fresh, links[0])
}
if strings.Contains(links[0], stale) {
t.Fatalf("link still carries stale settings UUID %q: %s", stale, links[0])
}
}