Files
x/handler/relay/conn_test.go
T
ginuerzh d432d28f81 refactor(handler/relay): split handler.go -> observe.go, fix observeStats event-loss bug (break->fallthrough)
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
2026-06-02 23:51:19 +08:00

229 lines
5.4 KiB
Go

package relay
import (
"bytes"
"io"
"testing"
)
func TestTCPConn_WriteWithCachedHeader(t *testing.T) {
var underlying bytes.Buffer
tc := &tcpConn{
Conn: &fakeConn{}, // embedded net.Conn — just for Read
}
// Simulate a cached header
tc.wbuf.Write([]byte("HEADER "))
n, err := tc.Write([]byte("data"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 4 {
t.Errorf("n = %d, want 4", n)
}
// The wbuf was flushed: header + data
combined := tc.wbuf.Bytes()
if len(combined) != 0 {
t.Errorf("wbuf should be empty after flush, got %d bytes", len(combined))
}
// tcpConn has no underlying writeBuf — writes go to the embedded conn
_ = underlying
_ = tc.Conn
}
func TestTCPConn_WriteWithoutCachedHeader(t *testing.T) {
// Nothing special to test — writes delegate to Conn.Write
// Just verify no panic and returns correctly
tc := &tcpConn{Conn: &fakeConn{}}
n, err := tc.Write([]byte("data"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 4 {
t.Errorf("n = %d, want 4", n)
}
}
func TestTCPConn_ReadDelegates(t *testing.T) {
buf := []byte("hello world")
fc := &fakeConn{buf: buf}
tc := &tcpConn{Conn: fc}
out := make([]byte, 5)
n, err := tc.Read(out)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 5 || string(out[:n]) != "hello" {
t.Errorf("Read = %q, want %q", string(out[:n]), "hello")
}
}
func TestUDPConn_ReadWithLengthPrefix(t *testing.T) {
// udpConn.Read expects [2-byte length][data]
payload := []byte("hello")
prefixed := make([]byte, 2+len(payload))
prefixed[0] = 0
prefixed[1] = byte(len(payload))
copy(prefixed[2:], payload)
fc := &fakeConn{buf: prefixed}
uc := &udpConn{Conn: fc}
out := make([]byte, 10)
n, err := uc.Read(out)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 5 {
t.Errorf("n = %d, want 5", n)
}
if string(out[:n]) != "hello" {
t.Errorf("Read = %q, want %q", string(out[:n]), "hello")
}
}
func TestUDPConn_ReadTruncated(t *testing.T) {
// When the read buffer is smaller than the UDP payload
payload := []byte("hello world")
prefixed := make([]byte, 2+len(payload))
prefixed[0] = 0
prefixed[1] = byte(len(payload))
copy(prefixed[2:], payload)
fc := &fakeConn{buf: prefixed}
uc := &udpConn{Conn: fc}
// Read with a small buffer
out := make([]byte, 5)
n, err := uc.Read(out)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 5 {
t.Errorf("n = %d, want 5", n)
}
if string(out[:n]) != "hello" {
t.Errorf("Read = %q, want %q", string(out[:n]), "hello")
}
}
func TestUDPConn_WriteWithCachedHeader(t *testing.T) {
fc := &fakeConn{}
uc := &udpConn{Conn: fc}
// Simulate a cached header (e.g., the relay response)
uc.wbuf.Write([]byte("HEADER "))
n, err := uc.Write([]byte("hi"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 2 {
t.Errorf("n = %d, want 2", n)
}
// Verify the wbuf was flushed
if uc.wbuf.Len() > 0 {
t.Errorf("wbuf should be empty after flush, got %d bytes", uc.wbuf.Len())
}
// The header + [2-byte length] + "hi" should be written to fc
written := fc.writeBuf.Bytes()
if len(written) == 0 {
t.Fatal("nothing written to underlying conn")
}
// Should contain: "HEADER " + length prefix + "hi"
expected := []byte("HEADER ")
expected = append(expected, 0x00, 0x02) // length prefix
expected = append(expected, 'h', 'i')
if !bytes.Equal(written, expected) {
t.Errorf("written = %v, want %v", written, expected)
}
}
func TestUDPConn_WriteWithoutCachedHeader(t *testing.T) {
fc := &fakeConn{}
uc := &udpConn{Conn: fc}
n, err := uc.Write([]byte("hi"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 2 {
t.Errorf("n = %d, want 2", n)
}
// Should write [2-byte length] + "hi"
written := fc.writeBuf.Bytes()
if len(written) < 2 {
t.Fatal("nothing written to underlying conn")
}
if written[0] != 0 || written[1] != 2 {
t.Errorf("length prefix = %v, want [0, 2]", written[:2])
}
if string(written[2:]) != "hi" {
t.Errorf("data = %q, want %q", string(written[2:]), "hi")
}
}
func TestUDPConn_WriteExceedsMaxLength(t *testing.T) {
fc := &fakeConn{}
uc := &udpConn{Conn: fc}
// Write more than MaxUint16 bytes
data := make([]byte, 65536)
_, err := uc.Write(data)
if err == nil {
t.Error("expected error for data > 65535 bytes")
}
}
func TestTCPConn_WriteWithCachedHeaderAndFlush(t *testing.T) {
fc := &fakeConn{}
tc := &tcpConn{Conn: fc}
tc.wbuf.Write([]byte("RESPONSE "))
n, err := tc.Write([]byte("body"))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if n != 4 {
t.Errorf("n = %d, want 4", n)
}
// wbuf should be empty after flush
if tc.wbuf.Len() > 0 {
t.Errorf("wbuf should be empty, got %d bytes", tc.wbuf.Len())
}
// The conn should have: "RESPONSE body"
written := fc.writeBuf.Bytes()
if string(written) != "RESPONSE body" {
t.Errorf("written = %q, want %q", string(written), "RESPONSE body")
}
}
func TestUDPConn_ReadEmpty(t *testing.T) {
// Read on an empty connection should return io.EOF
fc := &fakeConn{} // no buf
uc := &udpConn{Conn: fc}
_, err := uc.Read(make([]byte, 10))
if err != io.EOF && err != io.ErrUnexpectedEOF {
t.Errorf("expected EOF, got %v", err)
}
}
func TestUDPConn_ReadPartial(t *testing.T) {
// Length says 10 bytes but only 3 are available
prefixed := []byte{0x00, 0x0A, 'a', 'b', 'c'}
fc := &fakeConn{buf: prefixed}
uc := &udpConn{Conn: fc}
_, err := uc.Read(make([]byte, 10))
if err == nil {
t.Error("expected error for truncated data")
}
}