Files
3x-ui/internal/xray/api_test.go
T
MHSanaei 89c27c5835 fix(xray): guard RemoveUser against an uninitialized handler client
Every XrayAPI handler method returns an error when HandlerServiceClient is nil,
except RemoveUser, which dereferenced it directly. A depletion sweep runs Init
with the port ignored and, during a restart window where the fresh process's
api port is still 0, Init fails and leaves the client nil — so RemoveUser
panicked (recovered by the traffic writer, but re-thrown every poll) instead of
returning an error. Add the same nil guard the siblings have.
2026-07-15 03:21:11 +02:00

93 lines
2.7 KiB
Go

package xray
import (
"strings"
"testing"
)
// RemoveUser must return an error, not panic, when the handler client is not
// initialized — matching every sibling API method. A depletion sweep can reach
// it with a nil client during a restart window where Init(0) failed.
func TestRemoveUserGuardsNilHandlerClient(t *testing.T) {
err := (&XrayAPI{}).RemoveUser("in-443-tcp", "user@example.com")
if err == nil {
t.Fatal("RemoveUser with an uninitialized HandlerServiceClient must return an error")
}
}
func TestGetRequiredUserString_Present(t *testing.T) {
user := map[string]any{"email": "alice@example.com"}
got, err := getRequiredUserString(user, "email")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "alice@example.com" {
t.Fatalf("got %q, want %q", got, "alice@example.com")
}
}
func TestGetRequiredUserString_Missing(t *testing.T) {
user := map[string]any{}
if _, err := getRequiredUserString(user, "email"); err == nil {
t.Fatal("expected error for missing key")
}
}
func TestGetRequiredUserString_NilValue(t *testing.T) {
user := map[string]any{"email": nil}
if _, err := getRequiredUserString(user, "email"); err == nil {
t.Fatal("expected error for nil value")
}
}
func TestGetRequiredUserString_WrongType(t *testing.T) {
user := map[string]any{"email": 42}
_, err := getRequiredUserString(user, "email")
if err == nil {
t.Fatal("expected error for non-string value")
}
if !strings.Contains(err.Error(), "invalid type") {
t.Fatalf("expected %q in error, got: %v", "invalid type", err)
}
}
func TestGetOptionalUserString_Present(t *testing.T) {
user := map[string]any{"flow": "xtls-rprx-vision"}
got, err := getOptionalUserString(user, "flow")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "xtls-rprx-vision" {
t.Fatalf("got %q, want %q", got, "xtls-rprx-vision")
}
}
func TestGetOptionalUserString_MissingReturnsEmptyNoError(t *testing.T) {
user := map[string]any{}
got, err := getOptionalUserString(user, "flow")
if err != nil {
t.Fatalf("unexpected error for missing optional field: %v", err)
}
if got != "" {
t.Fatalf("got %q, want empty string", got)
}
}
func TestGetOptionalUserString_NilReturnsEmptyNoError(t *testing.T) {
user := map[string]any{"flow": nil}
got, err := getOptionalUserString(user, "flow")
if err != nil {
t.Fatalf("unexpected error for nil optional field: %v", err)
}
if got != "" {
t.Fatalf("got %q, want empty string", got)
}
}
func TestGetOptionalUserString_WrongTypeErrors(t *testing.T) {
user := map[string]any{"flow": []string{"a", "b"}}
if _, err := getOptionalUserString(user, "flow"); err == nil {
t.Fatal("expected error for non-string optional value")
}
}