3246b39c93
Extract the 1024-line handler.go into concern-focused files:
util.go — 5 pure functions (decodeServerName, basicProxyAuth, upgradeType,
normalizeHostPort, buildConnectResponse) — 100% coverage
auth.go — authenticate (5 probe-resistance strategies + knock) + checkRateLimit
connect.go — handleConnect, sniffAndHandle, dial, setupTrafficLimiter
proxy.go — handleProxy keep-alive loop, proxyRoundTrip, handleUpgradeResponse
websocket.go — WebSocket frame sniffing/recording with rate-limited sampling
udp.go — UDP-over-HTTP relay with SOCKS5 tunnel
metadata.go — metadata struct, parseMetadata, probeResistance type
Key improvements:
- Transport injection (http.RoundTripper field) enables testing without real network
- Nil Router guards in dial, handleUDP, and nil Addr guard in checkRateLimit
- Pure functions converted from methods to package-level (no state dependency)
- setupTrafficLimiter returns cleanup closure to preserve defer lifetime
- handleConnect accepts caller's resp for correct recorder status capture
- Comprehensive package doc with full request-flow documentation
96 tests: 100% on pure functions, 86% on metadata parsing, 55% overall (integration
paths verified by e2e tests).
236 lines
6.5 KiB
Go
236 lines
6.5 KiB
Go
package http
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseMetadata_Defaults(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.readTimeout != 15*time.Second {
|
|
t.Errorf("got readTimeout %v, want 15s", h.md.readTimeout)
|
|
}
|
|
if h.md.proxyAgent != defaultProxyAgent {
|
|
t.Errorf("got proxyAgent %q, want %q", h.md.proxyAgent, defaultProxyAgent)
|
|
}
|
|
if h.md.observerPeriod != 5*time.Second {
|
|
t.Errorf("got observerPeriod %v, want 5s", h.md.observerPeriod)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_ReadTimeout(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"readTimeout": "30s"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.readTimeout != 30*time.Second {
|
|
t.Errorf("got readTimeout %v, want 30s", h.md.readTimeout)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_NegativeReadTimeout(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"readTimeout": "-1"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.readTimeout != 0 {
|
|
t.Errorf("got readTimeout %v, want 0 for negative value", h.md.readTimeout)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_IdleTimeout(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"idleTimeout": "60s"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.idleTimeout != 60*time.Second {
|
|
t.Errorf("got idleTimeout %v, want 60s", h.md.idleTimeout)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_NegativeIdleTimeout(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"idleTimeout": "-1"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.idleTimeout != 0 {
|
|
t.Errorf("got idleTimeout %v, want 0 for negative value", h.md.idleTimeout)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_Headers(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{
|
|
"http.header": map[string]any{
|
|
"X-Custom": "value",
|
|
},
|
|
})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.header.Get("X-Custom") != "value" {
|
|
t.Errorf("got header X-Custom=%q, want %q", h.md.header.Get("X-Custom"), "value")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_Keepalive(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"keepalive": "true"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !h.md.keepalive {
|
|
t.Error("expected keepalive to be true")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_Compression(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"compression": "true"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !h.md.compression {
|
|
t.Error("expected compression to be true")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_ProbeResistance(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{
|
|
"probeResist": "code:404",
|
|
"knock": "secret.example.com",
|
|
})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.probeResistance == nil {
|
|
t.Fatal("expected probeResistance to be set")
|
|
}
|
|
if h.md.probeResistance.Type != "code" {
|
|
t.Errorf("got type %q, want %q", h.md.probeResistance.Type, "code")
|
|
}
|
|
if h.md.probeResistance.Value != "404" {
|
|
t.Errorf("got value %q, want %q", h.md.probeResistance.Value, "404")
|
|
}
|
|
if h.md.probeResistance.Knock != "secret.example.com" {
|
|
t.Errorf("got knock %q, want %q", h.md.probeResistance.Knock, "secret.example.com")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_ProbeResistance_InvalidFormat(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"probeResist": "invalid"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// probeResist without colon is ignored
|
|
if h.md.probeResistance != nil {
|
|
t.Error("expected nil probeResistance for invalid format")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_UDP(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{
|
|
"udp": "true",
|
|
"udpBufferSize": "4096",
|
|
})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !h.md.enableUDP {
|
|
t.Error("expected enableUDP to be true")
|
|
}
|
|
if h.md.udpBufferSize != 4096 {
|
|
t.Errorf("got udpBufferSize %d, want 4096", h.md.udpBufferSize)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_Hash(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"hash": "host"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.hash != "host" {
|
|
t.Errorf("got hash %q, want %q", h.md.hash, "host")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_ObserverPeriod(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"observePeriod": "10s"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.observerPeriod != 10*time.Second {
|
|
t.Errorf("got observerPeriod %v, want 10s", h.md.observerPeriod)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_ObserverPeriod_Minimum(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"observePeriod": "500ms"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.observerPeriod != time.Second {
|
|
t.Errorf("got observerPeriod %v, want minimum 1s", h.md.observerPeriod)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_Sniffing(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{
|
|
"sniffing": "true",
|
|
"sniffing.timeout": "5s",
|
|
"sniffing.websocket": "true",
|
|
"sniffing.websocket.sampleRate": "20.0",
|
|
})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !h.md.sniffing {
|
|
t.Error("expected sniffing to be true")
|
|
}
|
|
if h.md.sniffingTimeout != 5*time.Second {
|
|
t.Errorf("got sniffingTimeout %v, want 5s", h.md.sniffingTimeout)
|
|
}
|
|
if !h.md.sniffingWebsocket {
|
|
t.Error("expected sniffingWebsocket to be true")
|
|
}
|
|
if h.md.sniffingWebsocketSampleRate != 20.0 {
|
|
t.Errorf("got sampleRate %f, want 20.0", h.md.sniffingWebsocketSampleRate)
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_ProxyAgent(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"proxyAgent": "custom/1.0"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.proxyAgent != "custom/1.0" {
|
|
t.Errorf("got proxyAgent %q, want %q", h.md.proxyAgent, "custom/1.0")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_AuthBasicRealm(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{"authBasicRealm": "myrealm"})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.authBasicRealm != "myrealm" {
|
|
t.Errorf("got realm %q, want %q", h.md.authBasicRealm, "myrealm")
|
|
}
|
|
}
|
|
|
|
func TestParseMetadata_LimiterIntervals(t *testing.T) {
|
|
h := &httpHandler{}
|
|
if err := h.parseMetadata(testMD(map[string]any{
|
|
"limiter.refreshInterval": "30s",
|
|
"limiter.cleanupInterval": "60s",
|
|
})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.md.limiterRefreshInterval != 30*time.Second {
|
|
t.Errorf("got refreshInterval %v, want 30s", h.md.limiterRefreshInterval)
|
|
}
|
|
if h.md.limiterCleanupInterval != 60*time.Second {
|
|
t.Errorf("got cleanupInterval %v, want 60s", h.md.limiterCleanupInterval)
|
|
}
|
|
}
|