@@ -6,11 +6,14 @@ import (
66	"net/http" 
77	"net/http/httptest" 
88	"slices" 
9+ 	"strings" 
910	"testing" 
1011	"time" 
1112
1213	"github.com/hashicorp/go-retryablehttp" 
14+ 	"github.com/prometheus/client_golang/prometheus/testutil" 
1315	"github.com/stretchr/testify/assert" 
16+ 	"github.com/stretchr/testify/require" 
1417)
1518
1619func  TestRetryableHTTPClientCheckRetry (t  * testing.T ) {
@@ -269,3 +272,170 @@ func TestRetryableHTTPClientTimeout(t *testing.T) {
269272		})
270273	}
271274}
275+ 
276+ func  TestSanitizeURL (t  * testing.T ) {
277+ 	testCases  :=  []struct  {
278+ 		name      string 
279+ 		input     string 
280+ 		expected  string 
281+ 	}{
282+ 		{
283+ 			name :     "valid https URL" ,
284+ 			input :    "https://api.example.com/v1/users" ,
285+ 			expected : "https://api.example.com/v1/users" ,
286+ 		},
287+ 		{
288+ 			name :     "URL with query parameters" ,
289+ 			input :    "https://api.example.com/search?q=secret&limit=10" ,
290+ 			expected : "https://api.example.com/search" ,
291+ 		},
292+ 		{
293+ 			name :     "URL with fragment" ,
294+ 			input :    "https://example.com/page#section" ,
295+ 			expected : "https://example.com/page" ,
296+ 		},
297+ 		{
298+ 			name :     "URL with user info" ,
299+ 			input :    
"https://user:[email protected] /path" ,
 300+ 			expected : "https://api.example.com/path" ,
301+ 		},
302+ 		{
303+ 			name :     "empty URL" ,
304+ 			input :    "" ,
305+ 			expected : "unknown" ,
306+ 		},
307+ 		{
308+ 			name :     "invalid URL" ,
309+ 			input :    "not-a-url" ,
310+ 			expected : "relative_or_invalid" ,
311+ 		},
312+ 		{
313+ 			name :     "very long path" ,
314+ 			input :    "https://example.com/"  +  strings .Repeat ("a" , 150 ),
315+ 			expected : "https://example.com/"  +  strings .Repeat ("a" , 99 ) +  "..." , // 99 + 1 ("/") = 100 chars 
316+ 		},
317+ 		{
318+ 			name :     "root path" ,
319+ 			input :    "https://example.com" ,
320+ 			expected : "https://example.com/" ,
321+ 		},
322+ 	}
323+ 
324+ 	for  _ , tc  :=  range  testCases  {
325+ 		t .Run (tc .name , func (t  * testing.T ) {
326+ 			result  :=  sanitizeURL (tc .input )
327+ 			assert .Equal (t , tc .expected , result )
328+ 		})
329+ 	}
330+ }
331+ 
332+ func  TestSaneHttpClientMetrics (t  * testing.T ) {
333+ 	// Create a test server that returns different status codes 
334+ 	server  :=  httptest .NewServer (http .HandlerFunc (func (w  http.ResponseWriter , r  * http.Request ) {
335+ 		switch  r .URL .Path  {
336+ 		case  "/success" :
337+ 			w .WriteHeader (http .StatusOK )
338+ 			_ , _  =  w .Write ([]byte ("success" ))
339+ 		case  "/error" :
340+ 			w .WriteHeader (http .StatusInternalServerError )
341+ 			_ , _  =  w .Write ([]byte ("error" ))
342+ 		case  "/notfound" :
343+ 			w .WriteHeader (http .StatusNotFound )
344+ 			_ , _  =  w .Write ([]byte ("not found" ))
345+ 		default :
346+ 			w .WriteHeader (http .StatusOK )
347+ 			_ , _  =  w .Write ([]byte ("default" ))
348+ 		}
349+ 	}))
350+ 	defer  server .Close ()
351+ 
352+ 	// Create a SaneHttpClient 
353+ 	client  :=  SaneHttpClient ()
354+ 
355+ 	testCases  :=  []struct  {
356+ 		name                string 
357+ 		path                string 
358+ 		expectedStatusCode  int 
359+ 		expectsNon200       bool 
360+ 	}{
361+ 		{
362+ 			name :               "successful request" ,
363+ 			path :               "/success" ,
364+ 			expectedStatusCode : 200 ,
365+ 			expectsNon200 :      false ,
366+ 		},
367+ 		{
368+ 			name :               "server error request" ,
369+ 			path :               "/error" ,
370+ 			expectedStatusCode : 500 ,
371+ 			expectsNon200 :      true ,
372+ 		},
373+ 		{
374+ 			name :               "not found request" ,
375+ 			path :               "/notfound" ,
376+ 			expectedStatusCode : 404 ,
377+ 			expectsNon200 :      true ,
378+ 		},
379+ 	}
380+ 
381+ 	for  _ , tc  :=  range  testCases  {
382+ 		t .Run (tc .name , func (t  * testing.T ) {
383+ 			var  requestURL  string 
384+ 			if  strings .HasPrefix (tc .path , "http" ) {
385+ 				requestURL  =  tc .path 
386+ 			} else  {
387+ 				requestURL  =  server .URL  +  tc .path 
388+ 			}
389+ 
390+ 			// Get initial metric values 
391+ 			sanitizedURL  :=  sanitizeURL (requestURL )
392+ 			initialRequestsTotal  :=  testutil .ToFloat64 (httpRequestsTotal .WithLabelValues (sanitizedURL ))
393+ 
394+ 			// Make the request 
395+ 			resp , err  :=  client .Get (requestURL )
396+ 
397+ 			require .NoError (t , err )
398+ 			defer  resp .Body .Close ()
399+ 			assert .Equal (t , tc .expectedStatusCode , resp .StatusCode )
400+ 
401+ 			// Check that request counter was incremented 
402+ 			requestsTotal  :=  testutil .ToFloat64 (httpRequestsTotal .WithLabelValues (sanitizedURL ))
403+ 			assert .Equal (t , initialRequestsTotal + 1 , requestsTotal )
404+ 		})
405+ 	}
406+ }
407+ 
408+ func  TestInstrumentedTransport (t  * testing.T ) {
409+ 	// Create a mock transport that we can control 
410+ 	server  :=  httptest .NewServer (http .HandlerFunc (func (w  http.ResponseWriter , r  * http.Request ) {
411+ 		w .WriteHeader (http .StatusOK )
412+ 		_ , _  =  w .Write ([]byte ("test response" ))
413+ 	}))
414+ 	defer  server .Close ()
415+ 
416+ 	// Create instrumented transport 
417+ 	transport  :=  NewInstrumentedTransport (nil )
418+ 	client  :=  & http.Client {
419+ 		Transport : transport ,
420+ 		Timeout :   5  *  time .Second ,
421+ 	}
422+ 
423+ 	// Get initial metric value 
424+ 	sanitizedURL  :=  sanitizeURL (server .URL )
425+ 	initialCount  :=  testutil .ToFloat64 (httpRequestsTotal .WithLabelValues (sanitizedURL ))
426+ 
427+ 	// Make a request 
428+ 	resp , err  :=  client .Get (server .URL )
429+ 	require .NoError (t , err )
430+ 	defer  resp .Body .Close ()
431+ 
432+ 	// Verify the request was successful 
433+ 	assert .Equal (t , http .StatusOK , resp .StatusCode )
434+ 
435+ 	// Verify metrics were recorded 
436+ 	finalCount  :=  testutil .ToFloat64 (httpRequestsTotal .WithLabelValues (sanitizedURL ))
437+ 	assert .Equal (t , initialCount + 1 , finalCount )
438+ 
439+ 	// Note: Testing histogram metrics is complex due to the way Prometheus handles them 
440+ 	// The main thing is that the request completed successfully and counters were incremented 
441+ }
0 commit comments