Skip to content

Commit 11b68d6

Browse files
committed
test: add comprehensive URL construction edge case tests
- Trailing slash handling (baseURL with /) - Multiple query parameters - URL-encoded special characters - Empty path/query handling - Boundary conditions (nil/empty rpcReqs) - Production-like Kubernetes service URLs All tests pass ✅ - URL contract is solid.
1 parent 1527c4c commit 11b68d6

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

backend_url_edge_cases_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package proxyd
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
// TestBackendURLEdgeCases covers all the tricky slash/query edge cases
11+
func TestBackendURLEdgeCases(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
baseURL string
15+
method string
16+
path string
17+
query string
18+
expected string
19+
}{
20+
// === SLASH EDGE CASES ===
21+
{
22+
name: "base URL with trailing slash + path",
23+
baseURL: "http://backend:8080/",
24+
method: "eth_sendRawTransaction",
25+
path: "/fast",
26+
query: "",
27+
expected: "http://backend:8080/fast", // Should NOT be //fast
28+
},
29+
{
30+
name: "base URL with trailing slash + path + query",
31+
baseURL: "http://backend:8080/",
32+
method: "eth_sendRawTransaction",
33+
path: "/fast",
34+
query: "hint=hash",
35+
expected: "http://backend:8080/fast?hint=hash",
36+
},
37+
{
38+
name: "path is just slash with query",
39+
baseURL: "http://backend:8080",
40+
method: "eth_sendRawTransaction",
41+
path: "/",
42+
query: "hint=hash",
43+
expected: "http://backend:8080?hint=hash", // Should NOT add slash before ?
44+
},
45+
46+
// === QUERY STRING EDGE CASES ===
47+
{
48+
name: "multiple query parameters",
49+
baseURL: "http://backend:8080",
50+
method: "eth_sendRawTransaction",
51+
path: "/fast",
52+
query: "hint=hash&builder=builder1&origin=wallet",
53+
expected: "http://backend:8080/fast?hint=hash&builder=builder1&origin=wallet",
54+
},
55+
{
56+
name: "query with URL-encoded special chars",
57+
baseURL: "http://backend:8080",
58+
method: "eth_sendRawTransaction",
59+
path: "/fast",
60+
query: "hint=0x1234%20test&url=https%3A%2F%2Fexample.com",
61+
expected: "http://backend:8080/fast?hint=0x1234%20test&url=https%3A%2F%2Fexample.com",
62+
},
63+
{
64+
name: "query with equals in value",
65+
baseURL: "http://backend:8080",
66+
method: "eth_sendRawTransaction",
67+
path: "/fast",
68+
query: "signature=0xabc=def",
69+
expected: "http://backend:8080/fast?signature=0xabc=def",
70+
},
71+
72+
// === EMPTY/MISSING EDGE CASES ===
73+
{
74+
name: "empty path string",
75+
baseURL: "http://backend:8080",
76+
method: "eth_sendRawTransaction",
77+
path: "",
78+
query: "hint=hash",
79+
expected: "http://backend:8080?hint=hash", // Empty path should be treated like "/"
80+
},
81+
{
82+
name: "empty query string",
83+
baseURL: "http://backend:8080",
84+
method: "eth_sendRawTransaction",
85+
path: "/fast",
86+
query: "",
87+
expected: "http://backend:8080/fast", // Should NOT add ? for empty query
88+
},
89+
{
90+
name: "both path and query empty",
91+
baseURL: "http://backend:8080",
92+
method: "eth_sendRawTransaction",
93+
path: "",
94+
query: "",
95+
expected: "http://backend:8080",
96+
},
97+
98+
// === METHOD FILTERING ===
99+
{
100+
name: "non-eth_sendRawTransaction ignores everything",
101+
baseURL: "http://backend:8080",
102+
method: "eth_call",
103+
path: "/fast",
104+
query: "hint=hash&builder=builder1",
105+
expected: "http://backend:8080", // Should completely ignore path and query
106+
},
107+
108+
// === PRODUCTION-LIKE SCENARIOS ===
109+
{
110+
name: "real production URL with everything",
111+
baseURL: "http://rpc-endpoint.flashbots.svc.cluster.local:8080",
112+
method: "eth_sendRawTransaction",
113+
path: "/fast",
114+
query: "hint=0xabcdef1234567890&builder=flashbots&origin=metamask",
115+
expected: "http://rpc-endpoint.flashbots.svc.cluster.local:8080/fast?hint=0xabcdef1234567890&builder=flashbots&origin=metamask",
116+
},
117+
}
118+
119+
for _, tt := range tests {
120+
t.Run(tt.name, func(t *testing.T) {
121+
ctx := context.Background()
122+
ctx = context.WithValue(ctx, ContextKeyPath, tt.path)
123+
ctx = context.WithValue(ctx, ContextKeyRawQuery, tt.query)
124+
125+
rpcReqs := []*RPCReq{{Method: tt.method}}
126+
backendURL := buildBackendURL(tt.baseURL, rpcReqs, ctx)
127+
128+
assert.Equal(t, tt.expected, backendURL, "URL mismatch for case: %s", tt.name)
129+
})
130+
}
131+
}
132+
133+
// TestBackendURLBoundaryConditions tests weird inputs that shouldn't happen but might
134+
func TestBackendURLBoundaryConditions(t *testing.T) {
135+
tests := []struct {
136+
name string
137+
baseURL string
138+
rpcReqs []*RPCReq
139+
path string
140+
query string
141+
expected string
142+
}{
143+
{
144+
name: "empty rpcReqs array",
145+
baseURL: "http://backend:8080",
146+
rpcReqs: []*RPCReq{},
147+
path: "/fast",
148+
query: "hint=hash",
149+
expected: "http://backend:8080", // Should not panic, just return base
150+
},
151+
{
152+
name: "nil rpcReqs array",
153+
baseURL: "http://backend:8080",
154+
rpcReqs: nil,
155+
path: "/fast",
156+
query: "hint=hash",
157+
expected: "http://backend:8080",
158+
},
159+
}
160+
161+
for _, tt := range tests {
162+
t.Run(tt.name, func(t *testing.T) {
163+
ctx := context.Background()
164+
ctx = context.WithValue(ctx, ContextKeyPath, tt.path)
165+
ctx = context.WithValue(ctx, ContextKeyRawQuery, tt.query)
166+
167+
// Should not panic
168+
backendURL := buildBackendURL(tt.baseURL, tt.rpcReqs, ctx)
169+
assert.Equal(t, tt.expected, backendURL)
170+
})
171+
}
172+
}

0 commit comments

Comments
 (0)