Repository: google/go-github
Pull request #861 (2018-03-02 18:01:00)

Add remaining `app` fields
This is the companion PR for adding the remaining fields of the `app` response. 
context: https://github.com/google/go-github/issues/851

This PR adds the remaining fields for the `app` endpoints, specifically adding support for 
`target`
`target_type`
`repository_selection`
`single_file_name`
`permissions`
`events`

### Tests
* I've added unit tests for `target`(s) field for all the tests
* Added a more detailed response test for `ListInstallations` only (would like feedback if I can follow the same pattern for the remaining tests).

/cc @kshitij10496 @gmlewis 


Patch:
diff --git a/github/apps.go b/github/apps.go
index 740642e6..c1f7f138 100644
--- a/github/apps.go
+++ b/github/apps.go
@@ -35,6 +35,34 @@ type InstallationToken struct {
 	ExpiresAt *time.Time `json:"expires_at,omitempty"`
 }
 
+// InstallationPermissions lists the permissions for metadata, contents, issues and single file for an installation.
+type InstallationPermissions struct {
+	Metadata   *string `json:"metadata,omitempty"`
+	Contents   *string `json:"contents,omitempty"`
+	Issues     *string `json:"issues,omitempty"`
+	SingleFile *string `json:"single_file,omitempty"`
+}
+
+// Installation represents a GitHub Apps installation.
+type Installation struct {
+	ID                  *int64                   `json:"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"`
+}
+
+func (i Installation) String() string {
+	return Stringify(i)
+}
+
 // Get a single GitHub App. Passing the empty string will get
 // the authenticated GitHub App.
 //
diff --git a/github/apps_installation.go b/github/apps_installation.go
index 5a424ff1..ccfecb8d 100644
--- a/github/apps_installation.go
+++ b/github/apps_installation.go
@@ -10,20 +10,6 @@ import (
 	"fmt"
 )
 
-// Installation represents a GitHub Apps installation.
-type Installation struct {
-	ID              *int64  `json:"id,omitempty"`
-	AppID           *int64  `json:"app_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"`
-}
-
-func (i Installation) String() string {
-	return Stringify(i)
-}
-
 // ListRepos lists the repositories that are accessible to the authenticated installation.
 //
 // GitHub API docs: https://developer.github.com/v3/apps/installations/#list-repositories
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 32306121..2e9fcaf7 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -2732,6 +2732,14 @@ func (i *Installation) GetID() int64 {
 	return *i.ID
 }
 
