diff --git a/github/repos_contents.go b/github/repos_contents.go index 6d6a437b93c..9932deb873e 100644 --- a/github/repos_contents.go +++ b/github/repos_contents.go @@ -20,6 +20,15 @@ import ( "strings" ) +// ErrContentsDirectory indicates that the contents are not available for a directory. +var ErrContentsDirectory = errors.New("contents not available for directory") + +// ErrContentsSubmodule indicates that the contents are not available for a submodule. +var ErrContentsSubmodule = errors.New("contents not available for submodule") + +// ErrContentsNoDownloadURL indicates that the contents download URL is empty, which may occur when file size > 100 MB. +var ErrContentsNoDownloadURL = errors.New("contents download url is empty") + // RepositoryContent represents a file or directory in a github repository. type RepositoryContent struct { Type *string `json:"type,omitempty"` @@ -130,6 +139,10 @@ func (s *RepositoriesService) GetReadme(ctx context.Context, owner, repo string, // returned error is nil. Callers should check the returned Response status // code to verify the content is from a successful response. // +// DownloadContents returns [ErrContentsDirectory] if the path references a +// directory, [ErrContentsSubmodule] if the path references a submodule, and +// [ErrContentsNoDownloadURL] if the file's download URL is empty. +// // GitHub API docs: https://docs.github.com/rest/repos/contents?apiVersion=2022-11-28#get-repository-content // //meta:operation GET /repos/{owner}/{repo}/contents/{path} @@ -147,6 +160,11 @@ func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, // returned error is nil. Callers should check the returned Response status // code to verify the content is from a successful response. // +// DownloadContentsWithMeta returns [ErrContentsDirectory] if the path +// references a directory, [ErrContentsSubmodule] if the path references a +// submodule, and [ErrContentsNoDownloadURL] if the file's download URL is +// empty. +// // GitHub API docs: https://docs.github.com/rest/repos/contents?apiVersion=2022-11-28#get-repository-content // //meta:operation GET /repos/{owner}/{repo}/contents/{path} @@ -157,7 +175,11 @@ func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owne } if fileContent == nil { - return nil, nil, resp, errors.New("no file content found") + return nil, nil, resp, ErrContentsDirectory + } + + if fileContent.GetType() == "submodule" { + return nil, fileContent, resp, ErrContentsSubmodule } content, err := fileContent.GetContent() @@ -167,7 +189,7 @@ func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owne downloadURL := fileContent.GetDownloadURL() if downloadURL == "" { - return nil, fileContent, resp, errors.New("download url is empty") + return nil, fileContent, resp, ErrContentsNoDownloadURL } dlReq, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil) diff --git a/github/repos_contents_test.go b/github/repos_contents_test.go index bd0f1562779..dcaf602411e 100644 --- a/github/repos_contents_test.go +++ b/github/repos_contents_test.go @@ -229,7 +229,7 @@ func TestRepositoriesService_DownloadContents_SuccessByDownload(t *testing.T) { }) } -func TestRepositoriesService_DownloadContents_FailedResponse(t *testing.T) { +func TestRepositoriesService_DownloadContents_FailedDownloadResponse(t *testing.T) { t.Parallel() client, mux, serverURL := setup(t) @@ -269,26 +269,50 @@ func TestRepositoriesService_DownloadContents_FailedResponse(t *testing.T) { } } -func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) { +func TestRepositoriesService_DownloadContents_NotFound(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/repos/o/r/contents/d/f", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - fmt.Fprint(w, `{ + w.WriteHeader(http.StatusNotFound) + }) + + ctx := t.Context() + reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil) + if err == nil { + t.Error("Repositories.DownloadContents did not return expected error") + } + + if reader != nil { + t.Error("Repositories.DownloadContents did not return expected reader") + } + + if resp == nil || resp.Response.StatusCode != http.StatusNotFound { + t.Error("Repositories.DownloadContents did not return expected response") + } +} + +func TestRepositoriesService_DownloadContents_NotFile(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ "type": "file", "name": "f", "content": "" -}`) +}]`) }) ctx := t.Context() - reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil) - if err == nil { + reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d", nil) + if err == nil || !errors.Is(err, ErrContentsDirectory) { t.Error("Repositories.DownloadContents did not return expected error") } - if resp == nil { + if resp == nil || resp.Response.StatusCode != http.StatusOK { t.Error("Repositories.DownloadContents did not return expected response") } @@ -297,22 +321,28 @@ func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) { } } -func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) { +func TestRepositoriesService_DownloadContents_Submodule(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - mux.HandleFunc("/repos/o/r/contents/d/f", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, `{ + "type": "submodule", + "name": "f", + "content": "", + "download_url": "", + "submodule_git_url": "http://example.com/submodule.git" +}`) }) ctx := t.Context() - reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil) - if err == nil { + reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d", nil) + if err == nil || !errors.Is(err, ErrContentsSubmodule) { t.Error("Repositories.DownloadContents did not return expected error") } - if resp == nil { + if resp == nil || resp.Response.StatusCode != http.StatusOK { t.Error("Repositories.DownloadContents did not return expected response") } @@ -321,26 +351,26 @@ func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) { } } -func TestRepositoriesService_DownloadContents_NotFile(t *testing.T) { +func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/contents/d/f", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - fmt.Fprint(w, `[{ + fmt.Fprint(w, `{ "type": "file", "name": "f", "content": "" -}]`) +}`) }) ctx := t.Context() - reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d", nil) - if err == nil { + reader, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil) + if err == nil || !errors.Is(err, ErrContentsNoDownloadURL) { t.Error("Repositories.DownloadContents did not return expected error") } - if resp == nil { + if resp == nil || resp.Response.StatusCode != http.StatusOK { t.Error("Repositories.DownloadContents did not return expected response") } @@ -456,7 +486,7 @@ func TestRepositoriesService_DownloadContentsWithMeta_SuccessByDownload(t *testi } } -func TestRepositoriesService_DownloadContentsWithMeta_FailedResponse(t *testing.T) { +func TestRepositoriesService_DownloadContentsWithMeta_FailedDownloadResponse(t *testing.T) { t.Parallel() client, mux, serverURL := setup(t) @@ -503,17 +533,13 @@ func TestRepositoriesService_DownloadContentsWithMeta_FailedResponse(t *testing. } } -func TestRepositoriesService_DownloadContentsWithMeta_NoDownloadURL(t *testing.T) { +func TestRepositoriesService_DownloadContentsWithMeta_NotFound(t *testing.T) { t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/repos/o/r/contents/d/f", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - fmt.Fprint(w, `{ - "type": "file", - "name": "f", - "content": "" -}`) + w.WriteHeader(http.StatusNotFound) }) ctx := t.Context() @@ -526,55 +552,109 @@ func TestRepositoriesService_DownloadContentsWithMeta_NoDownloadURL(t *testing.T t.Error("Repositories.DownloadContentsWithMeta did not return expected reader") } - if resp == nil { + if contents != nil { + t.Error("Repositories.DownloadContentsWithMeta did not return expected content") + } + + if resp == nil || resp.Response.StatusCode != http.StatusNotFound { t.Error("Repositories.DownloadContentsWithMeta did not return expected response") } +} - if contents == nil { +func TestRepositoriesService_DownloadContentsWithMeta_NotFile(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "file", + "name": "f", + "content": "" +}]`) + }) + + ctx := t.Context() + reader, contents, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d", nil) + if err == nil || !errors.Is(err, ErrContentsDirectory) { + t.Error("Repositories.DownloadContentsWithMeta did not return expected error") + } + + if reader != nil { + t.Error("Repositories.DownloadContentsWithMeta did not return expected reader") + } + + if contents != nil { t.Error("Repositories.DownloadContentsWithMeta did not return expected content") } + + if resp == nil || resp.Response.StatusCode != http.StatusOK { + t.Error("Repositories.DownloadContentsWithMeta did not return expected response") + } } -func TestRepositoriesService_DownloadContentsWithMeta_NoFile(t *testing.T) { +func TestRepositoriesService_DownloadContentsWithMeta_Submodule(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - mux.HandleFunc("/repos/o/r/contents/d/f", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, `{ + "type": "submodule", + "name": "f", + "content": "", + "download_url": "", + "submodule_git_url": "http://example.com/submodule.git" +}`) }) ctx := t.Context() - _, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil) - if err == nil { + reader, contents, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d", nil) + if err == nil || !errors.Is(err, ErrContentsSubmodule) { t.Error("Repositories.DownloadContentsWithMeta did not return expected error") } - if resp == nil { + if reader != nil { + t.Error("Repositories.DownloadContentsWithMeta did not return expected reader") + } + + if contents == nil || contents.GetType() != "submodule" { + t.Error("Repositories.DownloadContentsWithMeta did not return expected content") + } + + if resp == nil || resp.Response.StatusCode != http.StatusOK { t.Error("Repositories.DownloadContentsWithMeta did not return expected response") } } -func TestRepositoriesService_DownloadContentsWithMeta_NotFile(t *testing.T) { +func TestRepositoriesService_DownloadContentsWithMeta_NoDownloadURL(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/repos/o/r/contents/d/f", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") - fmt.Fprint(w, `[{ + fmt.Fprint(w, `{ "type": "file", "name": "f", "content": "" -}]`) +}`) }) ctx := t.Context() - _, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d", nil) - if err == nil { + reader, contents, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil) + if err == nil || !errors.Is(err, ErrContentsNoDownloadURL) { t.Error("Repositories.DownloadContentsWithMeta did not return expected error") } - if resp == nil { + if reader != nil { + t.Error("Repositories.DownloadContentsWithMeta did not return expected reader") + } + + if contents == nil { + t.Error("Repositories.DownloadContentsWithMeta did not return expected content") + } + + if resp == nil || resp.Response.StatusCode != http.StatusOK { t.Error("Repositories.DownloadContentsWithMeta did not return expected response") } }