@@ -217,6 +217,123 @@ 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 )
0 commit comments