From e1081ae3f61b0ad6edb2ab95745788eb0b36f63c Mon Sep 17 00:00:00 2001 From: William Martin Date: Thu, 8 Oct 2026 17:58:05 +0200 Subject: [PATCH 1/2] Fix deprecated API lint findings Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- pkg/api/http_client.go | 10 ++++--- pkg/api/http_client_test.go | 39 ++++++++++++++++++++++++++++ pkg/x/markdown/accessibility_test.go | 4 +-- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/pkg/api/http_client.go b/pkg/api/http_client.go index 43193734..e87ebe8f 100644 --- a/pkg/api/http_client.go +++ b/pkg/api/http_client.go @@ -1,6 +1,7 @@ package api import ( + "context" "fmt" "io" "net" @@ -235,13 +236,14 @@ func (hrt headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, erro } func newUnixDomainSocketRoundTripper(socketPath string) http.RoundTripper { - dial := func(network, addr string) (net.Conn, error) { - return net.Dial("unix", socketPath) + dial := func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, "unix", socketPath) } return &http.Transport{ - Dial: dial, - DialTLS: dial, + DialContext: dial, + DialTLSContext: dial, DisableKeepAlives: true, } } diff --git a/pkg/api/http_client_test.go b/pkg/api/http_client_test.go index 557e1c60..8d9032f7 100644 --- a/pkg/api/http_client_test.go +++ b/pkg/api/http_client_test.go @@ -4,12 +4,16 @@ import ( "bytes" "fmt" "io" + "net" "net/http" + "os" + "path/filepath" "strings" "testing" "github.com/cli/go-gh/v2/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/h2non/gock.v1" ) @@ -321,6 +325,41 @@ func TestNewHTTPClientCheckRedirect(t *testing.T) { }) } +func TestNewHTTPClientUnixDomainSocket(t *testing.T) { + // t.TempDir() can exceed the 104-byte socket path limit on macOS. + dir, err := os.MkdirTemp("", "gh") + require.NoError(t, err) + t.Cleanup(func() { _ = os.RemoveAll(dir) }) + socketPath := filepath.Join(dir, "gh.sock") + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "%s %s", r.Host, r.URL.Path) + })} + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { _ = server.Close() }) + + client, err := NewHTTPClient(ClientOptions{ + Host: "github.com", + AuthToken: "oauth_token", + UnixDomainSocket: socketPath, + }) + assert.NoError(t, err) + + // Both plain and TLS URLs are routed over the socket as plain HTTP. + for _, url := range []string{"http://api.github.com/user", "https://api.github.com/user"} { + t.Run(url, func(t *testing.T) { + res, err := client.Get(url) + assert.NoError(t, err) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + assert.Equal(t, "api.github.com /user", string(body)) + }) + } +} + type tripper struct { roundTrip func(*http.Request) (*http.Response, error) } diff --git a/pkg/x/markdown/accessibility_test.go b/pkg/x/markdown/accessibility_test.go index 5e0ccc42..95294b8e 100644 --- a/pkg/x/markdown/accessibility_test.go +++ b/pkg/x/markdown/accessibility_test.go @@ -192,7 +192,7 @@ func TestAccessibleLightStyleConfigIs4Bit(t *testing.T) { // that are downsampled to 4-bit colors unlike Glamour, which are 8-bit colors. // For more information, https://github.com/alecthomas/chroma/blob/0bf0e9f9ae2a81d463afe769cce01ff821bee3ba/formatters/tty_indexed.go#L32-L44 func validateColors(t *testing.T, v reflect.Value, path string) { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { if v.IsNil() { return } @@ -212,7 +212,7 @@ func validateColors(t *testing.T, v reflect.Value, path string) { if fieldType.Name == "Chroma" { continue } else if (fieldType.Name == "Color" || fieldType.Name == "BackgroundColor") && - fieldType.Type.Kind() == reflect.Ptr && fieldType.Type.Elem().Kind() == reflect.String { + fieldType.Type.Kind() == reflect.Pointer && fieldType.Type.Elem().Kind() == reflect.String { if field.IsNil() { continue From 47fd94d4e7ee5812c2f8108dd5b16dc00adef148 Mon Sep 17 00:00:00 2001 From: William Martin Date: Thu, 8 Oct 2026 19:35:26 +0200 Subject: [PATCH 2/2] Split unix socket test into behavioural scenarios Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- pkg/api/http_client_test.go | 79 ++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/pkg/api/http_client_test.go b/pkg/api/http_client_test.go index 8d9032f7..38f2868f 100644 --- a/pkg/api/http_client_test.go +++ b/pkg/api/http_client_test.go @@ -325,39 +325,70 @@ func TestNewHTTPClientCheckRedirect(t *testing.T) { }) } -func TestNewHTTPClientUnixDomainSocket(t *testing.T) { - // t.TempDir() can exceed the 104-byte socket path limit on macOS. - dir, err := os.MkdirTemp("", "gh") +func TestNewHTTPClientSendsHTTPRequestsThroughUnixDomainSocket(t *testing.T) { + t.Parallel() + + // Given a server listening on a unix socket and a client configured to use it + socketPath := serveOnUnixSocket(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "socket server received %s%s", r.Host, r.URL.Path) + })) + client, err := NewHTTPClient(ClientOptions{ + Host: "github.com", + AuthToken: "oauth_token", + UnixDomainSocket: socketPath, + }) require.NoError(t, err) - t.Cleanup(func() { _ = os.RemoveAll(dir) }) - socketPath := filepath.Join(dir, "gh.sock") - listener, err := net.Listen("unix", socketPath) + + // When the client requests an http URL whose host cannot resolve + res, err := client.Get("http://unresolvable.invalid/user") + + // Then the request is delivered to the socket server require.NoError(t, err) - server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "%s %s", r.Host, r.URL.Path) - })} - go func() { _ = server.Serve(listener) }() - t.Cleanup(func() { _ = server.Close() }) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, "socket server received unresolvable.invalid/user", string(body)) +} +func TestNewHTTPClientSendsHTTPSRequestsThroughUnixDomainSocketWithoutTLS(t *testing.T) { + t.Parallel() + + // Given a plain HTTP server listening on a unix socket and a client configured to use it + socketPath := serveOnUnixSocket(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "socket server received %s%s", r.Host, r.URL.Path) + })) client, err := NewHTTPClient(ClientOptions{ Host: "github.com", AuthToken: "oauth_token", UnixDomainSocket: socketPath, }) - assert.NoError(t, err) + require.NoError(t, err) - // Both plain and TLS URLs are routed over the socket as plain HTTP. - for _, url := range []string{"http://api.github.com/user", "https://api.github.com/user"} { - t.Run(url, func(t *testing.T) { - res, err := client.Get(url) - assert.NoError(t, err) - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - assert.NoError(t, err) - assert.Equal(t, http.StatusOK, res.StatusCode) - assert.Equal(t, "api.github.com /user", string(body)) - }) - } + // When the client requests an https URL whose host cannot resolve + res, err := client.Get("https://unresolvable.invalid/user") + + // Then the request is delivered to the socket server as plain HTTP + require.NoError(t, err) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, "socket server received unresolvable.invalid/user", string(body)) +} + +// serveOnUnixSocket serves handler on a new unix socket and returns its path. +func serveOnUnixSocket(t *testing.T, handler http.Handler) string { + t.Helper() + // t.TempDir() can exceed the 104-byte socket path limit on macOS. + dir, err := os.MkdirTemp("", "gh") + require.NoError(t, err) + t.Cleanup(func() { _ = os.RemoveAll(dir) }) + socketPath := filepath.Join(dir, "gh.sock") + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + server := &http.Server{Handler: handler} + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { _ = server.Close() }) + return socketPath } type tripper struct {