mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-28 00:24:19 +00:00
39eb5baf42
An inbound exported from a build that predated the hosts table carries its external proxies inline in streamSettings.externalProxy. The startup migration that converts those to host rows runs once and is gated off afterwards, so it never sees a freshly imported inbound, leaving its external proxies stranded in streamSettings (never surfaced as Hosts). Extract the migration's per-inbound conversion into a shared database.CreateHostsFromExternalProxy and run it inside the AddInbound transaction. No-op for inbounds without externalProxy (everything the current UI builds), so it only fires on such imports.
104 lines
3.7 KiB
Go
104 lines
3.7 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
// TestAddInbound_ImportConvertsExternalProxyToHosts reproduces the panel report:
|
|
// an inbound exported from a build that predated the hosts table carries its
|
|
// external proxies inline in streamSettings.externalProxy. The one-time startup
|
|
// migration that converts those to host rows is gated off after first run, so a
|
|
// freshly imported inbound used to land with zero hosts (its external proxies
|
|
// silently lost). AddInbound must convert them on import.
|
|
func TestAddInbound_ImportConvertsExternalProxyToHosts(t *testing.T) {
|
|
setupConflictDB(t)
|
|
svc := &InboundService{}
|
|
|
|
stream := `{
|
|
"network":"ws",
|
|
"wsSettings":{"path":"/req3","host":"astr.khafanha.ir"},
|
|
"security":"none",
|
|
"externalProxy":[
|
|
{"forceTls":"same","dest":"snapp.ir","port":8080,"remark":"","sni":"","alpn":[],"pinnedPeerCertSha256":[],"echConfigList":""},
|
|
{"forceTls":"tls","dest":"cdn.example.com","port":8443,"remark":"front","sni":"sni.example.com","fingerprint":"chrome","alpn":["h2","h3"],"pinnedPeerCertSha256":["AAAA"],"echConfigList":"ECHV"}
|
|
]
|
|
}`
|
|
settings := `{"clients":[{"id":"6df5616b-ebfd-4186-86d5-4bce29fe8805","email":"imp_user","subId":"s-imp","enable":true}],"decryption":"none","encryption":"none"}`
|
|
|
|
in := &model.Inbound{
|
|
UserId: 1,
|
|
Tag: "in-8080-tcp",
|
|
Enable: true,
|
|
Listen: "",
|
|
Port: 8080,
|
|
Protocol: model.VLESS,
|
|
StreamSettings: stream,
|
|
Settings: settings,
|
|
}
|
|
created, _, err := svc.AddInbound(in)
|
|
if err != nil {
|
|
t.Fatalf("import inbound: %v", err)
|
|
}
|
|
|
|
var hosts []model.Host
|
|
if err := database.GetDB().Where("inbound_id = ?", created.Id).Order("sort_order asc").Find(&hosts).Error; err != nil {
|
|
t.Fatalf("load hosts: %v", err)
|
|
}
|
|
if len(hosts) != 2 {
|
|
t.Fatalf("hosts = %d, want 2 (one per externalProxy entry)", len(hosts))
|
|
}
|
|
|
|
a := hosts[0]
|
|
if a.SortOrder != 0 || a.Security != "same" || a.Address != "snapp.ir" || a.Port != 8080 {
|
|
t.Fatalf("host A mapping wrong: %+v", a)
|
|
}
|
|
if a.Remark == "" {
|
|
t.Fatalf("host A remark must be backfilled for a blank externalProxy remark, got empty")
|
|
}
|
|
|
|
b := hosts[1]
|
|
if b.SortOrder != 1 || b.Security != "tls" || b.Address != "cdn.example.com" || b.Port != 8443 ||
|
|
b.Remark != "front" || b.Sni != "sni.example.com" || b.Fingerprint != "chrome" || b.EchConfigList != "ECHV" {
|
|
t.Fatalf("host B mapping wrong: %+v", b)
|
|
}
|
|
if len(b.Alpn) != 2 || b.Alpn[0] != "h2" || b.Alpn[1] != "h3" {
|
|
t.Fatalf("host B alpn = %v, want [h2 h3]", b.Alpn)
|
|
}
|
|
if len(b.PinnedPeerCertSha256) != 1 || b.PinnedPeerCertSha256[0] != "AAAA" {
|
|
t.Fatalf("host B pins = %v, want [AAAA]", b.PinnedPeerCertSha256)
|
|
}
|
|
}
|
|
|
|
// TestAddInbound_NoExternalProxyCreatesNoHosts guards the no-op path: an inbound
|
|
// built by the current UI (no externalProxy) must not gain phantom host rows.
|
|
func TestAddInbound_NoExternalProxyCreatesNoHosts(t *testing.T) {
|
|
setupConflictDB(t)
|
|
svc := &InboundService{}
|
|
|
|
in := &model.Inbound{
|
|
UserId: 1,
|
|
Tag: "in-9201-tcp",
|
|
Enable: true,
|
|
Listen: "0.0.0.0",
|
|
Port: 9201,
|
|
Protocol: model.VLESS,
|
|
StreamSettings: `{"network":"tcp","security":"none"}`,
|
|
Settings: `{"clients":[{"id":"77777777-7777-7777-7777-777777777777","email":"plain","subId":"s-plain","enable":true}],"decryption":"none","encryption":"none"}`,
|
|
}
|
|
created, _, err := svc.AddInbound(in)
|
|
if err != nil {
|
|
t.Fatalf("add inbound: %v", err)
|
|
}
|
|
|
|
var count int64
|
|
if err := database.GetDB().Model(&model.Host{}).Where("inbound_id = ?", created.Id).Count(&count).Error; err != nil {
|
|
t.Fatalf("count hosts: %v", err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatalf("host count = %d, want 0", count)
|
|
}
|
|
}
|