Files
3x-ui/internal/web/service/outbound/probe_testability_gate_test.go
T
BlindMaster24 efcf152950 fix(outbound): read the probe testability gate's ids like the core (#6527)
A direct, DNS, loopback or blackhole outbound is not a proxy, so the probe
must reject it instead of measuring the panel host's own reachability. The
gate compared the protocol id exactly while the core lowercases it in
LoadWithID before resolving the handler, so "Freedom" and "DNS" were not
recognised: the HTTP probe ran through the direct outbound and returned
Success=true with a full egress block, and the row's Test button stayed
enabled because isUntestable compared exactly as well. The operator reads the
panel host's own country and delay as a working tunnel.

The batch gate now folds the id once before its switch, and isUntestable goes
through the shared isOutboundProtocol helper.
2026-09-14 19:53:05 +03:00

55 lines
1.7 KiB
Go

package outbound
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
// A direct, DNS, loopback or blackhole outbound is not a tunnel, so the probe
// rejects it whatever the spelling: the core resolves "Freedom" to freedom.
func TestTestOutboundsRejectsCaseVariantUntestableIDs(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
withStubProcess(t, func(cfg *xray.Config, configPath string) batchProcess {
return &stubProcess{cfg: cfg, serveSocks: true}
})
withEgressTraceProbe(t, func(*url.URL) *TestEgressResult {
return &TestEgressResult{IPv4: "198.51.100.2", Country: "ZZ", Warp: "off"}
})
tests := []struct {
name string
protocol string
wantErr string
}{
{"freedom", "freedom", "Direct/DNS outbound cannot be tested"},
{"Freedom", "Freedom", "Direct/DNS outbound cannot be tested"},
{"FREEDOM", "FREEDOM", "Direct/DNS outbound cannot be tested"},
{"DNS", "DNS", "Direct/DNS outbound cannot be tested"},
{"Blackhole", "Blackhole", "Blocked/blackhole outbound cannot be tested"},
{"Loopback", "Loopback", "Loopback outbound cannot be tested"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
batch := mustJSON(t, []any{map[string]any{"tag": "probe", "protocol": tt.protocol}})
results, err := (&OutboundService{}).TestOutbounds(batch, srv.URL, "", "http")
if err != nil {
t.Fatalf("TestOutbounds: %v", err)
}
r := results[0]
if r.Success || r.Error != tt.wantErr {
t.Errorf("%q = success=%v err=%q egress=%+v, want the rejection %q",
tt.protocol, r.Success, r.Error, r.Egress, tt.wantErr)
}
})
}
}