+// GetPermissions returns the Permissions field.
+func (i *Installation) GetPermissions() *InstallationPermissions {
+	if i == nil {
+		return nil
+	}
+	return i.Permissions
+}
+
 // GetRepositoriesURL returns the RepositoriesURL field if it's non-nil, zero value otherwise.
 func (i *Installation) GetRepositoriesURL() string {
 	if i == nil || i.RepositoriesURL == nil {
@@ -2740,6 +2748,38 @@ func (i *Installation) GetRepositoriesURL() string {
 	return *i.RepositoriesURL
 }
 
+// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise.
+func (i *Installation) GetRepositorySelection() string {
+	if i == nil || i.RepositorySelection == nil {
+		return ""
+	}
+	return *i.RepositorySelection
+}
+
+// GetSingleFileName returns the SingleFileName field if it's non-nil, zero value otherwise.
+func (i *Installation) GetSingleFileName() string {
+	if i == nil || i.SingleFileName == nil {
+		return ""
+	}
+	return *i.SingleFileName
+}
+
+// GetTargetID returns the TargetID field if it's non-nil, zero value otherwise.
+func (i *Installation) GetTargetID() int64 {
+	if i == nil || i.TargetID == nil {
+		return 0
+	}
+	return *i.TargetID
+}
+
+// GetTargetType returns the TargetType field if it's non-nil, zero value otherwise.
+func (i *Installation) GetTargetType() string {
+	if i == nil || i.TargetType == nil {
+		return ""
+	}
+	return *i.TargetType
+}
+
 // GetAction returns the Action field if it's non-nil, zero value otherwise.
 func (i *InstallationEvent) GetAction() string {
 	if i == nil || i.Action == nil {
@@ -2764,6 +2804,38 @@ func (i *InstallationEvent) GetSender() *User {
 	return i.Sender
 }
 
+// GetContents returns the Contents field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetContents() string {
+	if i == nil || i.Contents == nil {
+		return ""
+	}
+	return *i.Contents
+}
+
+// GetIssues returns the Issues field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetIssues() string {
+	if i == nil || i.Issues == nil {
+		return ""
+	}
+	return *i.Issues
+}
+
+// GetMetadata returns the Metadata field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetMetadata() string {
+	if i == nil || i.Metadata == nil {
+		return ""
+	}
+	return *i.Metadata
+}
+
+// GetSingleFile returns the SingleFile field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetSingleFile() string {
+	if i == nil || i.SingleFile == nil {
+		return ""
+	}
+	return *i.SingleFile
+}
+
 // GetAction returns the Action field if it's non-nil, zero value otherwise.
 func (i *InstallationRepositoriesEvent) GetAction() string {
 	if i == nil || i.Action == nil {
diff --git a/github/github.go b/github/github.go
index 6d45e4aa..46731a36 100644
--- a/github/github.go
+++ b/github/github.go
@@ -477,12 +477,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res
 
 		return nil, err
 	}
-
-	defer func() {
-		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
-		io.CopyN(ioutil.Discard, resp.Body, 512)
-		resp.Body.Close()
-	}()
+	defer resp.Body.Close()
 
 	response := newResponse(resp)
 
diff --git a/github/orgs_members.go b/github/orgs_members.go
index fb1957e6..98e138e7 100644
--- a/github/orgs_members.go
+++ b/github/orgs_members.go
@@ -323,7 +323,7 @@ type CreateOrgInvitationOptions struct {
 // the authenticated user must be an organization owner.
 //
 // https://developer.github.com/v3/orgs/members/#create-organization-invitation
-func (s *OrganizationsService) CreateOrgInvitation(ctx context.Context, org string, opt *CreateOrgInvitationOptions) ([]*Invitation, *Response, error) {
+func (s *OrganizationsService) CreateOrgInvitation(ctx context.Context, org string, opt *CreateOrgInvitationOptions) (*Invitation, *Response, error) {
 	u := fmt.Sprintf("orgs/%v/invitations", org)
 
 	req, err := s.client.NewRequest("POST", u, opt)
@@ -334,12 +334,12 @@ func (s *OrganizationsService) CreateOrgInvitation(ctx context.Context, org stri
 	// TODO: remove custom Accept header when this API fully launches.
 	req.Header.Set("Accept", mediaTypeOrganizationInvitationPreview)
 
-	var invitations []*Invitation
-	resp, err := s.client.Do(ctx, req, &invitations)
+	var invitation *Invitation
+	resp, err := s.client.Do(ctx, req, &invitation)
 	if err != nil {
 		return nil, resp, err
 	}
-	return invitations, resp, nil
+	return invitation, resp, nil
 }
 
 // ListOrgInvitationTeams lists all teams associated with an invitation. In order to see invitations in an organization,


Test patch:
diff --git a/github/apps_test.go b/github/apps_test.go
index d1ebde48..f17c10d2 100644
--- a/github/apps_test.go
+++ b/github/apps_test.go
@@ -66,7 +66,24 @@ func TestAppsService_ListInstallations(t *testing.T) {
 			"page":     "1",
 			"per_page": "2",
 		})
-		fmt.Fprint(w, `[{"id":1, "app_id":1}]`)
+		fmt.Fprint(w, `[{
+                                   "id":1,
+                                   "app_id":1,
+                                   "target_id":1,
+                                   "target_type": "Organization",
+                                   "permissions": {
+                                       "metadata": "read",
+                                       "contents": "read",
+                                       "issues": "write",
+                                       "single_file": "write"
+                                   },
+                                  "events": [
+                                      "push",
+                                      "pull_request"
+                                  ],
+                                 "single_file_name": "config.yml",
+                                 "repository_selection": "selected"}]`,
+		)
 	})
 
 	opt := &ListOptions{Page: 1, PerPage: 2}
@@ -75,7 +92,20 @@ func TestAppsService_ListInstallations(t *testing.T) {
 		t.Errorf("Apps.ListInstallations returned error: %v", err)
 	}
 
-	want := []*Installation{{ID: Int64(1), AppID: Int64(1)}}
+	want := []*Installation{{
+		ID:                  Int64(1),
+		AppID:               Int64(1),
+		TargetID:            Int64(1),
+		TargetType:          String("Organization"),
+		SingleFileName:      String("config.yml"),
+		RepositorySelection: String("selected"),
+		Permissions: &InstallationPermissions{
+			Metadata:   String("read"),
+			Contents:   String("read"),
+			Issues:     String("write"),
+			SingleFile: String("write")},
+		Events: []string{"push", "pull_request"},
+	}}
 	if !reflect.DeepEqual(installations, want) {
 		t.Errorf("Apps.ListInstallations returned %+v, want %+v", installations, want)
 	}
@@ -88,7 +118,7 @@ func TestAppsService_GetInstallation(t *testing.T) {
 	mux.HandleFunc("/app/installations/1", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeIntegrationPreview)
-		fmt.Fprint(w, `{"id":1, "app_id":1}`)
+		fmt.Fprint(w, `{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}`)
 	})
 
 	installation, _, err := client.Apps.GetInstallation(context.Background(), 1)
