d432d28f81
Extract checkRateLimit and observeStats into their own file (observe.go). Fix the one-tick event-loss bug in observeStats where the retry path used 'unconditional break' instead of fallthrough, causing events collected after a successful retry to be discarded. Add 87 unit tests across 7 files (helpers, metadata, conn, handler, connect, forward, bind) covering all handler modes, error paths, observer/stats integration, and conn wrapper edge cases. handler.go: -43 lines (unused observer import, extracted functions) observe.go: +52 lines (checkRateLimit, observeStats with fallthrough fix) test files: +1959 lines
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package relay
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-gost/core/handler"
|
|
"github.com/go-gost/relay"
|
|
)
|
|
|
|
// handleBind tests use a real TCP listener on port 0 to verify the bind flow.
|
|
// The Handle method is called with a CmdBind request, and the response is
|
|
// checked for StatusOK + an AddrFeature.
|
|
|
|
func TestHandleBind_Disabled(t *testing.T) {
|
|
rh := newInitdHandler(t,
|
|
handler.LoggerOption(&testLogger{}),
|
|
)
|
|
// bind is disabled by default
|
|
|
|
fc := &fakeConn{buf: buildRelayBindRequest(t, "127.0.0.1:0", "")}
|
|
err := rh.Handle(context.Background(), fc)
|
|
// handleBind returns the error from resp.WriteTo(conn) — when the write
|
|
// succeeds the returned error is nil. The response status indicates the
|
|
// denial, not the returned error.
|
|
_ = err
|
|
|
|
resp := readRelayResponse(t, fc.writeBuf.Bytes())
|
|
if resp.Status != relay.StatusForbidden {
|
|
t.Errorf("Status = %d, want %d", resp.Status, relay.StatusForbidden)
|
|
}
|
|
}
|
|
|
|
func TestHandleBind_TCP(t *testing.T) {
|
|
rh := &relayHandler{
|
|
options: handler.Options{
|
|
Logger: &testLogger{},
|
|
},
|
|
}
|
|
rh.parseMetadata(testMD(map[string]any{"bind": true}))
|
|
|
|
fc := &fakeConn{buf: buildRelayBindRequest(t, "127.0.0.1:0", "")}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
errCh <- rh.Handle(ctx, fc)
|
|
}()
|
|
|
|
// Wait for the response (bindTCP writes the response before blocking on Serve)
|
|
// bindTCP calls resp.WriteTo(conn) then upgrades to mux session.
|
|
// Since the fakeConn doesn't support mux (it's not a real mux-compatible conn),
|
|
// the mux.ClientSession call will fail.
|
|
// But we should at least get the response before that.
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
// bindTCP will fail when mux.ClientSession tries to upgrade the fakeConn
|
|
// That's expected — the response was written before that
|
|
_ = err
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("timed out waiting for bind handler")
|
|
}
|
|
|
|
// Check the response — should have StatusOK and an AddrFeature
|
|
resp := readRelayResponse(t, fc.writeBuf.Bytes())
|
|
if resp.Status != relay.StatusOK {
|
|
t.Errorf("Status = %d, want %d", resp.Status, relay.StatusOK)
|
|
}
|
|
|
|
// Verify response has at least one feature
|
|
if len(resp.Features) == 0 {
|
|
t.Error("response has no features, expected AddrFeature")
|
|
}
|
|
}
|
|
|
|
func TestHandleBind_UDP(t *testing.T) {
|
|
rh := &relayHandler{
|
|
options: handler.Options{
|
|
Logger: &testLogger{},
|
|
},
|
|
}
|
|
rh.parseMetadata(testMD(map[string]any{"bind": true}))
|
|
|
|
fc := &fakeConn{buf: buildRelayBindRequest(t, "127.0.0.1:0", "udp")}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
errCh <- rh.Handle(ctx, fc)
|
|
}()
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
_ = err
|
|
}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("timed out waiting for bind handler")
|
|
}
|
|
|
|
resp := readRelayResponse(t, fc.writeBuf.Bytes())
|
|
if resp.Status != relay.StatusOK {
|
|
t.Errorf("Status = %d, want %d", resp.Status, relay.StatusOK)
|
|
}
|
|
} |