From b03077bfe8f4f952eebbb4c690295146c640e33c Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 10:30:30 +0100 Subject: [PATCH 01/30] refactor: decouple writer in requests printing functions --- internal/requests/requests.go | 60 ++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 2de2c6a..618013e 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -7,9 +7,12 @@ import ( "crypto/x509" "errors" "fmt" + "io" "net" "net/http" "net/http/httputil" + "net/url" + "os" "strings" "time" @@ -181,11 +184,13 @@ func (r *RequestsMetaConfig) SetRequests(requests []RequestConfig) *RequestsMeta return r } -func (r *RequestsMetaConfig) PrintCmd() { +func (r *RequestsMetaConfig) PrintCmd(w io.Writer) { if r.RequestVerbose { - fmt.Println() - fmt.Println(style.LgSprintf(style.Cmd, "Requests")) - fmt.Println() + fmt.Fprintf( + w, + "\n%s\n", + style.LgSprintf(style.Cmd, "Requests"), + ) } } @@ -201,50 +206,57 @@ func (r *RequestConfig) PrintTitle(isVerbose bool) { } } -func (r *RequestConfig) PrintRequestDebug(req *http.Request) { +func (r *RequestConfig) PrintRequestDebug(w io.Writer, req *http.Request) error { + if req == nil { + return errors.New("nil pointer to http.Request") + } + if r.RequestDebug { reqDump, err := httputil.DumpRequestOut(req, true) if err != nil { - fmt.Printf("Warning: failed to dump request: %v\n", err) - return + fmt.Fprintf(w, "Warning: failed to dump request: %v\n", err) + return err } - fmt.Printf("Requesting url: %s\n", req.URL) - fmt.Printf("Request dump:\n%s\n", string(reqDump)) + _, err = fmt.Fprintf(w, "Requesting url: %s\nRequest dump:\n%s\n", req.URL, string(reqDump)) + + return err } + + return nil } -func (r *RequestConfig) PrintResponseDebug(resp *http.Response) { +func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { if r.ResponseDebug { respDump, err := httputil.DumpResponse(resp, true) if err != nil { - fmt.Printf("Warning: failed to dump response: %v\n", err) + fmt.Fprintf(w, "Warning: failed to dump response: %v\n", err) return } - fmt.Printf("Requested url: %s\n", resp.Request.URL) - fmt.Printf("Response dump:\n%s\n", string(respDump)) + fmt.Fprintf(w, "Requested url: %s\n", resp.Request.URL) + fmt.Fprintf(w, "Response dump:\n%s\n", string(respDump)) if resp.TLS != nil { - fmt.Println("TLS:") - fmt.Printf("Version: %v\n", TLSVersionName(resp.TLS.Version)) - fmt.Printf("CipherSuite: %v\n", cipherSuiteName(resp.TLS.CipherSuite)) + fmt.Fprintln(w, "TLS:") + fmt.Fprintf(w, "Version: %v\n", TLSVersionName(resp.TLS.Version)) + fmt.Fprintf(w, "CipherSuite: %v\n", cipherSuiteName(resp.TLS.CipherSuite)) for i, cert := range resp.TLS.PeerCertificates { - fmt.Printf("Certificate %d:\n", i) + fmt.Fprintf(w, "Certificate %d:\n", i) certinfo.PrintCertInfo(cert, 1) } for i, chain := range resp.TLS.VerifiedChains { - fmt.Printf("Verified Chain %d:\n", i) + fmt.Fprintf(w, "Verified Chain %d:\n", i) for j, cert := range chain { - fmt.Printf(" Cert %d:\n", j) + fmt.Fprintf(w, " Cert %d:\n", j) certinfo.PrintCertInfo(cert, 2) } } } else { - fmt.Println("TLS: Not available (non-TLS connection)") + fmt.Fprintln(w, "TLS: Not available (non-TLS connection)") } } } @@ -275,6 +287,10 @@ func (rc *RequestHTTPClient) SetServerName(serverName string) (*RequestHTTPClien "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize") } + if _, err := url.Parse(serverName); err != nil { + return nil, fmt.Errorf("unable to parse serverName %s: %w", serverName, err) + } + transport, ok := rc.client.Transport.(*http.Transport) if !ok { return nil, fmt.Errorf("expected *http.Transport, got %T", rc.client.Transport) @@ -563,7 +579,7 @@ func processHTTPRequestsByHost(r RequestConfig, caPool *x509.CertPool, isVerbose req.Header.Add(header.Key, header.Value) } - r.PrintRequestDebug(req) + r.PrintRequestDebug(os.Stdout, req) resp, err := reqClient.client.Do(req) if err != nil { @@ -580,7 +596,7 @@ func processHTTPRequestsByHost(r RequestConfig, caPool *x509.CertPool, isVerbose continue } - r.PrintResponseDebug(resp) + r.PrintResponseDebug(os.Stdout, resp) responseData.Response = resp From ab47545b504ee2be2ea6861dec5e7b3f8372ca83 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 10:31:30 +0100 Subject: [PATCH 02/30] refactor: decouple enable flag and header generation for SetProxyProtocol methods --- internal/requests/requests.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 618013e..ee0e929 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -422,7 +422,13 @@ func (rc *RequestHTTPClient) SetTransportOverride(transportURL string) (*Request return rc, nil } -func (rc *RequestHTTPClient) SetProxyProtocolV2(header proxyproto.Header) (*RequestHTTPClient, error) { +func (rc *RequestHTTPClient) SetProxyProtocolV2(enable bool) (*RequestHTTPClient, error) { + rc.enableProxyProtoV2 = enable + + return rc, nil +} + +func (rc *RequestHTTPClient) SetProxyProtocolHeader(header proxyproto.Header) (*RequestHTTPClient, error) { if rc.transportAddress == emptyString { return nil, errors.New("SetProxyProtocolV2 failed: transportOverrideURL not set") } @@ -473,6 +479,10 @@ func (rc *RequestHTTPClient) SetClientTimeout(timeout int) (*RequestHTTPClient, "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize") } + if timeout < 0 { + return nil, fmt.Errorf("timeout value must be positive: %v provided", timeout) + } + t := time.Duration(timeout) * time.Second rc.client.Timeout = t @@ -480,11 +490,6 @@ func (rc *RequestHTTPClient) SetClientTimeout(timeout int) (*RequestHTTPClient, } func NewHTTPClientFromRequestConfig(r RequestConfig, serverName string, caPool *x509.CertPool) (*RequestHTTPClient, error) { - if r.EnableProxyProtocolV2 && r.TransportOverrideURL == emptyString { - return nil, errors.New( - "if EnableProxyProtocolV2 is true, a TransportOverrideURL must be set") - } - reqClient := NewRequestHTTPClient() _, err := reqClient.SetCACertsPool(caPool) @@ -517,15 +522,25 @@ func NewHTTPClientFromRequestConfig(r RequestConfig, serverName string, caPool * return nil, fmt.Errorf("SetTransportOverride error: %w", err) } + _, err = reqClient.SetProxyProtocolV2(r.EnableProxyProtocolV2) + if err != nil { + return nil, fmt.Errorf("SetProxyProtocolV2 error: %w", err) + } + + if r.EnableProxyProtocolV2 && r.TransportOverrideURL == emptyString { + return nil, errors.New( + "if EnableProxyProtocolV2 is true, a TransportOverrideURL must be set") + } + if r.EnableProxyProtocolV2 && reqClient.transportAddress != emptyString { header, err := proxyProtoHeaderFromRequest(r, serverName) if err != nil { return nil, fmt.Errorf("error creating proxyproto Header: %w", err) } - _, err = reqClient.SetProxyProtocolV2(header) + _, err = reqClient.SetProxyProtocolHeader(header) if err != nil { - return nil, fmt.Errorf("SetProxyProtocolV2 error: %w", err) + return nil, fmt.Errorf("SetProxyProtocolHeader error: %w", err) } } From c447ab518385f9dff5fea3462fa20c311d5664e2 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 10:32:56 +0100 Subject: [PATCH 03/30] refactor: PrintCmd function call in requests_handlers --- internal/requests/requests_handlers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/requests/requests_handlers.go b/internal/requests/requests_handlers.go index 776c8d3..8f88eb3 100644 --- a/internal/requests/requests_handlers.go +++ b/internal/requests/requests_handlers.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/url" + "os" "regexp" "slices" "strconv" @@ -205,7 +206,7 @@ func proxyProtoHeaderFromRequest(r RequestConfig, serverName string) (proxyproto func HandleRequests(cfg *RequestsMetaConfig) (map[string][]ResponseData, error) { responseDataMap := make(map[string][]ResponseData) - cfg.PrintCmd() + cfg.PrintCmd(os.Stdout) for _, r := range cfg.Requests { responseDataList, err := processHTTPRequestsByHost( From 6080649511c8cdcead1a3d4f81b3a8e27ca2fe30 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 10:34:41 +0100 Subject: [PATCH 04/30] ci: add some tests for requests --- internal/requests/requests_test.go | 640 ++++++++++++++++++++++++++++- 1 file changed, 638 insertions(+), 2 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 29a7ac2..a5df360 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -1,12 +1,15 @@ package requests import ( + "bytes" "crypto/tls" "crypto/x509" "fmt" "net/http" "net/http/httptest" + "net/url" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" @@ -155,6 +158,43 @@ func TestRequestsMetaConfig_SetCaPoolFromFile(t *testing.T) { } } +func TestRequestsMetaConfig_SetRequests(t *testing.T) { + hosts := []Host{ + {Name: "www.example.com"}, + {Name: "example.com", URIList: []URI{"/test", "test2"}}, + } + + requestConfigs := []RequestConfig{ + { + Name: "first request", + Insecure: true, + TransportOverrideURL: "localhost:443", + }, + { + Name: "second request", + PrintResponseBody: true, + TransportOverrideURL: "localhost:443", + Hosts: hosts, + }, + } + + tests := [][]RequestConfig{requestConfigs} + + for _, tt := range tests { + testname := fmt.Sprintf("SetRequests(%v)", tt[0].Name) + t.Run(testname, func(t *testing.T) { + t.Parallel() + + rmc, _ := NewRequestsMetaConfig() + rmc.SetRequests(tt) + + if diff := cmp.Diff(tt, rmc.Requests); diff != "" { + t.Errorf("Requests mismatch (-want +got):\n%s", diff) + } + }) + } +} + func TestNewRequestHTTPClient(t *testing.T) { t.Run("NewRequestHTTPClient", func(t *testing.T) { t.Parallel() @@ -197,9 +237,153 @@ func TestNewRequestHTTPClient(t *testing.T) { }) } +func TestNewHTTPClientFromRequestConfig_Error(t *testing.T) { + tests := []struct { + desc string + reqConf RequestConfig + serverName string + errMsg string + }{ + { + desc: "EnableProxyProtocolV2", + reqConf: RequestConfig{ + EnableProxyProtocolV2: true, + }, + errMsg: "if EnableProxyProtocolV2 is true, a TransportOverrideURL must be set", + }, + { + desc: "EnableProxyProtoNoServerName", + reqConf: RequestConfig{ + TransportOverrideURL: "https://localhost:8443", + EnableProxyProtocolV2: true, + }, + serverName: "[::1]", + errMsg: "SetServerName error: unable to parse serverName [::1]: parse \"[::1]\": first path segment in URL cannot contain colon", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + _, err := NewHTTPClientFromRequestConfig( + tt.reqConf, + tt.serverName, + nil, + ) + require.Error(t, err) + assert.Equal(t, + tt.errMsg, + err.Error(), + ) + }) + } +} + +func TestNewHTTPClientFromRequestConfig(t *testing.T) { + tests := []struct { + desc string + reqConf RequestConfig + serverName string + pool *x509.CertPool + tranportAddress string + }{ + { + desc: "transpAddr", + reqConf: RequestConfig{ + ClientTimeout: 3, + UserAgent: "test1-ua", + TransportOverrideURL: "https://localhost:45555", + Insecure: true, + RequestMethod: http.MethodGet, + }, + tranportAddress: "localhost:45555", + }, + { + desc: "proxyProto", + reqConf: RequestConfig{ + TransportOverrideURL: "https://localhost:8443", + RequestMethod: http.MethodHead, + EnableProxyProtocolV2: true, + }, + tranportAddress: "localhost:8443", + }, + { + desc: "caPool", + reqConf: RequestConfig{ + RequestMethod: http.MethodPut, + }, + pool: caCertPool, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + rcClient, err := NewHTTPClientFromRequestConfig( + tt.reqConf, + tt.serverName, + tt.pool, + ) + if err != nil { + t.Error(err) + } + + var i any = rcClient.client + + client, ok := i.(*http.Client) + if !ok { + t.Errorf("expecting *http.Client, got %T", client) + } + + assert.Equal(t, + time.Duration(tt.reqConf.ClientTimeout)*time.Second, + client.Timeout, + "check client Timeout", + ) + + assert.Equal(t, + tt.reqConf.RequestMethod, + rcClient.method, + "check client Method", + ) + + assert.Equal(t, + tt.reqConf.EnableProxyProtocolV2, + rcClient.enableProxyProtoV2, + "check proxy proto enabled", + ) + + var ti any = rcClient.client.Transport + + transport, ok := ti.(*http.Transport) + if !ok { + t.Errorf("expexcting *http.Transport, got %T", transport) + } + + assert.Equal(t, + tt.reqConf.Insecure, + transport.TLSClientConfig.InsecureSkipVerify, + "check Insecure", + ) + + currPool := systemCertPool + + if tt.pool != nil { + currPool = caCertPool + } + + if diff := cmp.Diff(currPool, transport.TLSClientConfig.RootCAs); diff != "" { + t.Errorf("Client CA Pool mismatch (-want +got):\n%s", diff) + } + }) + } +} + func TestNewRequestHTTPClient_SetServerName(t *testing.T) { tests := []string{ - "", "localhost", "127.0.0.1", "[::1]", "example.com", " a silly string ", + "", "localhost", "127.0.0.1", "example.com", " a silly string ", } for _, tt := range tests { @@ -226,6 +410,100 @@ func TestNewRequestHTTPClient_SetServerName(t *testing.T) { } }) } + + testsError := []struct { + serverName string + errMsg string + }{ + { + "[::1]", + "unable to parse serverName [::1]: parse \"[::1]\": first path segment in URL cannot contain colon", + }, + } + + for _, tt := range testsError { + testname := fmt.Sprintf("Error: %s", tt) + + t.Run(testname, func(t *testing.T) { + t.Parallel() + + c := NewRequestHTTPClient() + _, err := c.SetServerName(tt.serverName) + require.Error(t, err) + assert.Equal(t, tt.errMsg, err.Error()) + }) + } + + t.Run("Error: Nil HTTP client", func(t *testing.T) { + t.Parallel() + + var c RequestHTTPClient + + _, err := c.SetServerName("localhost") + require.Error(t, err) + assert.Equal(t, + "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize", + err.Error(), + ) + }) +} + +func TestNewRequestHTTPClient_SetClientTimeout(t *testing.T) { + tests := []int{ + 3, 0, 50, + } + + for _, tt := range tests { + testname := fmt.Sprintf("%v", tt) + t.Run(testname, func(t *testing.T) { + t.Parallel() + + c := NewRequestHTTPClient() + + _, err := c.SetClientTimeout(tt) + if err != nil { + require.Error(t, err) + } + + var i any = c.client.Timeout + + duration, ok := i.(time.Duration) + if !ok { + t.Fatalf("expected time.Duration, got %T", c.client.Timeout) + } + + assert.Equal(t, time.Duration(tt)*time.Second, duration) + }) + } +} + +func TestNewRequestHTTPClient_SetClientTimeout_Error(t *testing.T) { + t.Run("Negative Timeout", func(t *testing.T) { + t.Parallel() + + c := NewRequestHTTPClient() + timeout := -1 + + _, err := c.SetClientTimeout(timeout) + require.Error(t, err) + assert.Equal(t, "timeout value must be positive: -1 provided", err.Error()) + }) + + t.Run("Nil Timeout", func(t *testing.T) { + t.Parallel() + + var c RequestHTTPClient + + timeout := 10 + + _, err := c.SetClientTimeout(timeout) + require.Error(t, err) + assert.Equal( + t, + "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize", + err.Error(), + ) + }) } func TestNewRequestHTTPClient_SetCaCertsPool(t *testing.T) { @@ -544,7 +822,7 @@ func TestRequestHTTPClient_SetProxyProtocolV2_server(t *testing.T) { c := NewRequestHTTPClient() c.SetTransportOverride(transportURL) - c.SetProxyProtocolV2(header) + c.SetProxyProtocolHeader(header) // Extract the transport via type assertion transport, ok := c.client.Transport.(*http.Transport) @@ -589,3 +867,361 @@ func TestRequestHTTPClient_SetProxyProtocolV2_server(t *testing.T) { }) } } + +func TestPrintCmd(t *testing.T) { + tests := []struct { + verbose bool + output string + }{ + {true, "\n Requests \n"}, + {false, ""}, + } + + for _, tt := range tests { + testname := fmt.Sprintf("%v", tt.verbose) + t.Run(testname, func(t *testing.T) { + t.Parallel() + + buffer := bytes.Buffer{} + r := RequestsMetaConfig{RequestVerbose: tt.verbose} + r.PrintCmd(&buffer) + + got := buffer.String() + want := tt.output + + assert.Equal(t, want, got, "check PrintCmd") + }) + } +} + +func TestPrintResponseDebug(t *testing.T) { + tests := []struct { + desc string + srvAddr string + verbose bool + output string + }{ + { + desc: "verboseTrue", + srvAddr: "localhost:46010", + verbose: true, + output: emptyString, + }, + { + desc: "verboseFalse", + srvAddr: "localhost:46010", + verbose: false, + output: emptyString, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + // t.Parallel() + httpSrvData := demoHttpServerData{ + serverAddr: tt.srvAddr, + proxyprotoEnabled: false, + serverName: "localhost", + } + + ts, err := NewHTTPSTestServer(httpSrvData) + if err != nil { + t.Fatal(err) + } + defer ts.Close() + + tr := &http.Transport{TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + // InsecureSkipVerify: true, + }} + + client := &http.Client{Transport: tr} + + res, err := client.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + + rc := RequestConfig{ResponseDebug: tt.verbose} + buffer := bytes.Buffer{} + rc.PrintResponseDebug(&buffer, res) + + got := buffer.String() + fmt.Printf("got:\n%s\n", got) + + if !tt.verbose && len(got) == 0 { + assert.Equal(t, + []byte(nil), + buffer.Bytes(), + "check PrintRequestDebug with verbose False", + ) + } + + if tt.verbose { + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("Requested url:")), + "check PrintResponseDebug contains: Requested url", + ) + + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("Response dump:")), + "check PrintResponseDebug contains: Response dump", + ) + + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("DemoHTTPSServer Handler - client output")), + "check PrintResponseDebug contains: DemoHTTPSServer Handler - client output", + ) + + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("TLS:")), + "check PrintResponseDebug contains: TLS", + ) + + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("CipherSuite:")), + "check PrintResponseDebug contains: CipherSuite", + ) + } + }) + + t.Run("non-TLS", func(t *testing.T) { + respURL := url.URL{Scheme: "http", Host: "localhost"} + req := http.Request{URL: &respURL} + resp := http.Response{ + StatusCode: 200, + Request: &req, + } + rc := RequestConfig{ResponseDebug: true} + buffer := bytes.Buffer{} + rc.PrintResponseDebug(&buffer, &resp) + + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("TLS: Not available")), + "check non-TLS connection", + ) + }) + } +} + +func TestPrintRequestDebug(t *testing.T) { + httpTestHeader := http.Header{} + httpTestHeader.Add("user-agent", "go-test") + requestTest := http.Request{ + Method: http.MethodGet, + URL: &url.URL{Scheme: "https", Host: "localhost"}, + Header: httpTestHeader, + } + + requestTestIncomplete := http.Request{ + Method: http.MethodGet, + URL: &url.URL{Scheme: "https", Host: "localhost"}, + } + + var requestTestNilPointer *http.Request + + expectetOutput := "Requesting url: https://localhost\nRequest dump:\nGET / " + expectetOutput += "HTTP/1.1\r\nHost: localhost\r\nUser-Agent: go-test\r\nAccept-Encoding: " + expectetOutput += "gzip\r\n\r\n\n" + + expectetOutputIncomplete := "Warning: failed to dump request: http: nil Request.Header\n" + + tests := []struct { + desc string + verbose bool + request *http.Request + output string + }{ + { + desc: "verboseTrue", + verbose: true, + request: &requestTest, + output: expectetOutput, + }, + + { + desc: "verboseFalse", + verbose: false, + request: &requestTest, + output: emptyString, + }, + { + desc: "nilRequestError", + verbose: true, + request: requestTestNilPointer, + output: emptyString, + }, + { + desc: "incompleteRequestError", + verbose: true, + request: &requestTestIncomplete, + output: expectetOutputIncomplete, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + buffer := bytes.Buffer{} + r := RequestConfig{RequestDebug: tt.verbose} + + err := r.PrintRequestDebug(&buffer, tt.request) + if err != nil { + require.Error(t, err, "PrintRequestDebug error") + } + + got := buffer.String() + want := tt.output + + assert.Equal(t, want, got, "check PrintRequestDebug") + }) + } +} + +func TestProcessHTTPRequestsByHost(t *testing.T) { + tests := []struct { + srvAddr string + reqConf RequestConfig + pool *x509.CertPool + verbose bool + respStatusCode int + errMsg string + }{ + { + srvAddr: "localhost:46001", + reqConf: RequestConfig{ + Name: "StatusOK", + TransportOverrideURL: "https://localhost:46001", + UserAgent: "test-ua", + RequestHeaders: []RequestHeader{ + {Key: "testKey", Value: "testValue"}, + {Key: "testKey2", Value: "testValue2"}, + }, + Hosts: []Host{ + {Name: "example.com"}, + }, + }, + pool: caCertPool, + verbose: false, + respStatusCode: http.StatusOK, + }, + + { + srvAddr: "localhost:46002", + reqConf: RequestConfig{ + Name: "invalidServerName", + TransportOverrideURL: "https://localhost:46002", + Hosts: []Host{ + {Name: "localhost"}, + }, + }, + pool: caCertPool, + verbose: false, + respStatusCode: 0, + errMsg: "Get \"https://localhost\": tls: failed to verify certificate: x509: certificate is valid for example.com, example.net, example.de, not localhost", + }, + + { + srvAddr: "localhost:46003", + reqConf: RequestConfig{ + Name: "bodyRex", + ResponseBodyMatchRegexp: "DemoHTTPSServer Handler - client output", + PrintResponseBody: true, + TransportOverrideURL: "https://localhost:46003", + Hosts: []Host{ + {Name: "example.com"}, + }, + }, + pool: caCertPool, + verbose: true, + respStatusCode: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.reqConf.Name, func(t *testing.T) { + t.Parallel() + + httpSrvData := demoHttpServerData{ + serverAddr: tt.srvAddr, + proxyprotoEnabled: false, + serverName: "localhost", + } + + ts, err := NewHTTPSTestServer(httpSrvData) + if err != nil { + t.Fatal(err) + } + defer ts.Close() + + respList, err := processHTTPRequestsByHost( + tt.reqConf, + tt.pool, + tt.verbose, + ) + if err != nil { + t.Error(err) + } + + for _, r := range respList { + fmt.Printf("resp type: %T\n", r) + + assert.Equal(t, + tt.srvAddr, + r.TransportAddress, + "check TransportAddress", + ) + + if tt.respStatusCode == 0 { + assert.Equal(t, + tt.errMsg, + r.Error.Error(), + "check Response Error", + ) + } + + // if expecting and error from the request do not + // check values from the response + if tt.respStatusCode != 0 { + require.NoError(t, + r.Error, + "check NoError in ResponseData", + ) + + ua := httpUserAgent + + if tt.reqConf.UserAgent != emptyString { + ua = tt.reqConf.UserAgent + } + + assert.Equal(t, + ua, + r.Response.Request.Header.Get("user-agent"), + "check UserAgent", + ) + + assert.Equal(t, + len(tt.reqConf.ResponseBodyMatchRegexp) > 0, + r.ResponseBodyRegexpMatched, + "check body rex match", + ) + + assert.Equal(t, + tt.respStatusCode, + r.Response.StatusCode, + "check StatusCode", + ) + + for _, headers := range tt.reqConf.RequestHeaders { + assert.Equal(t, + headers.Value, + r.Response.Request.Header.Get(headers.Key), + "check RequestHeaders Key", + ) + } + } + } + }) + } +} From c5622101c159d5ba7c8198d28d84f4238e2be3f4 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 11:12:49 +0100 Subject: [PATCH 05/30] fix: non-TLS test case out of main loop in TestPrintResponseDebug --- internal/requests/requests_test.go | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index a5df360..153ac61 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -984,24 +984,24 @@ func TestPrintResponseDebug(t *testing.T) { ) } }) + } - t.Run("non-TLS", func(t *testing.T) { - respURL := url.URL{Scheme: "http", Host: "localhost"} - req := http.Request{URL: &respURL} - resp := http.Response{ - StatusCode: 200, - Request: &req, - } - rc := RequestConfig{ResponseDebug: true} - buffer := bytes.Buffer{} - rc.PrintResponseDebug(&buffer, &resp) + t.Run("non-TLS", func(t *testing.T) { + respURL := url.URL{Scheme: "http", Host: "localhost"} + req := http.Request{URL: &respURL} + resp := http.Response{ + StatusCode: 200, + Request: &req, + } + rc := RequestConfig{ResponseDebug: true} + buffer := bytes.Buffer{} + rc.PrintResponseDebug(&buffer, &resp) - assert.True(t, - bytes.Contains(buffer.Bytes(), []byte("TLS: Not available")), - "check non-TLS connection", - ) - }) - } + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte("TLS: Not available")), + "check non-TLS connection", + ) + }) } func TestPrintRequestDebug(t *testing.T) { From 101de868632f8aa99866e6110acc03e2b400c7cb Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 11:17:49 +0100 Subject: [PATCH 06/30] refactor: check nil http Response in PrintResponseDebug --- internal/requests/requests.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index ee0e929..0ed2c90 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -227,6 +227,10 @@ func (r *RequestConfig) PrintRequestDebug(w io.Writer, req *http.Request) error } func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { + if resp == nil { + return + } + if r.ResponseDebug { respDump, err := httputil.DumpResponse(resp, true) if err != nil { From cb6589807ea6c3081685a7bddd662085cf0c38a5 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 11:34:30 +0100 Subject: [PATCH 07/30] refactor: manage error in PrintRequestDebug call --- internal/requests/requests.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 0ed2c90..5241b37 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -227,6 +227,7 @@ func (r *RequestConfig) PrintRequestDebug(w io.Writer, req *http.Request) error } func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { + // TODO: return an error if resp == nil { return } @@ -598,7 +599,9 @@ func processHTTPRequestsByHost(r RequestConfig, caPool *x509.CertPool, isVerbose req.Header.Add(header.Key, header.Value) } - r.PrintRequestDebug(os.Stdout, req) + if err := r.PrintRequestDebug(os.Stdout, req); err != nil { + fmt.Fprintf(os.Stderr, "Warning: PrintRequestDebug failed: %v\n", err) + } resp, err := reqClient.client.Do(req) if err != nil { From 7eccb1b465d1bf527fd857efd668ffb338c57589 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 11:37:02 +0100 Subject: [PATCH 08/30] fix: typo in TestNewHTTPClientFromRequestConfig --- internal/requests/requests_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 153ac61..a84ee4a 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -359,7 +359,7 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { transport, ok := ti.(*http.Transport) if !ok { - t.Errorf("expexcting *http.Transport, got %T", transport) + t.Errorf("expecting *http.Transport, got %T", transport) } assert.Equal(t, From 6c67a1d281bddfad6a377cbc519bc6026488360b Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 12:01:09 +0100 Subject: [PATCH 09/30] refactor: TestPrintRespondeDebug to decrease assert check over partial output strings and split non-TLS check --- internal/requests/requests_test.go | 50 ++++++++++++------------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index a84ee4a..b038f30 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -899,25 +899,32 @@ func TestPrintResponseDebug(t *testing.T) { desc string srvAddr string verbose bool - output string + outputs []string }{ { desc: "verboseTrue", srvAddr: "localhost:46010", verbose: true, - output: emptyString, + outputs: []string{ + "Requested url:", + "Response dump:", + "DemoHTTPSServer Handler - client output", + "TLS:", + "CipherSuite:", + }, }, { desc: "verboseFalse", - srvAddr: "localhost:46010", + srvAddr: "localhost:46011", verbose: false, - output: emptyString, + outputs: []string{emptyString}, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - // t.Parallel() + t.Parallel() + httpSrvData := demoHttpServerData{ serverAddr: tt.srvAddr, proxyprotoEnabled: false, @@ -932,7 +939,6 @@ func TestPrintResponseDebug(t *testing.T) { tr := &http.Transport{TLSClientConfig: &tls.Config{ RootCAs: caCertPool, - // InsecureSkipVerify: true, }} client := &http.Client{Transport: tr} @@ -958,34 +964,18 @@ func TestPrintResponseDebug(t *testing.T) { } if tt.verbose { - assert.True(t, - bytes.Contains(buffer.Bytes(), []byte("Requested url:")), - "check PrintResponseDebug contains: Requested url", - ) - - assert.True(t, - bytes.Contains(buffer.Bytes(), []byte("Response dump:")), - "check PrintResponseDebug contains: Response dump", - ) - - assert.True(t, - bytes.Contains(buffer.Bytes(), []byte("DemoHTTPSServer Handler - client output")), - "check PrintResponseDebug contains: DemoHTTPSServer Handler - client output", - ) - - assert.True(t, - bytes.Contains(buffer.Bytes(), []byte("TLS:")), - "check PrintResponseDebug contains: TLS", - ) - - assert.True(t, - bytes.Contains(buffer.Bytes(), []byte("CipherSuite:")), - "check PrintResponseDebug contains: CipherSuite", - ) + for _, output := range tt.outputs { + assert.True(t, + bytes.Contains(buffer.Bytes(), []byte(output)), + "check PrintResponseDebug contains: %s", output, + ) + } } }) } +} +func TestPrintResponseDebug_nonTLS(t *testing.T) { t.Run("non-TLS", func(t *testing.T) { respURL := url.URL{Scheme: "http", Host: "localhost"} req := http.Request{URL: &respURL} From 97b22291bdd30af2416d4467cafb5b2664c8af51 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 12:01:46 +0100 Subject: [PATCH 10/30] fix: typos in TestPrintRequestDebug --- internal/requests/requests_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index b038f30..1eca592 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -1010,11 +1010,11 @@ func TestPrintRequestDebug(t *testing.T) { var requestTestNilPointer *http.Request - expectetOutput := "Requesting url: https://localhost\nRequest dump:\nGET / " - expectetOutput += "HTTP/1.1\r\nHost: localhost\r\nUser-Agent: go-test\r\nAccept-Encoding: " - expectetOutput += "gzip\r\n\r\n\n" + expectedOutput := "Requesting url: https://localhost\nRequest dump:\nGET / " + expectedOutput += "HTTP/1.1\r\nHost: localhost\r\nUser-Agent: go-test\r\nAccept-Encoding: " + expectedOutput += "gzip\r\n\r\n\n" - expectetOutputIncomplete := "Warning: failed to dump request: http: nil Request.Header\n" + expectedOutputIncomplete := "Warning: failed to dump request: http: nil Request.Header\n" tests := []struct { desc string @@ -1026,7 +1026,7 @@ func TestPrintRequestDebug(t *testing.T) { desc: "verboseTrue", verbose: true, request: &requestTest, - output: expectetOutput, + output: expectedOutput, }, { @@ -1045,7 +1045,7 @@ func TestPrintRequestDebug(t *testing.T) { desc: "incompleteRequestError", verbose: true, request: &requestTestIncomplete, - output: expectetOutputIncomplete, + output: expectedOutputIncomplete, }, } From 0c05e61f19e41645d9d46eb5bab6f7abac3ccabe Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 13:11:56 +0100 Subject: [PATCH 11/30] refactor: SetServerName check for empty string and url as input --- internal/requests/requests.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 5241b37..c0be862 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -11,7 +11,6 @@ import ( "net" "net/http" "net/http/httputil" - "net/url" "os" "strings" "time" @@ -292,8 +291,12 @@ func (rc *RequestHTTPClient) SetServerName(serverName string) (*RequestHTTPClien "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize") } - if _, err := url.Parse(serverName); err != nil { - return nil, fmt.Errorf("unable to parse serverName %s: %w", serverName, err) + if serverName == emptyString { + return nil, errors.New("serverName cannot be empty") + } + + if strings.Contains(serverName, "://") { + return nil, fmt.Errorf("serverName should be a hostname, not a URL: %s", serverName) } transport, ok := rc.client.Transport.(*http.Transport) From 3e315748e8906470671b72ba22d697d1b6fa4e65 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 13:12:17 +0100 Subject: [PATCH 12/30] refactor: tests related to SetServerName --- internal/requests/requests_test.go | 57 ++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 1eca592..0e2a100 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -249,7 +249,8 @@ func TestNewHTTPClientFromRequestConfig_Error(t *testing.T) { reqConf: RequestConfig{ EnableProxyProtocolV2: true, }, - errMsg: "if EnableProxyProtocolV2 is true, a TransportOverrideURL must be set", + serverName: "localhost", + errMsg: "if EnableProxyProtocolV2 is true, a TransportOverrideURL must be set", }, { desc: "EnableProxyProtoNoServerName", @@ -257,8 +258,8 @@ func TestNewHTTPClientFromRequestConfig_Error(t *testing.T) { TransportOverrideURL: "https://localhost:8443", EnableProxyProtocolV2: true, }, - serverName: "[::1]", - errMsg: "SetServerName error: unable to parse serverName [::1]: parse \"[::1]\": first path segment in URL cannot contain colon", + serverName: emptyString, + errMsg: "SetServerName error: serverName cannot be empty", }, } @@ -297,6 +298,7 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { Insecure: true, RequestMethod: http.MethodGet, }, + serverName: "localhost", tranportAddress: "localhost:45555", }, { @@ -306,6 +308,7 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { RequestMethod: http.MethodHead, EnableProxyProtocolV2: true, }, + serverName: "localhost", tranportAddress: "localhost:8443", }, { @@ -313,7 +316,8 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { reqConf: RequestConfig{ RequestMethod: http.MethodPut, }, - pool: caCertPool, + serverName: "localhost", + pool: caCertPool, }, } @@ -383,7 +387,11 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { func TestNewRequestHTTPClient_SetServerName(t *testing.T) { tests := []string{ - "", "localhost", "127.0.0.1", "example.com", " a silly string ", + "[::1]", + "localhost", + "127.0.0.1", + "example.com", + " a silly string ", } for _, tt := range tests { @@ -392,7 +400,11 @@ func TestNewRequestHTTPClient_SetServerName(t *testing.T) { t.Parallel() c := NewRequestHTTPClient() - c.SetServerName(tt) + + _, err := c.SetServerName(tt) + if err != nil { + t.Fatal(err) + } // Extract the transport via type assertion transport, ok := c.client.Transport.(*http.Transport) @@ -400,31 +412,40 @@ func TestNewRequestHTTPClient_SetServerName(t *testing.T) { t.Fatalf("expected *http.Transport, got %T", c.client.Transport) } - if transport.TLSClientConfig == nil { - t.Fatal("TLSClientConfig is nil") - } + assert.NotNil(t, + transport.TLSClientConfig, + "check TLSClientConfig not nil", + ) - if transport.TLSClientConfig.ServerName != tt { - t.Errorf("expected ServerName to be %s, got %s", - tt, transport.TLSClientConfig.ServerName) - } + assert.Equal(t, + tt, + transport.TLSClientConfig.ServerName, + "check ServerName in TLSClientConfig", + ) }) } +} +func TestNewRequestHTTPClient_SetServerName_Error(t *testing.T) { testsError := []struct { + desc string serverName string errMsg string }{ { - "[::1]", - "unable to parse serverName [::1]: parse \"[::1]\": first path segment in URL cannot contain colon", + "empty serverName", + emptyString, + "serverName cannot be empty", + }, + { + "url as serverName", + "https://localhost", + "serverName should be a hostname, not a URL: https://localhost", }, } for _, tt := range testsError { - testname := fmt.Sprintf("Error: %s", tt) - - t.Run(testname, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { t.Parallel() c := NewRequestHTTPClient() From 15b1478a33e91d48a34886440e51b17c2e38d20f Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 13:25:07 +0100 Subject: [PATCH 13/30] fix: typos in tests --- internal/requests/requests_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 0e2a100..e1b5f05 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -283,11 +283,11 @@ func TestNewHTTPClientFromRequestConfig_Error(t *testing.T) { func TestNewHTTPClientFromRequestConfig(t *testing.T) { tests := []struct { - desc string - reqConf RequestConfig - serverName string - pool *x509.CertPool - tranportAddress string + desc string + reqConf RequestConfig + serverName string + pool *x509.CertPool + transportAddress string }{ { desc: "transpAddr", @@ -298,8 +298,8 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { Insecure: true, RequestMethod: http.MethodGet, }, - serverName: "localhost", - tranportAddress: "localhost:45555", + serverName: "localhost", + transportAddress: "localhost:45555", }, { desc: "proxyProto", @@ -308,8 +308,8 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { RequestMethod: http.MethodHead, EnableProxyProtocolV2: true, }, - serverName: "localhost", - tranportAddress: "localhost:8443", + serverName: "localhost", + transportAddress: "localhost:8443", }, { desc: "caPool", @@ -980,7 +980,7 @@ func TestPrintResponseDebug(t *testing.T) { assert.Equal(t, []byte(nil), buffer.Bytes(), - "check PrintRequestDebug with verbose False", + "check PrintResponseDebug with verbose False", ) } From 93bcea6cfd403f21f40fb944c64c008a7eafbbe1 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 13:51:27 +0100 Subject: [PATCH 14/30] refactor: TestPrintCmd to check for partial string match --- internal/requests/requests_test.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index e1b5f05..894d817 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -890,27 +890,29 @@ func TestRequestHTTPClient_SetProxyProtocolV2_server(t *testing.T) { } func TestPrintCmd(t *testing.T) { - tests := []struct { - verbose bool - output string - }{ - {true, "\n Requests \n"}, - {false, ""}, - } + tests := []bool{true, false} for _, tt := range tests { - testname := fmt.Sprintf("%v", tt.verbose) + testname := fmt.Sprintf("%v", tt) t.Run(testname, func(t *testing.T) { t.Parallel() buffer := bytes.Buffer{} - r := RequestsMetaConfig{RequestVerbose: tt.verbose} + r := RequestsMetaConfig{RequestVerbose: tt} r.PrintCmd(&buffer) got := buffer.String() - want := tt.output - - assert.Equal(t, want, got, "check PrintCmd") + if tt { + assert.Contains(t, got, + "Requests", + "check PrintCmd when verbose", + ) + } else { + assert.Empty(t, + got, + "check emprty outputs from PrintCmd when not verbose", + ) + } }) } } From 7035520e9029000f208f48a98c41128db22f17a9 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 14:08:40 +0100 Subject: [PATCH 15/30] refactor: remove unused error retuened by SetProxyProtocolV2 --- internal/requests/requests.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index c0be862..23821df 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -430,15 +430,15 @@ func (rc *RequestHTTPClient) SetTransportOverride(transportURL string) (*Request return rc, nil } -func (rc *RequestHTTPClient) SetProxyProtocolV2(enable bool) (*RequestHTTPClient, error) { +func (rc *RequestHTTPClient) SetProxyProtocolV2(enable bool) *RequestHTTPClient { rc.enableProxyProtoV2 = enable - return rc, nil + return rc } func (rc *RequestHTTPClient) SetProxyProtocolHeader(header proxyproto.Header) (*RequestHTTPClient, error) { if rc.transportAddress == emptyString { - return nil, errors.New("SetProxyProtocolV2 failed: transportOverrideURL not set") + return nil, errors.New("SetProxyProtocolHeader failed: transportOverrideURL not set") } if rc.client == nil { @@ -530,10 +530,7 @@ func NewHTTPClientFromRequestConfig(r RequestConfig, serverName string, caPool * return nil, fmt.Errorf("SetTransportOverride error: %w", err) } - _, err = reqClient.SetProxyProtocolV2(r.EnableProxyProtocolV2) - if err != nil { - return nil, fmt.Errorf("SetProxyProtocolV2 error: %w", err) - } + reqClient.SetProxyProtocolV2(r.EnableProxyProtocolV2) if r.EnableProxyProtocolV2 && r.TransportOverrideURL == emptyString { return nil, errors.New( From 3f7bfa4db00ec20fac6347ef663cee0096348bd3 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 14:14:16 +0100 Subject: [PATCH 16/30] fix: error checking and typos --- internal/requests/requests_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 894d817..1c9ee60 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -185,7 +185,8 @@ func TestRequestsMetaConfig_SetRequests(t *testing.T) { t.Run(testname, func(t *testing.T) { t.Parallel() - rmc, _ := NewRequestsMetaConfig() + rmc, err := NewRequestsMetaConfig() + require.NoError(t, err) rmc.SetRequests(tt) if diff := cmp.Diff(tt, rmc.Requests); diff != "" { @@ -482,9 +483,7 @@ func TestNewRequestHTTPClient_SetClientTimeout(t *testing.T) { c := NewRequestHTTPClient() _, err := c.SetClientTimeout(tt) - if err != nil { - require.Error(t, err) - } + require.Error(t, err) var i any = c.client.Timeout @@ -910,7 +909,7 @@ func TestPrintCmd(t *testing.T) { } else { assert.Empty(t, got, - "check emprty outputs from PrintCmd when not verbose", + "check empty outputs from PrintCmd when not verbose", ) } }) From 40358d38dbcce88f44f427790831f03d82d771a4 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 14:29:45 +0100 Subject: [PATCH 17/30] ci: add go code checks to GitHub Actions --- .github/workflows/codeChecks.yml | 42 ++++++++++++++++++++++++++++++++ .github/workflows/vulncheck.yml | 22 ----------------- 2 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/codeChecks.yml delete mode 100644 .github/workflows/vulncheck.yml diff --git a/.github/workflows/codeChecks.yml b/.github/workflows/codeChecks.yml new file mode 100644 index 0000000..3d3a392 --- /dev/null +++ b/.github/workflows/codeChecks.yml @@ -0,0 +1,42 @@ +--- +name: code checks +on: + push: + paths: + - "cmd/**" + - "internal/**" + - "pkg/**" + - "*.go" + - "go.*" +jobs: + code_check_job: + + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ['1.24', '1.25'] + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + + - name: Install dependencies + run: go get . + + - name: Build + run: go build -v ./... + + - name: Test with the Go CLI + run: go test -v ./... + + - name: Check for vulnerabilities + uses: golang/govulncheck-action@v1 + with: + go-version-input: ${{ matrix.go-version }} + go-package: ./... + work-dir: . diff --git a/.github/workflows/vulncheck.yml b/.github/workflows/vulncheck.yml deleted file mode 100644 index d990cbf..0000000 --- a/.github/workflows/vulncheck.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -on: - push: - paths: - - "cmd/**" - - "internal/**" - - "pkg/**" - - "*.go" - - "go.*" -jobs: - govulncheck_job: - runs-on: ubuntu-latest - name: Run govulncheck - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: govulncheck - uses: golang/govulncheck-action@v1 - with: - go-version-input: 1.24 - go-package: ./... - work-dir: . From beaca2dbae6a2ab9e6a591106c2fc5f4f386ac43 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 14:40:15 +0100 Subject: [PATCH 18/30] ci: update checkout and Setup Go actions --- .github/workflows/codeChecks.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeChecks.yml b/.github/workflows/codeChecks.yml index 3d3a392..100f27b 100644 --- a/.github/workflows/codeChecks.yml +++ b/.github/workflows/codeChecks.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 136409d..911b86f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: DOCKER_CLI_EXPERIMENTAL: "enabled" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - uses: cachix/install-nix-action@v31 @@ -28,7 +28,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GH_GORELEASER_TOKEN }} - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: 1.24 - name: Run GoReleaser From 9c4eb6d2fd948bdc2778dd02e1786f06076744b9 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 15:00:15 +0100 Subject: [PATCH 19/30] fix: error assertion in SetClientTimeout test --- internal/requests/requests_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 1c9ee60..884eadb 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -482,8 +482,7 @@ func TestNewRequestHTTPClient_SetClientTimeout(t *testing.T) { c := NewRequestHTTPClient() - _, err := c.SetClientTimeout(tt) - require.Error(t, err) + c.SetClientTimeout(tt) var i any = c.client.Timeout From 23d605b9833b9cc94f36d0432ef51bd7d70d6e0d Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 16:48:16 +0100 Subject: [PATCH 20/30] ci: wrap go-version in release action and update go.mod --- .github/workflows/release.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 911b86f..579aa8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: 1.24 + go-version: '1.24.9' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: diff --git a/go.mod b/go.mod index 5bde940..c744827 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/xenos76/https-wrench -go 1.24.4 +go 1.24.9 require ( github.com/alecthomas/assert/v2 v2.11.0 From 463ba5827838c13ff29fc1274c2f2106d6d45679 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 17:40:10 +0100 Subject: [PATCH 21/30] refactor: error handling --- internal/requests/requests_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 884eadb..92a049f 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -332,7 +332,7 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { tt.pool, ) if err != nil { - t.Error(err) + t.Fatal(err) } var i any = rcClient.client From e20fa0920e5bb6bcbafaaa141259c2fc5e099ce5 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 17:41:20 +0100 Subject: [PATCH 22/30] refactor: validate transportAddress value returned by RequestConfig HTTP client --- internal/requests/requests_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 92a049f..8d7ecff 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -360,6 +360,14 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { "check proxy proto enabled", ) + if tt.transportAddress != emptyString { + assert.Equal(t, + tt.transportAddress, + rcClient.transportAddress, + "check transportAddress", + ) + } + var ti any = rcClient.client.Transport transport, ok := ti.(*http.Transport) From e3f9841e0c6768b35e581512020a851d393fcc48 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 17:43:14 +0100 Subject: [PATCH 23/30] refactor: validate on empty byte list instead of comparing when PrintResponseDebug is false --- internal/requests/requests_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 8d7ecff..291febf 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -985,8 +985,7 @@ func TestPrintResponseDebug(t *testing.T) { fmt.Printf("got:\n%s\n", got) if !tt.verbose && len(got) == 0 { - assert.Equal(t, - []byte(nil), + assert.Empty(t, buffer.Bytes(), "check PrintResponseDebug with verbose False", ) From 0e46054982114ca73f7b536ae60136088a5eb090 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 17:44:00 +0100 Subject: [PATCH 24/30] refactor: update test struct to manage cases causing errors --- internal/requests/requests_test.go | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 291febf..f41da9d 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -1045,10 +1045,11 @@ func TestPrintRequestDebug(t *testing.T) { expectedOutputIncomplete := "Warning: failed to dump request: http: nil Request.Header\n" tests := []struct { - desc string - verbose bool - request *http.Request - output string + desc string + verbose bool + request *http.Request + output string + expectErr bool }{ { desc: "verboseTrue", @@ -1064,16 +1065,18 @@ func TestPrintRequestDebug(t *testing.T) { output: emptyString, }, { - desc: "nilRequestError", - verbose: true, - request: requestTestNilPointer, - output: emptyString, + desc: "nilRequestError", + verbose: true, + request: requestTestNilPointer, + output: emptyString, + expectErr: true, }, { - desc: "incompleteRequestError", - verbose: true, - request: &requestTestIncomplete, - output: expectedOutputIncomplete, + desc: "incompleteRequestError", + verbose: true, + request: &requestTestIncomplete, + output: expectedOutputIncomplete, + expectErr: true, }, } @@ -1085,8 +1088,11 @@ func TestPrintRequestDebug(t *testing.T) { r := RequestConfig{RequestDebug: tt.verbose} err := r.PrintRequestDebug(&buffer, tt.request) - if err != nil { - require.Error(t, err, "PrintRequestDebug error") + + if tt.expectErr { + require.Error(t, err, "PrintRequestDebug should return an error") + } else { + require.NoError(t, err, "PrintRequestDebug should not return an error") } got := buffer.String() From d4203ab01e5be291bdd11cf9edbf1201f3799161 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 20:14:40 +0100 Subject: [PATCH 25/30] refactor: test loops using t.Parallel() to import a local copy of the test case variable --- internal/requests/requests_test.go | 63 ++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index f41da9d..91828e8 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -52,7 +52,8 @@ func TestNewRequestsMetaConfig(t *testing.T) { func TestRequestsMetaConfig_SetVerbose(t *testing.T) { tests := []bool{true, false} - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("SetVerbose(%v)", tt) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -67,7 +68,8 @@ func TestRequestsMetaConfig_SetVerbose(t *testing.T) { func TestRequestsMetaConfig_SetDebug(t *testing.T) { tests := []bool{true, false} - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("SetDebug(%v)", tt) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -92,7 +94,8 @@ func TestRequestsMetaConfig_SetCaPoolFromYAML(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("SetCaPoolFromYAML(%v)", tt.desc) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -137,7 +140,8 @@ func TestRequestsMetaConfig_SetCaPoolFromFile(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("SetCaPoolFromFile(%v)", tt.desc) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -180,7 +184,8 @@ func TestRequestsMetaConfig_SetRequests(t *testing.T) { tests := [][]RequestConfig{requestConfigs} - for _, tt := range tests { + for _, tc := range tests { + tt := tc // use a local copy of tc should be safer when using t.Parallel() testname := fmt.Sprintf("SetRequests(%v)", tt[0].Name) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -264,7 +269,8 @@ func TestNewHTTPClientFromRequestConfig_Error(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.desc, func(t *testing.T) { t.Parallel() @@ -322,7 +328,8 @@ func TestNewHTTPClientFromRequestConfig(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.desc, func(t *testing.T) { t.Parallel() @@ -403,7 +410,8 @@ func TestNewRequestHTTPClient_SetServerName(t *testing.T) { " a silly string ", } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -453,7 +461,8 @@ func TestNewRequestHTTPClient_SetServerName_Error(t *testing.T) { }, } - for _, tt := range testsError { + for _, tc := range testsError { + tt := tc // safer when using t.Parallel() t.Run(tt.desc, func(t *testing.T) { t.Parallel() @@ -483,7 +492,8 @@ func TestNewRequestHTTPClient_SetClientTimeout(t *testing.T) { 3, 0, 50, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -550,7 +560,8 @@ func TestNewRequestHTTPClient_SetCaCertsPool(t *testing.T) { {"System Cert Pool", defaultCertPool, defaultCertPool}, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.testname, func(t *testing.T) { t.Parallel() @@ -576,7 +587,8 @@ func TestNewRequestHTTPClient_SetCaCertsPool(t *testing.T) { func TestNewRequestHTTPClient_SetInsecureSkipVerify_struct(t *testing.T) { tests := []bool{true, false} - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt) t.Run(testname, func(t *testing.T) { @@ -605,7 +617,8 @@ func TestNewRequestHTTPClient_SetInsecureSkipVerify_struct(t *testing.T) { func TestNewRequestHTTPClient_SetInsecureSkipVerify_tlsServer(t *testing.T) { tests := []bool{true, false} - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt) t.Run(testname, func(t *testing.T) { @@ -653,7 +666,8 @@ func TestNewRequestHTTPClient_SetMethod(t *testing.T) { {"post", http.MethodPost}, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt.got) t.Run(testname, func(t *testing.T) { @@ -679,7 +693,8 @@ func TestRequestHTTPClient_SetTransportOverride_transportAddress_struc(t *testin {"https://example.com", "example.com:443"}, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt.got) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -716,7 +731,8 @@ func TestRequestHTTPClient_SetTransportOverride_transportAddress_server(t *testi }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt.trasportURL) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -812,7 +828,8 @@ func TestRequestHTTPClient_SetProxyProtocolV2_server(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.testname, func(t *testing.T) { t.Parallel() @@ -898,7 +915,8 @@ func TestRequestHTTPClient_SetProxyProtocolV2_server(t *testing.T) { func TestPrintCmd(t *testing.T) { tests := []bool{true, false} - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() testname := fmt.Sprintf("%v", tt) t.Run(testname, func(t *testing.T) { t.Parallel() @@ -950,7 +968,8 @@ func TestPrintResponseDebug(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.desc, func(t *testing.T) { t.Parallel() @@ -1080,7 +1099,8 @@ func TestPrintRequestDebug(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.desc, func(t *testing.T) { t.Parallel() @@ -1163,7 +1183,8 @@ func TestProcessHTTPRequestsByHost(t *testing.T) { }, } - for _, tt := range tests { + for _, tc := range tests { + tt := tc // safer when using t.Parallel() t.Run(tt.reqConf.Name, func(t *testing.T) { t.Parallel() From b5a27a748014a57cb5618643d8a69fe3ec839c02 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 21:44:49 +0100 Subject: [PATCH 26/30] ci: test errors in PrintResponseDebug --- internal/requests/requests_test.go | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 91828e8..56a4a00 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -1022,6 +1022,52 @@ func TestPrintResponseDebug(t *testing.T) { } } +func TestPrintResponseDebug_Error(t *testing.T) { + t.Run("NilResponse", func(t *testing.T) { + rc := RequestConfig{ResponseDebug: true} + buffer := bytes.Buffer{} + rc.PrintResponseDebug(&buffer, nil) + + got := buffer.String() + assert.Empty(t, + got, + "output should be empty when Response is nil", + ) + }) + + // TODO: trigger error on malformed Response + // + // t.Run("MalformedResponse", func(t *testing.T) { + // httpTestHeader := http.Header{} + // httpTestHeader.Add("user-agent", "go-test") + // + // httpTestRequest := http.Request{ + // // Method: http.MethodGet, + // // URL: &url.URL{Scheme: "https", Host: "localhost"}, + // ContentLength: 300, + // Body: nil, + // } + // + // response := http.Response{ + // // Status: "200 OK", + // // StatusCode: 200, + // // Header: httpTestHeader, + // Request: &httpTestRequest, + // } + // rc := RequestConfig{ResponseDebug: true} + // buffer := bytes.Buffer{} + // rc.PrintResponseDebug(&buffer, &response) + // + // got := buffer.String() + // fmt.Printf("got:\n%s\n", got) + // assert.Contains(t, + // got, + // "Warning: failed to dump response:", + // "check PrintResponseDebug: MalformedResponse", + // ) + // }) +} + func TestPrintResponseDebug_nonTLS(t *testing.T) { t.Run("non-TLS", func(t *testing.T) { respURL := url.URL{Scheme: "http", Host: "localhost"} From e9b5db3301f189550dfc2c482c62f6e00cf75b21 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 21:46:06 +0100 Subject: [PATCH 27/30] refactor: replace assert with require in SetInsecureSkipVerify --- internal/requests/requests_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 56a4a00..e80c298 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -640,7 +640,7 @@ func TestNewRequestHTTPClient_SetInsecureSkipVerify_tlsServer(t *testing.T) { } if tt { - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) } }) From de18e7100297020ad6aaba42045d139bda4078a9 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 21:46:48 +0100 Subject: [PATCH 28/30] ci: test malformed HTTP client in SetServerName --- internal/requests/requests_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index e80c298..7e009b6 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -443,6 +443,21 @@ func TestNewRequestHTTPClient_SetServerName(t *testing.T) { } } +func TestNewRequestHTTPClient_SetServerName_clientError(t *testing.T) { + t.Run("malformedClient", func(t *testing.T) { + t.Parallel() + + noTransportClient := http.Client{} + c := NewRequestHTTPClient() + + c.client = &noTransportClient + + _, err := c.SetServerName("localhost") + + require.Error(t, err) + }) +} + func TestNewRequestHTTPClient_SetServerName_Error(t *testing.T) { testsError := []struct { desc string From 969f42b5f6c4c19f2f10adb4465fcfdf79253674 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 22:57:52 +0100 Subject: [PATCH 29/30] ci: add tests to check nil or incomplete HTTP client in RequestHTTPClient methods --- internal/requests/requests_test.go | 153 +++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 7e009b6..b5e8132 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/pires/go-proxyproto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -600,6 +601,40 @@ func TestNewRequestHTTPClient_SetCaCertsPool(t *testing.T) { } } +func TestNewRequestHTTPClient_SetCaCertsPool_Error(t *testing.T) { + t.Run("nilClient", func(t *testing.T) { + t.Parallel() + + var c RequestHTTPClient + + _, err := c.SetCACertsPool(caCertPool) + + require.Error(t, err) + assert.Equal(t, + "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize", + err.Error(), + ) + }) + + t.Run("malformedClient", func(t *testing.T) { + t.Parallel() + + incompleteClient := http.Client{} + c := NewRequestHTTPClient() + + c.client = &incompleteClient + + _, err := c.SetCACertsPool(caCertPool) + + require.Error(t, err) + + assert.Equal(t, + "expected *http.Transport, got ", + err.Error(), + ) + }) +} + func TestNewRequestHTTPClient_SetInsecureSkipVerify_struct(t *testing.T) { tests := []bool{true, false} for _, tc := range tests { @@ -662,6 +697,40 @@ func TestNewRequestHTTPClient_SetInsecureSkipVerify_tlsServer(t *testing.T) { } } +func TestNewRequestHTTPClient_SetInsecureSkipVerify_Error(t *testing.T) { + t.Run("nilClient", func(t *testing.T) { + t.Parallel() + + var c RequestHTTPClient + + _, err := c.SetInsecureSkipVerify(true) + + require.Error(t, err) + assert.Equal(t, + "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize", + err.Error(), + ) + }) + + t.Run("malformedClient", func(t *testing.T) { + t.Parallel() + + incompleteClient := http.Client{} + c := NewRequestHTTPClient() + + c.client = &incompleteClient + + _, err := c.SetInsecureSkipVerify(true) + + require.Error(t, err) + + assert.Equal(t, + "expected *http.Transport, got ", + err.Error(), + ) + }) +} + func TestNewRequestHTTPClient_SetMethod(t *testing.T) { tests := []struct { got string @@ -724,6 +793,39 @@ func TestRequestHTTPClient_SetTransportOverride_transportAddress_struc(t *testin } } +func TestRequestHTTPClient_SetTransportOverride_Error(t *testing.T) { + t.Run("nilClient", func(t *testing.T) { + t.Parallel() + + var c RequestHTTPClient + + _, err := c.SetTransportOverride("http://localhost") + require.Error(t, err) + assert.Equal(t, + "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize", + err.Error(), + ) + }) + + t.Run("malformedClient", func(t *testing.T) { + t.Parallel() + + incompleteClient := http.Client{} + c := NewRequestHTTPClient() + + c.client = &incompleteClient + + _, err := c.SetTransportOverride("http://localhost") + + require.Error(t, err) + + assert.Equal(t, + "expected *http.Transport, got ", + err.Error(), + ) + }) +} + // Test SetTransportOverride method for RequestHTTPClient. // Use case: we want our client to redirect HTTPS request meant for https://hostname to a // third party proxy. This in order to test the settings of that proxy before pointing to it the DNS @@ -927,6 +1029,57 @@ func TestRequestHTTPClient_SetProxyProtocolV2_server(t *testing.T) { } } +func TestRequestHTTPClient_SetProxyProtocolHeader_Error(t *testing.T) { + t.Run("nilClient", func(t *testing.T) { + t.Parallel() + + c := RequestHTTPClient{transportAddress: "127.0.0.1:443"} + header := proxyproto.Header{} + _, err := c.SetProxyProtocolHeader(header) + + require.Error(t, err) + assert.Equal(t, + "*RequestHTTPClient.client is nil. Use NewRequestHTTPClient to initialize", + err.Error(), + ) + }) + + t.Run("malformedClient", func(t *testing.T) { + t.Parallel() + + incompleteClient := http.Client{} + c := NewRequestHTTPClient() + c.transportAddress = "127.0.0.1:443" + c.client = &incompleteClient + + header := proxyproto.Header{} + _, err := c.SetProxyProtocolHeader(header) + require.Error(t, err) + + assert.Equal(t, + "expected *http.Transport, got ", + err.Error(), + ) + }) + + t.Run("noTransportAddress", func(t *testing.T) { + t.Parallel() + + incompleteClient := http.Client{} + c := NewRequestHTTPClient() + c.client = &incompleteClient + + header := proxyproto.Header{} + _, err := c.SetProxyProtocolHeader(header) + require.Error(t, err) + + assert.Equal(t, + "SetProxyProtocolHeader failed: transportOverrideURL not set", + err.Error(), + ) + }) +} + func TestPrintCmd(t *testing.T) { tests := []bool{true, false} From cdf659e74204ab69f35908c210dcedcd6b825d9b Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 29 Nov 2025 23:10:33 +0100 Subject: [PATCH 30/30] refactor: require no error in SetClientTimeout --- internal/requests/requests_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index b5e8132..95630ad 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -516,7 +516,8 @@ func TestNewRequestHTTPClient_SetClientTimeout(t *testing.T) { c := NewRequestHTTPClient() - c.SetClientTimeout(tt) + _, err := c.SetClientTimeout(tt) + require.NoError(t, err) var i any = c.client.Timeout