Repository: google/go-github
Pull request #1810 (2021-02-21 23:44:27)

Add DownloadContentsWithMeta to receive RepositoryContent
Added an additional method and tests for callers to receive a
RepositoryContent when downloading a large file from a GitHub
repository. This simplifies future operations (e.g. UpdateFile)
involving the requested file.

Fixes: #1809.

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=h1) Report
> Merging [#1810](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=desc) (857e3d0) into [master](https://codecov.io/gh/google/go-github/commit/bff639efb8827d25e4b020e6ae003537028617da?el=desc) (bff639e) will **decrease** coverage by `0.05%`.
> The diff coverage is `69.23%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/1810/graphs/tree.svg?width=650&height=150&src=pr&token=EgYjGjZlI0)](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1810      +/-   ##
==========================================
- Coverage   97.59%   97.53%   -0.06%     
==========================================
  Files         100      100              
  Lines        6521     6534      +13     
==========================================
+ Hits         6364     6373       +9     
- Misses         85       87       +2     
- Partials       72       74       +2     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [github/repos\_contents.go](https://codecov.io/gh/google/go-github/pull/1810/diff?src=pr&el=tree#diff-Z2l0aHViL3JlcG9zX2NvbnRlbnRzLmdv) | `86.50% <69.23%> (-1.99%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=footer). Last update [bff639e...386ce5a](https://codecov.io/gh/google/go-github/pull/1810?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/github/actions_workflow_runs.go b/github/actions_workflow_runs.go
index 4ccb7a6d..64274a3d 100644
--- a/github/actions_workflow_runs.go
+++ b/github/actions_workflow_runs.go
@@ -15,6 +15,7 @@ import (
 // WorkflowRun represents a repository action workflow run.
 type WorkflowRun struct {
 	ID             *int64         `json:"id,omitempty"`
+	Name           *string        `json:"name,omitempty"`
 	NodeID         *string        `json:"node_id,omitempty"`
 	HeadBranch     *string        `json:"head_branch,omitempty"`
 	HeadSHA        *string        `json:"head_sha,omitempty"`
diff --git a/github/apps.go b/github/apps.go
index 12df54dd..cb0069de 100644
--- a/github/apps.go
+++ b/github/apps.go
@@ -90,21 +90,26 @@ type InstallationPermissions struct {
 
 // Installation represents a GitHub Apps installation.
 type Installation struct {
-	ID                  *int64                   `json:"id,omitempty"`
-	NodeID              *string                  `json:"node_id,omitempty"`
-	AppID               *int64                   `json:"app_id,omitempty"`
-	TargetID            *int64                   `json:"target_id,omitempty"`
-	Account             *User                    `json:"account,omitempty"`
-	AccessTokensURL     *string                  `json:"access_tokens_url,omitempty"`
-	RepositoriesURL     *string                  `json:"repositories_url,omitempty"`
-	HTMLURL             *string                  `json:"html_url,omitempty"`
-	TargetType          *string                  `json:"target_type,omitempty"`
-	SingleFileName      *string                  `json:"single_file_name,omitempty"`
-	RepositorySelection *string                  `json:"repository_selection,omitempty"`
-	Events              []string                 `json:"events,omitempty"`
-	Permissions         *InstallationPermissions `json:"permissions,omitempty"`
-	CreatedAt           *Timestamp               `json:"created_at,omitempty"`
-	UpdatedAt           *Timestamp               `json:"updated_at,omitempty"`
+	ID                     *int64                   `json:"id,omitempty"`
+	NodeID                 *string                  `json:"node_id,omitempty"`
+	AppID                  *int64                   `json:"app_id,omitempty"`
+	AppSlug                *string                  `json:"app_slug,omitempty"`
+	TargetID               *int64                   `json:"target_id,omitempty"`
+	Account                *User                    `json:"account,omitempty"`
+	AccessTokensURL        *string                  `json:"access_tokens_url,omitempty"`
+	RepositoriesURL        *string                  `json:"repositories_url,omitempty"`
+	HTMLURL                *string                  `json:"html_url,omitempty"`
+	TargetType             *string                  `json:"target_type,omitempty"`
+	SingleFileName         *string                  `json:"single_file_name,omitempty"`
+	RepositorySelection    *string                  `json:"repository_selection,omitempty"`
+	Events                 []string                 `json:"events,omitempty"`
+	SingleFilePaths        []string                 `json:"single_file_paths,omitempty"`
+	Permissions            *InstallationPermissions `json:"permissions,omitempty"`
+	CreatedAt              *Timestamp               `json:"created_at,omitempty"`
+	UpdatedAt              *Timestamp               `json:"updated_at,omitempty"`
+	HasMultipleSingleFiles *bool                    `json:"has_multiple_single_files,omitempty"`
+	SuspendedBy            *User                    `json:"suspended_by,omitempty"`
+	SuspendedAt            *Timestamp               `json:"suspended_at,omitempty"`
 }
 
 // Attachment represents a GitHub Apps attachment.
diff --git a/github/github-accessors.go b/github/github-accessors.go
index e6ea3431..88f82e31 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -4268,6 +4268,14 @@ func (i *Installation) GetAppID() int64 {
 	return *i.AppID
 }
 
+// GetAppSlug returns the AppSlug field if it's non-nil, zero value otherwise.
+func (i *Installation) GetAppSlug() string {
+	if i == nil || i.AppSlug == nil {
+		return ""
+	}
+	return *i.AppSlug
+}
+
 // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
 func (i *Installation) GetCreatedAt() Timestamp {
 	if i == nil || i.CreatedAt == nil {
@@ -4276,6 +4284,14 @@ func (i *Installation) GetCreatedAt() Timestamp {
 	return *i.CreatedAt
 }
 
+// GetHasMultipleSingleFiles returns the HasMultipleSingleFiles field if it's non-nil, zero value otherwise.
+func (i *Installation) GetHasMultipleSingleFiles() bool {
+	if i == nil || i.HasMultipleSingleFiles == nil {
+		return false
+	}
+	return *i.HasMultipleSingleFiles
+}
+
 // GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
 func (i *Installation) GetHTMLURL() string {
 	if i == nil || i.HTMLURL == nil {
@@ -4332,6 +4348,22 @@ func (i *Installation) GetSingleFileName() string {
 	return *i.SingleFileName
 }
 
+// GetSuspendedAt returns the SuspendedAt field if it's non-nil, zero value otherwise.
+func (i *Installation) GetSuspendedAt() Timestamp {
+	if i == nil || i.SuspendedAt == nil {
+		return Timestamp{}
+	}
+	return *i.SuspendedAt
+}
+
+// GetSuspendedBy returns the SuspendedBy field.
+func (i *Installation) GetSuspendedBy() *User {
+	if i == nil {
+		return nil
+	}
+	return i.SuspendedBy
+}
+
 // GetTargetID returns the TargetID field if it's non-nil, zero value otherwise.
 func (i *Installation) GetTargetID() int64 {
 	if i == nil || i.TargetID == nil {
@@ -15676,6 +15708,14 @@ func (w *WorkflowRun) GetLogsURL() string {
 	return *w.LogsURL
 }
 
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (w *WorkflowRun) GetName() string {
+	if w == nil || w.Name == nil {
+		return ""
+	}
+	return *w.Name
+}
+
 // GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
 func (w *WorkflowRun) GetNodeID() string {
 	if w == nil || w.NodeID == nil {
diff --git a/github/repos_contents.go b/github/repos_contents.go
index 924fee2f..d88ae74b 100644
--- a/github/repos_contents.go
+++ b/github/repos_contents.go
@@ -144,6 +144,36 @@ func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo,
 	return nil, resp, fmt.Errorf("No file named %s found in %s", filename, dir)
 }
 
+// DownloadContentsWithMeta is identical to DownloadContents but additionally
+// returns the RepositoryContent of the requested file. This additional data
+// is useful for future operations involving the requested file. For merely
+// reading the content of a file, DownloadContents is perfectly adequate.
+//
+// It is possible for the download to result in a failed response when the
+// returned error is nil. Callers should check the returned Response status
+// code to verify the content is from a successful response.
+func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *RepositoryContent, *Response, error) {
+	dir := path.Dir(filepath)
+	filename := path.Base(filepath)
+	_, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
+	if err != nil {
+		return nil, nil, resp, err
+	}
+	for _, contents := range dirContents {
+		if *contents.Name == filename {
+			if contents.DownloadURL == nil || *contents.DownloadURL == "" {
+				return nil, contents, resp, fmt.Errorf("No download link found for %s", filepath)
+			}
+			dlResp, err := s.client.client.Get(*contents.DownloadURL)
+			if err != nil {
+				return nil, contents, &Response{Response: dlResp}, err
+			}
+			return dlResp.Body, contents, &Response{Response: dlResp}, nil
+		}
+	}
+	return nil, nil, resp, fmt.Errorf("No file named %s found in %s", filename, dir)
+}
+
 // GetContents can return either the metadata and content of a single file
 // (when path references a file) or the metadata of all the files and/or
 // subdirectories of a directory (when path references a directory). To make it
diff --git a/update-urls/main.go b/update-urls/main.go
index bdfd0882..d0d4955a 100644
--- a/update-urls/main.go
+++ b/update-urls/main.go
@@ -55,38 +55,39 @@ var (
 	// skipMethods holds methods which are skipped because they do not have GitHub v3
 	// API URLs or are otherwise problematic in parsing, discovering, and/or fixing.
 	skipMethods = map[string]bool{
-		"ActionsService.DownloadArtifact":            true,
-		"AdminService.CreateOrg":                     true,
-		"AdminService.CreateUser":                    true,
-		"AdminService.CreateUserImpersonation":       true,
-		"AdminService.DeleteUserImpersonation":       true,
-		"AdminService.GetAdminStats":                 true,
-		"AdminService.RenameOrg":                     true,
-		"AdminService.RenameOrgByName":               true,
-		"AdminService.UpdateTeamLDAPMapping":         true,
-		"AdminService.UpdateUserLDAPMapping":         true,
-		"AppsService.FindRepositoryInstallationByID": true,
-		"AuthorizationsService.CreateImpersonation":  true,
-		"AuthorizationsService.DeleteImpersonation":  true,
-		"IssueImportService.CheckStatus":             true,
-		"IssueImportService.CheckStatusSince":        true,
-		"IssueImportService.Create":                  true,
-		"MarketplaceService.marketplaceURI":          true,
-		"OrganizationsService.GetByID":               true,
-		"RepositoriesService.DeletePreReceiveHook":   true,
-		"RepositoriesService.DownloadContents":       true,
-		"RepositoriesService.GetArchiveLink":         true,
-		"RepositoriesService.GetByID":                true,
-		"RepositoriesService.GetPreReceiveHook":      true,
-		"RepositoriesService.ListPreReceiveHooks":    true,
-		"RepositoriesService.UpdatePreReceiveHook":   true,
-		"SearchService.search":                       true,
-		"TeamsService.ListTeamMembersByID":           true,
-		"UsersService.DemoteSiteAdmin":               true,
-		"UsersService.GetByID":                       true,
-		"UsersService.PromoteSiteAdmin":              true,
-		"UsersService.Suspend":                       true,
-		"UsersService.Unsuspend":                     true,
+		"ActionsService.DownloadArtifact":              true,
+		"AdminService.CreateOrg":                       true,
+		"AdminService.CreateUser":                      true,
+		"AdminService.CreateUserImpersonation":         true,
+		"AdminService.DeleteUserImpersonation":         true,
+		"AdminService.GetAdminStats":                   true,
+		"AdminService.RenameOrg":                       true,
+		"AdminService.RenameOrgByName":                 true,
+		"AdminService.UpdateTeamLDAPMapping":           true,
+		"AdminService.UpdateUserLDAPMapping":           true,
+		"AppsService.FindRepositoryInstallationByID":   true,
+		"AuthorizationsService.CreateImpersonation":    true,
+		"AuthorizationsService.DeleteImpersonation":    true,
+		"IssueImportService.CheckStatus":               true,
+		"IssueImportService.CheckStatusSince":          true,
+		"IssueImportService.Create":                    true,
+		"MarketplaceService.marketplaceURI":            true,
+		"OrganizationsService.GetByID":                 true,
+		"RepositoriesService.DeletePreReceiveHook":     true,
+		"RepositoriesService.DownloadContents":         true,
+		"RepositoriesService.DownloadContentsWithMeta": true,
+		"RepositoriesService.GetArchiveLink":           true,
+		"RepositoriesService.GetByID":                  true,
+		"RepositoriesService.GetPreReceiveHook":        true,
+		"RepositoriesService.ListPreReceiveHooks":      true,
+		"RepositoriesService.UpdatePreReceiveHook":     true,
+		"SearchService.search":                         true,
+		"TeamsService.ListTeamMembersByID":             true,
+		"UsersService.DemoteSiteAdmin":                 true,
+		"UsersService.GetByID":                         true,
+		"UsersService.PromoteSiteAdmin":                true,
+		"UsersService.Suspend":                         true,
+		"UsersService.Unsuspend":                       true,
 	}
 
 	helperOverrides = map[string]overrideFunc{


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 5090e52e..c41e574a 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -5003,6 +5003,16 @@ func TestInstallation_GetAppID(tt *testing.T) {
 	i.GetAppID()
 }
 
+func TestInstallation_GetAppSlug(tt *testing.T) {
+	var zeroValue string
+	i := &Installation{AppSlug: &zeroValue}
+	i.GetAppSlug()
+	i = &Installation{}
+	i.GetAppSlug()
+	i = nil
+	i.GetAppSlug()
+}
+
 func TestInstallation_GetCreatedAt(tt *testing.T) {
 	var zeroValue Timestamp
 	i := &Installation{CreatedAt: &zeroValue}
@@ -5013,6 +5023,16 @@ func TestInstallation_GetCreatedAt(tt *testing.T) {
 	i.GetCreatedAt()
 }
 
+func TestInstallation_GetHasMultipleSingleFiles(tt *testing.T) {
+	var zeroValue bool
+	i := &Installation{HasMultipleSingleFiles: &zeroValue}
+	i.GetHasMultipleSingleFiles()
+	i = &Installation{}
+	i.GetHasMultipleSingleFiles()
+	i = nil
+	i.GetHasMultipleSingleFiles()
+}
+
 func TestInstallation_GetHTMLURL(tt *testing.T) {
 	var zeroValue string
 	i := &Installation{HTMLURL: &zeroValue}
@@ -5080,6 +5100,23 @@ func TestInstallation_GetSingleFileName(tt *testing.T) {
 	i.GetSingleFileName()
 }
 
+func TestInstallation_GetSuspendedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	i := &Installation{SuspendedAt: &zeroValue}
+	i.GetSuspendedAt()
+	i = &Installation{}
+	i.GetSuspendedAt()
+	i = nil
+	i.GetSuspendedAt()
+}
+
+func TestInstallation_GetSuspendedBy(tt *testing.T) {
+	i := &Installation{}
+	i.GetSuspendedBy()
+	i = nil
+	i.GetSuspendedBy()
+}
+
 func TestInstallation_GetTargetID(tt *testing.T) {
 	var zeroValue int64
 	i := &Installation{TargetID: &zeroValue}
@@ -18384,6 +18421,16 @@ func TestWorkflowRun_GetLogsURL(tt *testing.T) {
 	w.GetLogsURL()
 }
 
+func TestWorkflowRun_GetName(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowRun{Name: &zeroValue}
+	w.GetName()
+	w = &WorkflowRun{}
+	w.GetName()
+	w = nil
+	w.GetName()
+}
+
 func TestWorkflowRun_GetNodeID(tt *testing.T) {
 	var zeroValue string
 	w := &WorkflowRun{NodeID: &zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index d77747d8..8a1995d0 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -569,22 +569,26 @@ func TestImport_String(t *testing.T) {
 
 func TestInstallation_String(t *testing.T) {
 	v := Installation{
-		ID:                  Int64(0),
-		NodeID:              String(""),
-		AppID:               Int64(0),
-		TargetID:            Int64(0),
-		Account:             &User{},
-		AccessTokensURL:     String(""),
-		RepositoriesURL:     String(""),
-		HTMLURL:             String(""),
-		TargetType:          String(""),
-		SingleFileName:      String(""),
-		RepositorySelection: String(""),
-		Permissions:         &InstallationPermissions{},
-		CreatedAt:           &Timestamp{},
-		UpdatedAt:           &Timestamp{},
-	}
-	want := `github.Installation{ID:0, NodeID:"", AppID:0, TargetID:0, Account:github.User{}, AccessTokensURL:"", RepositoriesURL:"", HTMLURL:"", TargetType:"", SingleFileName:"", RepositorySelection:"", Permissions:github.InstallationPermissions{}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
+		ID:                     Int64(0),
+		NodeID:                 String(""),
+		AppID:                  Int64(0),
+		AppSlug:                String(""),
+		TargetID:               Int64(0),
+		Account:                &User{},
+		AccessTokensURL:        String(""),
+		RepositoriesURL:        String(""),
+		HTMLURL:                String(""),
+		TargetType:             String(""),
+		SingleFileName:         String(""),
+		RepositorySelection:    String(""),
+		Permissions:            &InstallationPermissions{},
+		CreatedAt:              &Timestamp{},
+		UpdatedAt:              &Timestamp{},
+		HasMultipleSingleFiles: Bool(false),
+		SuspendedBy:            &User{},
+		SuspendedAt:            &Timestamp{},
+	}
+	want := `github.Installation{ID:0, NodeID:"", AppID:0, AppSlug:"", TargetID:0, Account:github.User{}, AccessTokensURL:"", RepositoriesURL:"", HTMLURL:"", TargetType:"", SingleFileName:"", RepositorySelection:"", Permissions:github.InstallationPermissions{}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HasMultipleSingleFiles:false, SuspendedBy:github.User{}, SuspendedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
 	if got := v.String(); got != want {
 		t.Errorf("Installation.String = %v, want %v", got, want)
 	}
diff --git a/github/repos_contents_test.go b/github/repos_contents_test.go
index afc260b3..c8cfb45c 100644
--- a/github/repos_contents_test.go
+++ b/github/repos_contents_test.go
@@ -247,6 +247,155 @@ func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) {
 	}
 }
 
+func TestRepositoriesService_DownloadContentsWithMeta_Success(t *testing.T) {
+	client, mux, serverURL, teardown := setup()
+	defer teardown()
+	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",
+		  "download_url": "`+serverURL+baseURLPath+`/download/f"
+		}]`)
+	})
+	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, "foo")
+	})
+
+	ctx := context.Background()
+	r, c, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err != nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned error: %v", err)
+	}
+
+	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned status code %v, want %v", got, want)
+	}
+
+	bytes, err := ioutil.ReadAll(r)
+	if err != nil {
+		t.Errorf("Error reading response body: %v", err)
+	}
+	r.Close()
+
+	if got, want := string(bytes), "foo"; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned %v, want %v", got, want)
+	}
+
+	if c != nil && c.Name != nil {
+		if got, want := *c.Name, "f"; got != want {
+			t.Errorf("Repositories.DownloadContentsWithMeta returned content name %v, want %v", got, want)
+		}
+	} else {
+		t.Errorf("Returned RepositoryContent is null")
+	}
+
+	const methodName = "DownloadContentsWithMeta"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, _, err = client.Repositories.DownloadContentsWithMeta(ctx, "\n", "\n", "\n", nil)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, cot, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		if cot != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, cot)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_DownloadContentsWithMeta_FailedResponse(t *testing.T) {
+	client, mux, serverURL, teardown := setup()
+	defer teardown()
+	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",
+			"download_url": "`+serverURL+baseURLPath+`/download/f"
+		  }]`)
+	})
+	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		w.WriteHeader(http.StatusInternalServerError)
+		fmt.Fprint(w, "foo error")
+	})
+
+	ctx := context.Background()
+	r, c, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err != nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned error: %v", err)
+	}
+
+	if got, want := resp.Response.StatusCode, http.StatusInternalServerError; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned status code %v, want %v", got, want)
+	}
+
+	bytes, err := ioutil.ReadAll(r)
+	if err != nil {
+		t.Errorf("Error reading response body: %v", err)
+	}
+	r.Close()
+
+	if got, want := string(bytes), "foo error"; got != want {
+		t.Errorf("Repositories.DownloadContentsWithMeta returned %v, want %v", got, want)
+	}
+
+	if c != nil && c.Name != nil {
+		if got, want := *c.Name, "f"; got != want {
+			t.Errorf("Repositories.DownloadContentsWithMeta returned content name %v, want %v", got, want)
+		}
+	} else {
+		t.Errorf("Returned RepositoryContent is null")
+	}
+}
+
+func TestRepositoriesService_DownloadContentsWithMeta_NoDownloadURL(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	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",
+		}]`)
+	})
+
+	ctx := context.Background()
+	_, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected error")
+	}
+
+	if resp == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected response")
+	}
+}
+
+func TestRepositoriesService_DownloadContentsWithMeta_NoFile(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[]`)
+	})
+
+	ctx := context.Background()
+	_, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
+	if err == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected error")
+	}
+
+	if resp == nil {
+		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected response")
+	}
+}
+
 func TestRepositoriesService_GetContents_File(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()