From 32056092f50681b310c1ee4fdf3e39137b70d6d5 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Wed, 3 Jun 2026 23:42:19 +0800 Subject: [PATCH] test(handler/router): add router_test.go (Connector, Router, ConnectorPool, parseRouterID) --- handler/router/router_test.go | 491 ++++++++++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 handler/router/router_test.go diff --git a/handler/router/router_test.go b/handler/router/router_test.go new file mode 100644 index 00000000..1baf7759 --- /dev/null +++ b/handler/router/router_test.go @@ -0,0 +1,491 @@ +package router + +import ( + "bytes" + "io" + "testing" + + "github.com/go-gost/core/logger" + "github.com/go-gost/relay" + "github.com/google/uuid" +) + +// init sets up the default logger to prevent nil panics in NewConnector. +func init() { + logger.SetDefault(&testLogger{}) +} + +// --------------------------------------------------------------------------- +// CloseWriter — helper that tracks close calls +// --------------------------------------------------------------------------- + +type closeWriter struct { + closeFn func() error +} + +func (w *closeWriter) Write(p []byte) (int, error) { return len(p), nil } +func (w *closeWriter) Close() error { + if w.closeFn != nil { + return w.closeFn() + } + return nil +} + +// --------------------------------------------------------------------------- +// Connector tests +// --------------------------------------------------------------------------- + +func TestNewConnector_Minimal(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + c := NewConnector(rid, cid, "example.com", nil, nil) + if c == nil { + t.Fatal("connector is nil") + } + if !c.ID().Equal(cid) { + t.Errorf("ID = %v, want %v", c.ID(), cid) + } + if w := c.Writer(); w != nil { + t.Errorf("Writer = %v, want nil", w) + } +} + +func TestNewConnector_WithOpts(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + var buf bytes.Buffer + c := NewConnector(rid, cid, "example.com", &buf, &ConnectorOptions{}) + if c == nil { + t.Fatal("connector is nil") + } + n, err := c.Writer().Write([]byte("hello")) + if err != nil { + t.Fatalf("write: %v", err) + } + if n != 5 { + t.Errorf("n = %d, want 5", n) + } + if buf.String() != "hello" { + t.Errorf("buf = %q, want hello", buf.String()) + } +} + +func TestConnector_Close_NilWriter(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + c := NewConnector(rid, cid, "example.com", nil, nil) + if err := c.Close(); err != nil { + t.Fatalf("Close: %v", err) + } +} + +func TestConnector_Close_Writer(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + closed := false + c := NewConnector(rid, cid, "example.com", &closeWriter{ + closeFn: func() error { closed = true; return nil }, + }, nil) + if err := c.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + if !closed { + t.Error("writer was not closed") + } +} + +func TestConnector_Close_Double(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + c := NewConnector(rid, cid, "example.com", &closeWriter{}, nil) + c.Close() + c.Close() // second close should be safe +} + +func TestConnector_Writer_Nil(t *testing.T) { + var c *Connector + if w := c.Writer(); w != nil { + t.Errorf("nil connector Writer = %v, want nil", w) + } +} + +func TestConnector_Close_Nil(t *testing.T) { + var c *Connector + if err := c.Close(); err != nil { + t.Fatalf("nil connector Close: %v", err) + } +} + +func TestConnector_Close_NonCloser(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + // bytes.Buffer does not implement io.Closer + var buf bytes.Buffer + c := NewConnector(rid, cid, "example.com", &buf, nil) + if err := c.Close(); err != nil { + t.Fatalf("Close: %v", err) + } +} + +// xcloseWriter implements io.Closer but times out +type xcloseWriter struct { + data []byte +} + +func (w *xcloseWriter) Write(p []byte) (int, error) { + w.data = append(w.data, p...) + return len(p), nil +} +func (w *xcloseWriter) Close() error { return io.ErrClosedPipe } + +func TestConnector_Close_CloserError(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + c := NewConnector(rid, cid, "example.com", &xcloseWriter{}, nil) + if err := c.Close(); err != io.ErrClosedPipe { + t.Errorf("Close = %v, want ErrClosedPipe", err) + } +} + +// --------------------------------------------------------------------------- +// Router tests +// --------------------------------------------------------------------------- + +func TestRouter_AddConnector_Nil(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + r.AddConnector(nil) // should not panic +} + +func TestRouter_AddGetConnector_Single(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + if !r.ID().Equal(rid) { + t.Errorf("ID = %v, want %v", r.ID(), rid) + } + + cid := relay.NewConnectorID([]byte("abcdef0123456789")) + c := NewConnector(rid, cid, "host1", nil, nil) + r.AddConnector(c) + + got := r.GetConnector("host1") + if got == nil { + t.Fatal("GetConnector returned nil") + } + if !got.ID().Equal(cid) { + t.Errorf("ID = %v, want %v", got.ID(), cid) + } +} + +func TestRouter_AddGetConnector_MultipleHosts(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + cid1 := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + cid2 := relay.NewConnectorID([]byte("bbbbbbbbbbbbbbbb")) + r.AddConnector(NewConnector(rid, cid1, "host1", nil, nil)) + r.AddConnector(NewConnector(rid, cid2, "host2", nil, nil)) + + got1 := r.GetConnector("host1") + if got1 == nil || !got1.ID().Equal(cid1) { + t.Errorf("host1 got ID = %v, want %v", got1, cid1) + } + got2 := r.GetConnector("host2") + if got2 == nil || !got2.ID().Equal(cid2) { + t.Errorf("host2 got ID = %v, want %v", got2, cid2) + } +} + +func TestRouter_GetConnector_EmptyHost(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + if got := r.GetConnector(""); got != nil { + t.Errorf("GetConnector('') = %v, want nil", got) + } +} + +func TestRouter_GetConnector_MissingHost(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + if got := r.GetConnector("nonexistent"); got != nil { + t.Errorf("GetConnector('nonexistent') = %v, want nil", got) + } +} + +func TestRouter_GetConnector_Weighted(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + cid1 := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + cid2 := relay.NewConnectorID([]byte("bbbbbbbbbbbbbbbb")) + // Weight 0 should be treated as 1 + r.AddConnector(NewConnector(rid, cid1, "host1", nil, nil)) + r.AddConnector(NewConnector(rid, cid2, "host1", nil, nil)) + + // With multiple connectors weighted selection should return non-nil + got := r.GetConnector("host1") + if got == nil { + t.Fatal("GetConnector returned nil for weighted selection") + } +} + +func TestRouter_DelConnector(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + cid1 := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + cid2 := relay.NewConnectorID([]byte("bbbbbbbbbbbbbbbb")) + r.AddConnector(NewConnector(rid, cid1, "host1", nil, nil)) + r.AddConnector(NewConnector(rid, cid2, "host1", nil, nil)) + + r.DelConnector("host1", cid1) + got := r.GetConnector("host1") + if got == nil { + t.Fatal("GetConnector returned nil after delete") + } + if !got.ID().Equal(cid2) { + t.Errorf("remaining connector = %v, want %v", got.ID(), cid2) + } +} + +func TestRouter_DelConnector_MultipleRemaining(t *testing.T) { + // Regression test: deleting one of three should leave two intact + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + ids := make([]relay.ConnectorID, 3) + for i := range ids { + uid := uuid.New() + ids[i] = relay.NewConnectorID(uid[:]) + r.AddConnector(NewConnector(rid, ids[i], "host1", nil, nil)) + } + + r.DelConnector("host1", ids[0]) + got := r.GetConnector("host1") + if got == nil { + t.Fatal("GetConnector returned nil after deleting one of three") + } +} + +func TestRouter_DelConnector_NoMatch(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + cid1 := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + cid2 := relay.NewConnectorID([]byte("cccccccccccccccc")) + r.AddConnector(NewConnector(rid, cid1, "host1", nil, nil)) + + // Delete with non-existent cid — should be no-op + r.DelConnector("host1", cid2) + got := r.GetConnector("host1") + if got == nil { + t.Fatal("GetConnector returned nil after deleting non-existent cid") + } + if !got.ID().Equal(cid1) { + t.Errorf("remaining connector = %v, want %v", got.ID(), cid1) + } +} + +func TestRouter_DelConnector_WrongHost(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + cid1 := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + r.AddConnector(NewConnector(rid, cid1, "host1", nil, nil)) + + // Delete with correct cid but wrong host — should be no-op + r.DelConnector("other-host", cid1) + got := r.GetConnector("host1") + if got == nil { + t.Fatal("GetConnector returned nil after delete with wrong host") + } +} + +func TestRouter_Close(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + r.AddConnector(NewConnector(rid, relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")), "host1", nil, nil)) + + if err := r.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + if got := r.GetConnector("host1"); got != nil { + t.Error("GetConnector returned non-nil after Close") + } +} + +func TestRouter_Close_Double(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + r.Close() + r.Close() // second close should not panic +} + +func TestRouter_Close_ClosesConnectors(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + + closed := false + c := NewConnector(rid, relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")), "host1", &closeWriter{ + closeFn: func() error { closed = true; return nil }, + }, nil) + r.AddConnector(c) + + r.Close() + if !closed { + t.Error("connector was not closed by Router.Close") + } +} + +func TestRouter_ID(t *testing.T) { + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + r := NewRouter("node1", rid) + if !r.ID().Equal(rid) { + t.Errorf("ID = %v, want %v", r.ID(), rid) + } +} + +// --------------------------------------------------------------------------- +// ConnectorPool tests +// --------------------------------------------------------------------------- + +func TestConnectorPool_NilSafe_Get(t *testing.T) { + var p *ConnectorPool + if got := p.Get(relay.TunnelID{}, "host"); got != nil { + t.Errorf("nil pool Get = %v, want nil", got) + } +} + +func TestConnectorPool_NilSafe_Del(t *testing.T) { + var p *ConnectorPool + p.Del(relay.TunnelID{}, "host", relay.ConnectorID{}) // should not panic +} + +func TestConnectorPool_NilSafe_Close(t *testing.T) { + var p *ConnectorPool + if err := p.Close(); err != nil { + t.Fatalf("nil pool Close: %v", err) + } +} + +func TestConnectorPool_AddGet(t *testing.T) { + p := NewConnectorPool("node1") + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + + c := NewConnector(rid, cid, "host1", nil, nil) + p.Add(rid, c) + + got := p.Get(rid, "host1") + if got == nil { + t.Fatal("Get returned nil") + } + if !got.ID().Equal(cid) { + t.Errorf("ID = %v, want %v", got.ID(), cid) + } +} + +func TestConnectorPool_AddGet_NewRouter(t *testing.T) { + p := NewConnectorPool("node1") + rid1 := relay.NewTunnelID([]byte("aaaaaaaaaaaaaaaa")) + rid2 := relay.NewTunnelID([]byte("bbbbbbbbbbbbbbbb")) + + p.Add(rid1, NewConnector(rid1, relay.NewConnectorID([]byte("1111111111111111")), "host1", nil, nil)) + p.Add(rid2, NewConnector(rid2, relay.NewConnectorID([]byte("2222222222222222")), "host1", nil, nil)) + + got1 := p.Get(rid1, "host1") + got2 := p.Get(rid2, "host1") + if got1 == nil || got2 == nil { + t.Fatal("different routers should both return connectors") + } +} + +func TestConnectorPool_Get_MissingRouter(t *testing.T) { + p := NewConnectorPool("node1") + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + otherRid := relay.NewTunnelID([]byte("ffffffffffffffff")) + + p.Add(rid, NewConnector(rid, relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")), "host1", nil, nil)) + + if got := p.Get(otherRid, "host1"); got != nil { + t.Error("Get with unknown rid returned non-nil") + } +} + +func TestConnectorPool_Get_MissingHost(t *testing.T) { + p := NewConnectorPool("node1") + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + + p.Add(rid, NewConnector(rid, relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")), "host1", nil, nil)) + + if got := p.Get(rid, "other-host"); got != nil { + t.Error("Get with unknown host returned non-nil") + } +} + +func TestConnectorPool_Del(t *testing.T) { + p := NewConnectorPool("node1") + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + cid1 := relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")) + cid2 := relay.NewConnectorID([]byte("bbbbbbbbbbbbbbbb")) + + p.Add(rid, NewConnector(rid, cid1, "host1", nil, nil)) + p.Add(rid, NewConnector(rid, cid2, "host1", nil, nil)) + + p.Del(rid, "host1", cid1) + got := p.Get(rid, "host1") + if got == nil || !got.ID().Equal(cid2) { + t.Errorf("after Del got ID = %v, want %v", got, cid2) + } +} + +func TestConnectorPool_Del_UnknownRouter(t *testing.T) { + p := NewConnectorPool("node1") + p.Del(relay.NewTunnelID([]byte("aaaaaaaaaaaaaaaa")), "host1", relay.ConnectorID{}) // no-op, should not panic +} + +func TestConnectorPool_Close(t *testing.T) { + p := NewConnectorPool("node1") + rid := relay.NewTunnelID([]byte("0123456789abcdef")) + p.Add(rid, NewConnector(rid, relay.NewConnectorID([]byte("aaaaaaaaaaaaaaaa")), "host1", nil, nil)) + + if err := p.Close(); err != nil { + t.Fatalf("Close: %v", err) + } + if got := p.Get(rid, "host1"); got != nil { + t.Error("Get returned non-nil after Close") + } +} + +func TestConnectorPool_Close_Double(t *testing.T) { + p := NewConnectorPool("node1") + p.Close() + p.Close() // should not panic +} + +// --------------------------------------------------------------------------- +// parseRouterID tests +// --------------------------------------------------------------------------- + +func TestParseRouterID_Empty(t *testing.T) { + rid := parseRouterID("") + if !rid.Equal(relay.TunnelID{}) { + t.Errorf("parseRouterID('') = %v, want zero value", rid) + } +} + +func TestParseRouterID_ValidUUID(t *testing.T) { + uid := uuid.New().String() + rid := parseRouterID(uid) + if rid.Equal(relay.TunnelID{}) { + t.Error("parseRouterID(valid) returned zero value") + } +} + +func TestParseRouterID_Invalid(t *testing.T) { + rid := parseRouterID("not-a-uuid") + // Invalid UUID returns zero value (no error returned from function) + if !rid.Equal(relay.TunnelID{}) { + t.Errorf("parseRouterID('invalid') = %v, want zero value", rid) + } +} \ No newline at end of file