From ca83acc32388db6129ccbc4ac9238b10267f9e2c Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Thu, 4 Jun 2026 08:32:56 +0800 Subject: [PATCH] test(handler/router): add handler_test.go (NewHandler, Init, Handle, Close) --- handler/router/handler_test.go | 262 +++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 handler/router/handler_test.go diff --git a/handler/router/handler_test.go b/handler/router/handler_test.go new file mode 100644 index 00000000..e6a26225 --- /dev/null +++ b/handler/router/handler_test.go @@ -0,0 +1,262 @@ +package router + +import ( + "bytes" + "context" + "testing" + + "github.com/go-gost/core/auth" + "github.com/go-gost/core/handler" + "github.com/go-gost/relay" + "github.com/google/uuid" +) + +// --------------------------------------------------------------------------- +// newInitdHandler — convenience constructor with minimal Init +// --------------------------------------------------------------------------- + +func newInitdHandler(t *testing.T, opts ...handler.Option) *routerHandler { + t.Helper() + h := NewHandler(append([]handler.Option{ + handler.LoggerOption(&testLogger{}), + }, opts...)...).(*routerHandler) + if err := h.parseMetadata(testMD(nil)); err != nil { + t.Fatalf("parseMetadata: %v", err) + } + h.id = uuid.New().String() + h.log = &testLogger{} + h.pool = NewConnectorPool(h.id) + _, cancel := context.WithCancel(context.Background()) + h.cancel = cancel + return h +} + +// --------------------------------------------------------------------------- +// NewHandler tests +// --------------------------------------------------------------------------- + +func TestNewHandler_Minimal(t *testing.T) { + h := NewHandler() + if h == nil { + t.Fatal("handler is nil") + } + rh := h.(*routerHandler) + if rh.options.Service != "" { + t.Errorf("Service = %q, want empty", rh.options.Service) + } + if rh.sdCache == nil { + t.Error("sdCache is nil") + } + if rh.routeCache == nil { + t.Error("routeCache is nil") + } +} + +func TestNewHandler_WithOptions(t *testing.T) { + h := NewHandler( + handler.LoggerOption(&testLogger{}), + handler.ServiceOption("test-svc"), + ) + rh := h.(*routerHandler) + if rh.options.Logger == nil { + t.Error("Logger not set") + } + if rh.options.Service != "test-svc" { + t.Errorf("Service = %q, want test-svc", rh.options.Service) + } +} + +// --------------------------------------------------------------------------- +// Init tests +// --------------------------------------------------------------------------- + +func TestInit_Minimal(t *testing.T) { + rh := NewHandler(handler.LoggerOption(&testLogger{})).(*routerHandler) + if err := rh.Init(testMD(nil)); err != nil { + t.Fatalf("Init: %v", err) + } + if rh.id == "" { + t.Error("id is empty") + } + if rh.cancel == nil { + t.Error("cancel is nil") + } + if rh.stats != nil { + t.Error("stats should be nil (no observer)") + } + if rh.limiter != nil { + t.Error("limiter should be nil (no limiter)") + } +} + +func TestInit_WithObserver(t *testing.T) { + obs := newFakeObserver(10) + rh := NewHandler( + handler.LoggerOption(&testLogger{}), + handler.ObserverOption(obs), + handler.ServiceOption("test-svc"), + ).(*routerHandler) + + if err := rh.Init(testMD(nil)); err != nil { + t.Fatalf("Init: %v", err) + } + if rh.stats == nil { + t.Error("stats should be non-nil when observer is set") + } +} + +func TestInit_WithLimiter(t *testing.T) { + rh := NewHandler( + handler.LoggerOption(&testLogger{}), + handler.TrafficLimiterOption(&mockTrafficLimiter{}), + ).(*routerHandler) + + if err := rh.Init(testMD(nil)); err != nil { + t.Fatalf("Init: %v", err) + } + if rh.limiter == nil { + t.Error("limiter should be non-nil when limiter is set") + } +} + +// --------------------------------------------------------------------------- +// Handle tests +// --------------------------------------------------------------------------- + +func TestHandle_BadVersion(t *testing.T) { + h := newInitdHandler(t) + + req := relay.Request{ + Version: 0x02, // not Version1 — triggers error in ReadFrom + Cmd: relay.CmdAssociate, + } + var buf bytes.Buffer + req.WriteTo(&buf) + + conn := &fakeConn{buf: buf.Bytes()} + err := h.Handle(context.Background(), conn) + if err == nil { + t.Fatal("expected error for bad version") + } + + // The response is not written when ReadFrom fails, so we only verify + // that the connection was closed (defer conn.Close in Handle). + if !conn.closed { + t.Error("connection was not closed") + } +} + +func TestHandle_UnknownCmd(t *testing.T) { + h := newInitdHandler(t) + + req := relay.Request{ + Version: relay.Version1, + Cmd: 0xFF, + } + var buf bytes.Buffer + req.WriteTo(&buf) + + conn := &fakeConn{buf: buf.Bytes()} + err := h.Handle(context.Background(), conn) + if err != ErrUnknownCmd { + t.Errorf("err = %v, want ErrUnknownCmd", err) + } + + resp := readRelayResponse(t, conn.writeBuf.Bytes()) + if resp.Status != relay.StatusBadRequest { + t.Errorf("status = %d, want BadRequest", resp.Status) + } +} + +func TestHandle_AuthSuccess(t *testing.T) { + h := newInitdHandler(t, + handler.AutherOption(&mockAuther{ + authenticateFn: func(ctx context.Context, user, pass string, opts ...auth.Option) (string, bool) { + return "client-1", true + }, + }), + ) + + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + req := relay.Request{ + Version: relay.Version1, + Cmd: relay.CmdAssociate, + Features: []relay.Feature{ + &relay.UserAuthFeature{Username: "user", Password: "pass"}, + &relay.TunnelFeature{ID: rid}, + &relay.NetworkFeature{Network: relay.NetworkIP}, + }, + } + var buf bytes.Buffer + req.WriteTo(&buf) + + conn := &fakeConn{buf: buf.Bytes()} + err := h.Handle(context.Background(), conn) + if err != nil { + t.Fatalf("Handle: %v", err) + } +} + +func TestHandle_AuthFailure(t *testing.T) { + h := newInitdHandler(t, + handler.AutherOption(&mockAuther{ + authenticateFn: func(ctx context.Context, user, pass string, opts ...auth.Option) (string, bool) { + return "", false + }, + }), + ) + + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + req := relay.Request{ + Version: relay.Version1, + Cmd: relay.CmdAssociate, + Features: []relay.Feature{ + &relay.UserAuthFeature{Username: "user", Password: "wrong"}, + &relay.TunnelFeature{ID: rid}, + &relay.NetworkFeature{Network: relay.NetworkIP}, + }, + } + var buf bytes.Buffer + req.WriteTo(&buf) + + conn := &fakeConn{buf: buf.Bytes()} + err := h.Handle(context.Background(), conn) + if err != ErrUnauthorized { + t.Errorf("err = %v, want ErrUnauthorized", err) + } + + resp := readRelayResponse(t, conn.writeBuf.Bytes()) + if resp.Status != relay.StatusUnauthorized { + t.Errorf("status = %d, want Unauthorized", resp.Status) + } +} + +func TestHandle_AssociateValid(t *testing.T) { + h := newInitdHandler(t) + h.md.ingress = nil + + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + reqData := buildRelayAssociateRequest(t, "10.0.0.1:0", rid, "ip") + + conn := &fakeConn{buf: reqData} + err := h.Handle(context.Background(), conn) + if err != nil { + t.Fatalf("Handle: %v", err) + } +} + +// --------------------------------------------------------------------------- +// Close tests +// --------------------------------------------------------------------------- + +func TestClose(t *testing.T) { + h := newInitdHandler(t) + h.Close() + // should not panic +} + +func TestClose_Double(t *testing.T) { + h := newInitdHandler(t) + h.Close() + h.Close() // should not panic +} \ No newline at end of file