chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/github/github-mcp-server/pkg/raw"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
"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 (
|
||||
resourceResponseTypeUnknown resourceResponseType = iota
|
||||
resourceResponseTypeBlob
|
||||
resourceResponseTypeText
|
||||
)
|
||||
|
||||
func Test_repositoryResourceContents(t *testing.T) {
|
||||
base, _ := url.Parse("https://raw.example.com/")
|
||||
tests := []struct {
|
||||
name string
|
||||
mockedClient *http.Client
|
||||
uri string
|
||||
handlerFn func() mcp.ResourceHandler
|
||||
expectedResponseType resourceResponseType
|
||||
expectError string
|
||||
expectedResult *mcp.ReadResourceResult
|
||||
}{
|
||||
{
|
||||
name: "missing owner",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo:///repo/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText, // Ignored as error is expected
|
||||
expectError: "owner is required",
|
||||
},
|
||||
{
|
||||
name: "missing repo",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner//refs/heads/main/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceBranchContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText, // Ignored as error is expected
|
||||
expectError: "repo is required",
|
||||
},
|
||||
{
|
||||
name: "successful blob content fetch",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/contents/data.png",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeBlob,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Blob: []byte("IyBUZXN0IFJlcG9zaXRvcnkKClRoaXMgaXMgYSB0ZXN0IHJlcG9zaXRvcnku"),
|
||||
MIMEType: "image/png",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "successful text content fetch (HEAD)",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Text: "# Test Repository\n\nThis is a test repository.",
|
||||
MIMEType: "text/markdown",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "successful text content fetch (HEAD)",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
||||
require.Contains(t, r.URL.Path, "pkg/github/actions.go")
|
||||
_, err := w.Write([]byte("package actions\n\nfunc main() {\n // Sample Go file content\n}\n"))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/contents/pkg/github/actions.go",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Text: "package actions\n\nfunc main() {\n // Sample Go file content\n}\n",
|
||||
MIMEType: "text/plain",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "successful text content fetch (branch)",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByBranchByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/refs/heads/main/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceBranchContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Text: "# Test Repository\n\nThis is a test repository.",
|
||||
MIMEType: "text/markdown",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "successful text content fetch (tag)",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoByTagByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/refs/tags/v1.0.0/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceTagContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Text: "# Test Repository\n\nThis is a test repository.",
|
||||
MIMEType: "text/markdown",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "successful text content fetch (sha)",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetRawReposContentsByOwnerByRepoBySHAByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/sha/abc123/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceCommitContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Text: "# Test Repository\n\nThis is a test repository.",
|
||||
MIMEType: "text/markdown",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "successful text content fetch (pr)",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetReposPullsByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write([]byte(`{"head": {"sha": "abc123"}}`))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
GetRawReposContentsByOwnerByRepoBySHAByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/markdown")
|
||||
_, err := w.Write([]byte("# Test Repository\n\nThis is a test repository."))
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/refs/pull/42/head/contents/README.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourcePrContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText,
|
||||
expectedResult: &mcp.ReadResourceResult{
|
||||
Contents: []*mcp.ResourceContents{{
|
||||
Text: "# Test Repository\n\nThis is a test repository.",
|
||||
MIMEType: "text/markdown",
|
||||
URI: "",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "content fetch fails",
|
||||
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
|
||||
GetReposContentsByOwnerByRepoByPath: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message": "Not Found"}`))
|
||||
}),
|
||||
}),
|
||||
uri: "repo://owner/repo/contents/nonexistent.md",
|
||||
handlerFn: func() mcp.ResourceHandler {
|
||||
return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate)
|
||||
},
|
||||
expectedResponseType: resourceResponseTypeText, // Ignored as error is expected
|
||||
expectError: "404 Not Found",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := mustNewGHClient(t, tc.mockedClient)
|
||||
mockRawClient, err := raw.NewClient(client, base)
|
||||
require.NoError(t, err)
|
||||
deps := BaseDeps{
|
||||
Client: client,
|
||||
RawClient: mockRawClient,
|
||||
}
|
||||
ctx := ContextWithDeps(context.Background(), deps)
|
||||
handler := tc.handlerFn()
|
||||
|
||||
request := &mcp.ReadResourceRequest{
|
||||
Params: &mcp.ReadResourceParams{
|
||||
URI: tc.uri,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := handler(ctx, request)
|
||||
|
||||
if tc.expectError != "" {
|
||||
require.ErrorContains(t, err, tc.expectError)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
content := resp.Contents[0]
|
||||
switch tc.expectedResponseType {
|
||||
case resourceResponseTypeBlob:
|
||||
require.Equal(t, tc.expectedResult.Contents[0].Blob, content.Blob)
|
||||
case resourceResponseTypeText:
|
||||
require.Equal(t, tc.expectedResult.Contents[0].Text, content.Text)
|
||||
default:
|
||||
t.Fatalf("unknown expectedResponseType %v", tc.expectedResponseType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := mustNewGHClient(t, httpClient)
|
||||
mockRawClient, err := raw.NewClient(client, base)
|
||||
require.NoError(t, err)
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user