diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index 8b515d1b4..be86cc451 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -191,13 +191,14 @@ func RepositoryResourceContentsHandler(resourceURITemplate *uritemplate.Template } resp, err := rawClient.GetRawContent(ctx, owner, repo, path, rawOpts) + if err != nil { + return nil, fmt.Errorf("failed to get raw content: %w", err) + } defer func() { _ = resp.Body.Close() }() // If the raw content is not found, we will fall back to the GitHub API (in case it is a directory) switch { - case err != nil: - return nil, fmt.Errorf("failed to get raw content: %w", err) case resp.StatusCode == http.StatusOK: ext := filepath.Ext(path) mimeType := resp.Header.Get("Content-Type") diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index b032554d8..f0fba30df 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -2,6 +2,7 @@ package github import ( "context" + "errors" "net/http" "net/url" "testing" @@ -12,6 +13,15 @@ import ( "github.com/stretchr/testify/require" ) +// errorTransport is a http.RoundTripper that always returns an error. +type errorTransport struct { + err error +} + +func (t *errorTransport) RoundTrip(*http.Request) (*http.Response, error) { + return nil, t.err +} + type resourceResponseType int const ( @@ -272,3 +282,33 @@ func Test_repositoryResourceContents(t *testing.T) { }) } } + +// Test_repositoryResourceContentsHandler_NetworkError tests that a network error +// during raw content fetch does not cause a panic (nil response body dereference). +func Test_repositoryResourceContentsHandler_NetworkError(t *testing.T) { + base, _ := url.Parse("https://raw.example.com/") + networkErr := errors.New("network error: connection refused") + + httpClient := &http.Client{Transport: &errorTransport{err: networkErr}} + client := github.NewClient(httpClient) + mockRawClient := raw.NewClient(client, base) + deps := BaseDeps{ + Client: client, + RawClient: mockRawClient, + } + ctx := ContextWithDeps(context.Background(), deps) + + handler := RepositoryResourceContentsHandler(repositoryResourceContentURITemplate) + + request := &mcp.ReadResourceRequest{ + Params: &mcp.ReadResourceParams{ + URI: "repo://owner/repo/contents/README.md", + }, + } + + // This should not panic, even though the HTTP client returns an error + resp, err := handler(ctx, request) + require.Error(t, err) + require.Nil(t, resp) + require.ErrorContains(t, err, "failed to get raw content") +}