@@ -59,7 +59,7 @@ func TestServeHTTP_InvalidMultipart_Returns400(t *testing.T) {
5959 h := newTestHandler (t , defaultCfg (), nil )
6060
6161 // Boundary declared in header but body has no valid multipart parts → EOF.
62- req := httptest .NewRequest ( http .MethodPost , "/" , strings .NewReader ("" ))
62+ req := httptest .NewRequestWithContext ( t . Context (), http .MethodPost , "/" , strings .NewReader ("" ))
6363 req .Header .Set ("Content-Type" , "multipart/form-data; boundary=1111" )
6464
6565 rr := httptest .NewRecorder ()
@@ -73,7 +73,7 @@ func TestServeHTTP_InvalidMultipart_Returns400(t *testing.T) {
7373func TestServeHTTP_StreamBody_MaxBytesExceeded_Returns413 (t * testing.T ) {
7474 h := newTestHandler (t , defaultCfg (), nil )
7575
76- req := httptest .NewRequest ( http .MethodPost , "/" , strings .NewReader ("this body is too long" ))
76+ req := httptest .NewRequestWithContext ( t . Context (), http .MethodPost , "/" , strings .NewReader ("this body is too long" ))
7777 req .Header .Set ("Content-Type" , "application/json" )
7878
7979 rr := httptest .NewRecorder ()
@@ -92,7 +92,7 @@ func TestServeHTTP_TruncatedMultipart_Returns400(t *testing.T) {
9292
9393 // Multipart body with an open part but no closing boundary → ErrUnexpectedEOF.
9494 body := "--1111\r \n Content-Disposition: form-data; name=\" f\" \r \n \r \n val"
95- req := httptest .NewRequest ( http .MethodPost , "/" , strings .NewReader (body ))
95+ req := httptest .NewRequestWithContext ( t . Context (), http .MethodPost , "/" , strings .NewReader (body ))
9696 req .Header .Set ("Content-Type" , "multipart/form-data; boundary=1111" )
9797
9898 rr := httptest .NewRecorder ()
@@ -150,7 +150,7 @@ func TestHandleError_DebugMode_WritesEscapedError(t *testing.T) {
150150}
151151
152152func TestURI_PlainHTTP (t * testing.T ) {
153- r := httptest .NewRequest ( http .MethodGet , "/path?q=1" , nil )
153+ r := httptest .NewRequestWithContext ( t . Context (), http .MethodGet , "/path?q=1" , nil )
154154 r .Host = "example.com"
155155
156156 got := URI (r )
@@ -161,7 +161,7 @@ func TestURI_PlainHTTP(t *testing.T) {
161161}
162162
163163func TestURI_TLSRequest_HTTPSScheme (t * testing.T ) {
164- r := httptest .NewRequest ( http .MethodGet , "/path?q=1" , nil )
164+ r := httptest .NewRequestWithContext ( t . Context (), http .MethodGet , "/path?q=1" , nil )
165165 r .Host = "example.com"
166166 r .TLS = & tls.ConnectionState {}
167167
@@ -173,7 +173,7 @@ func TestURI_TLSRequest_HTTPSScheme(t *testing.T) {
173173}
174174
175175func TestURI_StripsCRLFInjection (t * testing.T ) {
176- r := httptest .NewRequest ( http .MethodGet , "/path" , nil )
176+ r := httptest .NewRequestWithContext ( t . Context (), http .MethodGet , "/path" , nil )
177177 r .Host = "example.com"
178178 // Inject CRLF into the raw query — a classic HTTP response-splitting vector.
179179 r .URL .RawQuery = "param=value\r \n X-Injected: true"
@@ -206,7 +206,7 @@ func TestServeHTTP_PoolExecError_Returns500(t *testing.T) {
206206 mp := & mockPool {execErr : fmt .Errorf ("worker died" )}
207207 h := newTestHandler (t , defaultCfg (), mp )
208208
209- req := httptest .NewRequest ( http .MethodPost , "/" , strings .NewReader (`{"key":"val"}` ))
209+ req := httptest .NewRequestWithContext ( t . Context (), http .MethodPost , "/" , strings .NewReader (`{"key":"val"}` ))
210210 req .Header .Set ("Content-Type" , "application/json" )
211211
212212 rr := httptest .NewRecorder ()
@@ -217,11 +217,128 @@ func TestServeHTTP_PoolExecError_Returns500(t *testing.T) {
217217 }
218218}
219219
220+ // ── Group B′: FetchIP edge cases ─────────────────────────────────────────────
221+
222+ func TestFetchIP_EdgeCases (t * testing.T ) {
223+ tests := []struct {
224+ name string
225+ input string
226+ want string
227+ }{
228+ {"empty string" , "" , "" },
229+ {"ipv4 no port" , "10.0.0.1" , "10.0.0.1" },
230+ {"ipv6 bracketed with port" , "[::1]:8080" , "::1" },
231+ {"ipv6 full address bare" , "2001:db8::1" , "2001:db8::1" },
232+ {"garbage with colons" , "not:a:valid:thing" , "" },
233+ {"port only" , ":8080" , "" },
234+ {"ipv4 with empty port" , "127.0.0.1:" , "127.0.0.1" },
235+ {"ipv6 full with port" , "[2001:db8::1]:443" , "2001:db8::1" },
236+ }
237+
238+ log := zap .NewNop ()
239+ for _ , tt := range tests {
240+ t .Run (tt .name , func (t * testing.T ) {
241+ got := FetchIP (tt .input , log )
242+ if got != tt .want {
243+ t .Errorf ("FetchIP(%q) = %q, want %q" , tt .input , got , tt .want )
244+ }
245+ })
246+ }
247+ }
248+
249+ // ── Group B″: URI edge cases ─────────────────────────────────────────────────
250+
251+ func TestURI_EdgeCases (t * testing.T ) {
252+ tests := []struct {
253+ name string
254+ setup func () * http.Request
255+ want string
256+ }{
257+ {
258+ name : "empty host" ,
259+ setup : func () * http.Request {
260+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/" , nil )
261+ r .Host = ""
262+ return r
263+ },
264+ want : "http:///" ,
265+ },
266+ {
267+ name : "host with port" ,
268+ setup : func () * http.Request {
269+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/p" , nil )
270+ r .Host = "example.com:8080"
271+ return r
272+ },
273+ want : "http://example.com:8080/p" ,
274+ },
275+ {
276+ name : "url already has host set" ,
277+ setup : func () * http.Request {
278+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/x" , nil )
279+ r .URL .Host = "other.com"
280+ return r
281+ },
282+ want : "//other.com/x" ,
283+ },
284+ {
285+ name : "root path only" ,
286+ setup : func () * http.Request {
287+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/" , nil )
288+ r .Host = "example.com"
289+ return r
290+ },
291+ want : "http://example.com/" ,
292+ },
293+ {
294+ name : "query but no path" ,
295+ setup : func () * http.Request {
296+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/" , nil )
297+ r .Host = "example.com"
298+ r .URL .Path = ""
299+ r .URL .RawQuery = "a=1"
300+ return r
301+ },
302+ want : "http://example.com?a=1" ,
303+ },
304+ {
305+ name : "encoded CRLF in path preserved" ,
306+ setup : func () * http.Request {
307+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/foo%0D%0Abar" , nil )
308+ r .Host = "example.com"
309+ return r
310+ },
311+ want : "http://example.com/foo%0D%0Abar" ,
312+ },
313+ {
314+ name : "tab in query not stripped" ,
315+ setup : func () * http.Request {
316+ r := httptest .NewRequestWithContext (t .Context (), http .MethodGet , "/" , nil )
317+ r .Host = "example.com"
318+ r .URL .RawQuery = "x=1\t X-Bad: true"
319+ return r
320+ },
321+ want : "http://example.com/?x=1\t X-Bad: true" ,
322+ },
323+ }
324+
325+ for _ , tt := range tests {
326+ t .Run (tt .name , func (t * testing.T ) {
327+ got := URI (tt .setup ())
328+ if got != tt .want {
329+ t .Errorf ("URI() = %q, want %q" , got , tt .want )
330+ }
331+ })
332+ }
333+ }
334+
335+ // ── Group C: mockPool tests ───────────────────────────────────────────────────
336+
220337func TestServeHTTP_NoFreeWorkers_SetsHeader (t * testing.T ) {
221338 mp := & mockPool {execErr : errors .E (errors .NoFreeWorkers )}
222339 h := newTestHandler (t , defaultCfg (), mp )
223340
224- req := httptest .NewRequest ( http .MethodPost , "/" , strings .NewReader (`{"key":"val"}` ))
341+ req := httptest .NewRequestWithContext ( t . Context (), http .MethodPost , "/" , strings .NewReader (`{"key":"val"}` ))
225342 req .Header .Set ("Content-Type" , "application/json" )
226343
227344 rr := httptest .NewRecorder ()
0 commit comments