@@ -96,7 +126,7 @@ func TestAppsService_GetInstallation(t *testing.T) {
 		t.Errorf("Apps.GetInstallation returned error: %v", err)
 	}
 
-	want := &Installation{ID: Int64(1), AppID: Int64(1)}
+	want := &Installation{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")}
 	if !reflect.DeepEqual(installation, want) {
 		t.Errorf("Apps.GetInstallation returned %+v, want %+v", installation, want)
 	}
@@ -113,7 +143,7 @@ func TestAppsService_ListUserInstallations(t *testing.T) {
 			"page":     "1",
 			"per_page": "2",
 		})
-		fmt.Fprint(w, `{"installations":[{"id":1, "app_id":1}]}`)
+		fmt.Fprint(w, `{"installations":[{"id":1, "app_id":1, "target_id":1, "target_type": "Organization"}]}`)
 	})
 
 	opt := &ListOptions{Page: 1, PerPage: 2}
@@ -122,7 +152,7 @@ func TestAppsService_ListUserInstallations(t *testing.T) {
 		t.Errorf("Apps.ListUserInstallations returned error: %v", err)
 	}
 
-	want := []*Installation{{ID: Int64(1), AppID: Int64(1)}}
+	want := []*Installation{{ID: Int64(1), AppID: Int64(1), TargetID: Int64(1), TargetType: String("Organization")}}
 	if !reflect.DeepEqual(installations, want) {
 		t.Errorf("Apps.ListUserInstallations returned %+v, want %+v", installations, want)
 	}
diff --git a/github/orgs_members_test.go b/github/orgs_members_test.go
index 83a63535..b69b39fe 100644
--- a/github/orgs_members_test.go
+++ b/github/orgs_members_test.go
@@ -471,7 +471,7 @@ func TestOrganizationsService_CreateOrgInvitation(t *testing.T) {
 			t.Errorf("Request body = %+v, want %+v", v, input)
 		}
 
-		fmt.Fprintln(w, `[{"email": "octocat@github.com"}]`)
+		fmt.Fprintln(w, `{"email": "octocat@github.com"}`)
 
 	})
 
@@ -480,7 +480,7 @@ func TestOrganizationsService_CreateOrgInvitation(t *testing.T) {
 		t.Errorf("Organizations.CreateOrgInvitation returned error: %v", err)
 	}
 
-	want := []*Invitation{{Email: String("octocat@github.com")}}
+	want := &Invitation{Email: String("octocat@github.com")}
 	if !reflect.DeepEqual(invitations, want) {
 		t.Errorf("Organizations.ListPendingOrgInvitations returned %+v, want %+v", invitations, want)
 	}