Repository: google/go-github (go)

Issue:
Github Actions SetEnabledRepos functionality is missing
Currently we can list enabled repos on an org for github actions:
https://github.com/google/go-github/blob/b1b5e4e9d0ebffbb88e5c19958c87462fb94f169/github/actions_runners.go#L243-L265

However i dont see the Put and Delete method implemented yet for https://docs.github.com/en/rest/reference/actions#set-selected-repositories-enabled-for-github-actions-in-an-organization

Pull request description:
Add methods to add and remove repos for actions on org level
This commit adds the abillity to add one or more repositories to the
list of enabled repositories for github actions on org level.
It also adds the abillity to remove them from this list.
With this change we can now enable and disable repos to use github actions on org level.

The used endpoints for this are:
https://docs.github.com/en/rest/reference/actions#set-selected-repositories-enabled-for-github-actions-in-an-organization
https://docs.github.com/en/rest/reference/actions#disable-a-selected-repository-for-github-actions-in-an-organization

fixes #1995 

Patch:
diff --git a/github/actions_runner_groups.go b/github/actions_runner_groups.go
index 5772daaa..e34495a9 100644
--- a/github/actions_runner_groups.go
+++ b/github/actions_runner_groups.go
@@ -36,12 +36,15 @@ type CreateRunnerGroupRequest struct {
 	SelectedRepositoryIDs []int64 `json:"selected_repository_ids,omitempty"`
 	// Runners represent a list of runner IDs to add to the runner group.
 	Runners []int64 `json:"runners,omitempty"`
+	// If set to True, public repos can use this runner group
+	AllowsPublicRepositories *bool `json:"allows_public_repositories,omitempty"`
 }
 
 // UpdateRunnerGroupRequest represents a request to update a Runner group for an organization.
 type UpdateRunnerGroupRequest struct {
-	Name       *string `json:"name,omitempty"`
-	Visibility *string `json:"visibility,omitempty"`
+	Name                     *string `json:"name,omitempty"`
+	Visibility               *string `json:"visibility,omitempty"`
+	AllowsPublicRepositories *bool   `json:"allows_public_repositories,omitempty"`
 }
 
 // SetRepoAccessRunnerGroupRequest represents a request to replace the list of repositories
diff --git a/github/actions_runners.go b/github/actions_runners.go
index 5bdebb31..f37e1aa4 100644
--- a/github/actions_runners.go
+++ b/github/actions_runners.go
@@ -264,6 +264,65 @@ func (s *ActionsService) ListEnabledReposInOrg(ctx context.Context, owner string
 	return repos, resp, nil
 }
 
+// SetEnabledReposInOrg replaces the list of selected repositories that are enabled for GitHub Actions in an organization..
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#set-selected-repositories-enabled-for-github-actions-in-an-organization
+func (s *ActionsService) SetEnabledReposInOrg(ctx context.Context, owner string, repositoryIDs []int64) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions/repositories", owner)
+
+	req, err := s.client.NewRequest("PUT", u, struct {
+		IDs []int64 `json:"selected_repository_ids"`
+	}{IDs: repositoryIDs})
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, nil
+}
+
+// AddEnabledReposInOrg adds a repository to the list of selected repositories that are enabled for GitHub Actions in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#enable-a-selected-repository-for-github-actions-in-an-organization
+func (s *ActionsService) AddEnabledReposInOrg(ctx context.Context, owner string, repositoryID int64) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions/repositories/%v", owner, repositoryID)
+
+	req, err := s.client.NewRequest("PUT", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, nil
+}
+
+// RemoveEnabledRepoInOrg removes a single repository from the list of enabled repos for GitHub Actions in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/actions#disable-a-selected-repository-for-github-actions-in-an-organization
+func (s *ActionsService) RemoveEnabledRepoInOrg(ctx context.Context, owner string, repositoryID int64) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/actions/permissions/repositories/%v", owner, repositoryID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, nil
+}
+
 // GetOrganizationRunner gets a specific self-hosted runner for an organization using its runner ID.
 //
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-self-hosted-runner-for-an-organization
diff --git a/github/github-accessors.go b/github/github-accessors.go
index fb3e2903..3dcb9a4e 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -2916,6 +2916,14 @@ func (c *CreateOrgInvitationOptions) GetRole() string {
 	return *c.Role
 }
 
+// GetAllowsPublicRepositories returns the AllowsPublicRepositories field if it's non-nil, zero value otherwise.
+func (c *CreateRunnerGroupRequest) GetAllowsPublicRepositories() bool {
+	if c == nil || c.AllowsPublicRepositories == nil {
+		return false
+	}
+	return *c.AllowsPublicRepositories
+}
+
 // GetName returns the Name field if it's non-nil, zero value otherwise.
 func (c *CreateRunnerGroupRequest) GetName() string {
 	if c == nil || c.Name == nil {
@@ -15924,6 +15932,14 @@ func (u *UpdateCheckRunOptions) GetStatus() string {
 	return *u.Status
 }
 
+// GetAllowsPublicRepositories returns the AllowsPublicRepositories field if it's non-nil, zero value otherwise.
+func (u *UpdateRunnerGroupRequest) GetAllowsPublicRepositories() bool {
+	if u == nil || u.AllowsPublicRepositories == nil {
+		return false
+	}
+	return *u.AllowsPublicRepositories
+}
+
 // GetName returns the Name field if it's non-nil, zero value otherwise.
 func (u *UpdateRunnerGroupRequest) GetName() string {
 	if u == nil || u.Name == nil {


Test patch:
diff --git a/github/actions_runner_groups_test.go b/github/actions_runner_groups_test.go
index 27489ad9..55de0b93 100644
--- a/github/actions_runner_groups_test.go
+++ b/github/actions_runner_groups_test.go
@@ -139,8 +139,9 @@ func TestActionsService_CreateOrganizationRunnerGroup(t *testing.T) {
 
 	ctx := context.Background()
 	req := CreateRunnerGroupRequest{
-		Name:       String("octo-runner-group"),
-		Visibility: String("selected"),
+		Name:                     String("octo-runner-group"),
+		Visibility:               String("selected"),
+		AllowsPublicRepositories: Bool(true),
 	}
 	group, _, err := client.Actions.CreateOrganizationRunnerGroup(ctx, "o", req)
 	if err != nil {
@@ -188,8 +189,9 @@ func TestActionsService_UpdateOrganizationRunnerGroup(t *testing.T) {
 
 	ctx := context.Background()
 	req := UpdateRunnerGroupRequest{
-		Name:       String("octo-runner-group"),
-		Visibility: String("selected"),
+		Name:                     String("octo-runner-group"),
+		Visibility:               String("selected"),
+		AllowsPublicRepositories: Bool(true),
 	}
 	group, _, err := client.Actions.UpdateOrganizationRunnerGroup(ctx, "o", 2, req)
 	if err != nil {
@@ -541,17 +543,19 @@ func TestCreateRunnerGroupRequest_Marshal(t *testing.T) {
 	testJSONMarshal(t, &CreateRunnerGroupRequest{}, "{}")
 
 	u := &CreateRunnerGroupRequest{
-		Name:                  String("n"),
-		Visibility:            String("v"),
-		SelectedRepositoryIDs: []int64{1},
-		Runners:               []int64{1},
+		Name:                     String("n"),
+		Visibility:               String("v"),
+		SelectedRepositoryIDs:    []int64{1},
+		Runners:                  []int64{1},
+		AllowsPublicRepositories: Bool(true),
 	}
 
 	want := `{
 		"name": "n",
 		"visibility": "v",
 		"selected_repository_ids": [1],
-		"runners": [1]
+		"runners": [1],
+		"allows_public_repositories": true
 	}`
 
 	testJSONMarshal(t, u, want)
@@ -561,13 +565,15 @@ func TestUpdateRunnerGroupRequest_Marshal(t *testing.T) {
 	testJSONMarshal(t, &UpdateRunnerGroupRequest{}, "{}")
 
 	u := &UpdateRunnerGroupRequest{
-		Name:       String("n"),
-		Visibility: String("v"),
+		Name:                     String("n"),
+		Visibility:               String("v"),
+		AllowsPublicRepositories: Bool(true),
 	}
 
 	want := `{
 		"name": "n",
-		"visibility": "v"
+		"visibility": "v",
+		"allows_public_repositories": true
 	}`
 
 	testJSONMarshal(t, u, want)
diff --git a/github/actions_runners_test.go b/github/actions_runners_test.go
index a997f627..9b338641 100644
--- a/github/actions_runners_test.go
+++ b/github/actions_runners_test.go
@@ -401,6 +401,89 @@ func TestActionsService_ListEnabledReposInOrg(t *testing.T) {
 	})
 }
 
+func TestActionsService_SetEnabledReposInOrg(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/permissions/repositories", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		testHeader(t, r, "Content-Type", "application/json")
+		testBody(t, r, `{"selected_repository_ids":[123,1234]}`+"\n")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	_, err := client.Actions.SetEnabledReposInOrg(ctx, "o", []int64{123, 1234})
+	if err != nil {
+		t.Errorf("Actions.SetEnabledReposInOrg returned error: %v", err)
+	}
+
+	const methodName = "SetEnabledReposInOrg"
+
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Actions.SetEnabledReposInOrg(ctx, "\n", []int64{123, 1234})
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Actions.SetEnabledReposInOrg(ctx, "o", []int64{123, 1234})
+	})
+}
+
+func TestActionsService_AddEnabledReposInOrg(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/permissions/repositories/123", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	_, err := client.Actions.AddEnabledReposInOrg(ctx, "o", 123)
+	if err != nil {
+		t.Errorf("Actions.AddEnabledReposInOrg returned error: %v", err)
+	}
+
+	const methodName = "AddEnabledReposInOrg"
+
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Actions.AddEnabledReposInOrg(ctx, "\n", 123)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Actions.AddEnabledReposInOrg(ctx, "o", 123)
+	})
+}
+
+func TestActionsService_RemoveEnabledRepoInOrg(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/actions/permissions/repositories/123", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	_, err := client.Actions.RemoveEnabledRepoInOrg(ctx, "o", 123)
+	if err != nil {
+		t.Errorf("Actions.RemoveEnabledRepoInOrg returned error: %v", err)
+	}
+
+	const methodName = "RemoveEnabledRepoInOrg"
+
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Actions.RemoveEnabledRepoInOrg(ctx, "\n", 123)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Actions.RemoveEnabledRepoInOrg(ctx, "o", 123)
+	})
+}
+
 func TestActionsService_GetOrganizationRunner(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 03fd08ea..e0be0614 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -3415,6 +3415,16 @@ func TestCreateOrgInvitationOptions_GetRole(tt *testing.T) {
 	c.GetRole()
 }
 
+func TestCreateRunnerGroupRequest_GetAllowsPublicRepositories(tt *testing.T) {
+	var zeroValue bool
+	c := &CreateRunnerGroupRequest{AllowsPublicRepositories: &zeroValue}
+	c.GetAllowsPublicRepositories()
+	c = &CreateRunnerGroupRequest{}
+	c.GetAllowsPublicRepositories()
+	c = nil
+	c.GetAllowsPublicRepositories()
+}
+
 func TestCreateRunnerGroupRequest_GetName(tt *testing.T) {
 	var zeroValue string
 	c := &CreateRunnerGroupRequest{Name: &zeroValue}
@@ -18619,6 +18629,16 @@ func TestUpdateCheckRunOptions_GetStatus(tt *testing.T) {
 	u.GetStatus()
 }
 
+func TestUpdateRunnerGroupRequest_GetAllowsPublicRepositories(tt *testing.T) {
+	var zeroValue bool
+	u := &UpdateRunnerGroupRequest{AllowsPublicRepositories: &zeroValue}
+	u.GetAllowsPublicRepositories()
+	u = &UpdateRunnerGroupRequest{}
+	u.GetAllowsPublicRepositories()
+	u = nil
+	u.GetAllowsPublicRepositories()
+}
+
 func TestUpdateRunnerGroupRequest_GetName(tt *testing.T) {
 	var zeroValue string
 	u := &UpdateRunnerGroupRequest{Name: &zeroValue}
diff --git a/github/packages_test.go b/github/packages_test.go
new file mode 100644
index 00000000..7d287d89
--- /dev/null
+++ b/github/packages_test.go
@@ -0,0 +1,677 @@
+// Copyright 2021 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import "testing"
+
+func TestPackageRegistry_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PackageRegistry{}, "{}")
+
+	o := &PackageRegistry{
+		AboutURL: String("aurl"),
+		Name:     String("name"),
+		Type:     String("type"),
+		URL:      String("url"),
+		Vendor:   String("vendor"),
+	}
+	want := `{
+		"about_url": "aurl",
+		"name": "name",
+		"type": "type",
+		"url": "url",
+		"vendor": "vendor"
+	}`
+
+	testJSONMarshal(t, o, want)
+}
+
+func TestPackageFile_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PackageFile{}, "{}")
+
+	o := &PackageFile{
+		DownloadURL: String("durl"),
+		ID:          Int64(1),
+		Name:        String("name"),
+		SHA256:      String("sha256"),
+		SHA1:        String("sha1"),
+		MD5:         String("md5"),
+		ContentType: String("ct"),
+		State:       String("state"),
+		Author: &User{
+			Login:           String("l"),
+			ID:              Int64(1),
+			URL:             String("u"),
+			AvatarURL:       String("a"),
+			GravatarID:      String("g"),
+			Name:            String("n"),
+			Company:         String("c"),
+			Blog:            String("b"),
+			Location:        String("l"),
+			Email:           String("e"),
+			Hireable:        Bool(true),
+			Bio:             String("b"),
+			TwitterUsername: String("t"),
+			PublicRepos:     Int(1),
+			Followers:       Int(1),
+			Following:       Int(1),
+			CreatedAt:       &Timestamp{referenceTime},
+			SuspendedAt:     &Timestamp{referenceTime},
+		},
+		Size:      Int64(1),
+		CreatedAt: &Timestamp{referenceTime},
+		UpdatedAt: &Timestamp{referenceTime},
+	}
+
+	want := `{
+		"download_url": "durl",
+		"id": 1,
+		"name": "name",
+		"sha256": "sha256",
+		"sha1": "sha1",
+		"md5": "md5",
+		"content_type": "ct",
+		"state": "state",
+		"author": {
+			"login": "l",
+			"id": 1,
+			"avatar_url": "a",
+			"gravatar_id": "g",
+			"name": "n",
+			"company": "c",
+			"blog": "b",
+			"location": "l",
+			"email": "e",
+			"hireable": true,
+			"bio": "b",
+			"twitter_username": "t",
+			"public_repos": 1,
+			"followers": 1,
+			"following": 1,
+			"created_at": ` + referenceTimeStr + `,
+			"suspended_at": ` + referenceTimeStr + `,
+			"url": "u"
+		},
+		"size": 1,
+		"created_at": ` + referenceTimeStr + `,
+		"updated_at": ` + referenceTimeStr + `
+	}`
+
+	testJSONMarshal(t, o, want)
+}
+
+func TestPackageRelease_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PackageRelease{}, "{}")
+
+	o := &PackageRelease{
+		URL:             String("url"),
+		HTMLURL:         String("hurl"),
+		ID:              Int64(1),
+		TagName:         String("tn"),
+		TargetCommitish: String("tcs"),
+		Name:            String("name"),
+		Draft:           Bool(true),
+		Author: &User{
+			Login:           String("l"),
+			ID:              Int64(1),
+			URL:             String("u"),
+			AvatarURL:       String("a"),
+			GravatarID:      String("g"),
+			Name:            String("n"),
+			Company:         String("c"),
+			Blog:            String("b"),
+			Location:        String("l"),
+			Email:           String("e"),
+			Hireable:        Bool(true),
+			Bio:             String("b"),
+			TwitterUsername: String("t"),
+			PublicRepos:     Int(1),
+			Followers:       Int(1),
+			Following:       Int(1),
+			CreatedAt:       &Timestamp{referenceTime},
+			SuspendedAt:     &Timestamp{referenceTime},
+		},
+		Prerelease:  Bool(true),
+		CreatedAt:   &Timestamp{referenceTime},
+		PublishedAt: &Timestamp{referenceTime},
+	}
+
+	want := `{
+		"url": "url",
+		"html_url": "hurl",
+		"id": 1,
+		"tag_name": "tn",
+		"target_commitish": "tcs",
+		"name": "name",
+		"draft": true,
+		"author": {
+			"login": "l",
+			"id": 1,
+			"avatar_url": "a",
+			"gravatar_id": "g",
+			"name": "n",
+			"company": "c",
+			"blog": "b",
+			"location": "l",
+			"email": "e",
+			"hireable": true,
+			"bio": "b",
+			"twitter_username": "t",
+			"public_repos": 1,
+			"followers": 1,
+			"following": 1,
+			"created_at": ` + referenceTimeStr + `,
+			"suspended_at": ` + referenceTimeStr + `,
+			"url": "u"
+		},
+		"prerelease": true,
+		"created_at": ` + referenceTimeStr + `,
+		"published_at": ` + referenceTimeStr + `
+	}`
+
+	testJSONMarshal(t, o, want)
+}
+
+func TestPackageVersion_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PackageVersion{}, "{}")
+
+	o := &PackageVersion{
+		ID:       Int64(1),
+		Version:  String("ver"),
+		Summary:  String("sum"),
+		Body:     String("body"),
+		BodyHTML: String("btnhtml"),
+		Release: &PackageRelease{
+			URL:             String("url"),
+			HTMLURL:         String("hurl"),
+			ID:              Int64(1),
+			TagName:         String("tn"),
+			TargetCommitish: String("tcs"),
+			Name:            String("name"),
+			Draft:           Bool(true),
+			Author: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			Prerelease:  Bool(true),
+			CreatedAt:   &Timestamp{referenceTime},
+			PublishedAt: &Timestamp{referenceTime},
+		},
+		Manifest:        String("mani"),
+		HTMLURL:         String("hurl"),
+		TagName:         String("tn"),
+		TargetCommitish: String("tcs"),
+		TargetOID:       String("tid"),
+		Draft:           Bool(true),
+		Prerelease:      Bool(true),
+		CreatedAt:       &Timestamp{referenceTime},
+		UpdatedAt:       &Timestamp{referenceTime},
+		PackageFiles: []*PackageFile{
+			{
+				DownloadURL: String("durl"),
+				ID:          Int64(1),
+				Name:        String("name"),
+				SHA256:      String("sha256"),
+				SHA1:        String("sha1"),
+				MD5:         String("md5"),
+				ContentType: String("ct"),
+				State:       String("state"),
+				Author: &User{
+					Login:           String("l"),
+					ID:              Int64(1),
+					URL:             String("u"),
+					AvatarURL:       String("a"),
+					GravatarID:      String("g"),
+					Name:            String("n"),
+					Company:         String("c"),
+					Blog:            String("b"),
+					Location:        String("l"),
+					Email:           String("e"),
+					Hireable:        Bool(true),
+					Bio:             String("b"),
+					TwitterUsername: String("t"),
+					PublicRepos:     Int(1),
+					Followers:       Int(1),
+					Following:       Int(1),
+					CreatedAt:       &Timestamp{referenceTime},
+					SuspendedAt:     &Timestamp{referenceTime},
+				},
+				Size:      Int64(1),
+				CreatedAt: &Timestamp{referenceTime},
+				UpdatedAt: &Timestamp{referenceTime},
+			},
+		},
+		Author: &User{
+			Login:           String("l"),
+			ID:              Int64(1),
+			URL:             String("u"),
+			AvatarURL:       String("a"),
+			GravatarID:      String("g"),
+			Name:            String("n"),
+			Company:         String("c"),
+			Blog:            String("b"),
+			Location:        String("l"),
+			Email:           String("e"),
+			Hireable:        Bool(true),
+			Bio:             String("b"),
+			TwitterUsername: String("t"),
+			PublicRepos:     Int(1),
+			Followers:       Int(1),
+			Following:       Int(1),
+			CreatedAt:       &Timestamp{referenceTime},
+			SuspendedAt:     &Timestamp{referenceTime},
+		},
+		InstallationCommand: String("ic"),
+	}
+
+	want := `{
+		"id": 1,
+		"version": "ver",
+		"summary": "sum",
+		"body": "body",
+		"body_html": "btnhtml",
+		"release": {
+			"url": "url",
+			"html_url": "hurl",
+			"id": 1,
+			"tag_name": "tn",
+			"target_commitish": "tcs",
+			"name": "name",
+			"draft": true,
+			"author": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"prerelease": true,
+			"created_at": ` + referenceTimeStr + `,
+			"published_at": ` + referenceTimeStr + `
+		},
+		"manifest": "mani",
+		"html_url": "hurl",
+		"tag_name": "tn",
+		"target_commitish": "tcs",
+		"target_oid": "tid",
+		"draft": true,
+		"prerelease": true,
+		"created_at": ` + referenceTimeStr + `,
+		"updated_at": ` + referenceTimeStr + `,
+		"package_files": [
+			{
+				"download_url": "durl",
+				"id": 1,
+				"name": "name",
+				"sha256": "sha256",
+				"sha1": "sha1",
+				"md5": "md5",
+				"content_type": "ct",
+				"state": "state",
+				"author": {
+					"login": "l",
+					"id": 1,
+					"avatar_url": "a",
+					"gravatar_id": "g",
+					"name": "n",
+					"company": "c",
+					"blog": "b",
+					"location": "l",
+					"email": "e",
+					"hireable": true,
+					"bio": "b",
+					"twitter_username": "t",
+					"public_repos": 1,
+					"followers": 1,
+					"following": 1,
+					"created_at": ` + referenceTimeStr + `,
+					"suspended_at": ` + referenceTimeStr + `,
+					"url": "u"
+				},
+				"size": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"updated_at": ` + referenceTimeStr + `
+			}
+		],
+		"author": {
+			"login": "l",
+			"id": 1,
+			"avatar_url": "a",
+			"gravatar_id": "g",
+			"name": "n",
+			"company": "c",
+			"blog": "b",
+			"location": "l",
+			"email": "e",
+			"hireable": true,
+			"bio": "b",
+			"twitter_username": "t",
+			"public_repos": 1,
+			"followers": 1,
+			"following": 1,
+			"created_at": ` + referenceTimeStr + `,
+			"suspended_at": ` + referenceTimeStr + `,
+			"url": "u"
+		},
+		"installation_command": "ic"
+	}`
+
+	testJSONMarshal(t, o, want)
+}
+
+func TestPackage_Marshal(t *testing.T) {
+	testJSONMarshal(t, &Package{}, "{}")
+
+	o := &Package{
+		ID:          Int64(1),
+		Name:        String("name"),
+		PackageType: String("pt"),
+		HTMLURL:     String("hurl"),
+		CreatedAt:   &Timestamp{referenceTime},
+		UpdatedAt:   &Timestamp{referenceTime},
+		Owner: &User{
+			Login:           String("l"),
+			ID:              Int64(1),
+			URL:             String("u"),
+			AvatarURL:       String("a"),
+			GravatarID:      String("g"),
+			Name:            String("n"),
+			Company:         String("c"),
+			Blog:            String("b"),
+			Location:        String("l"),
+			Email:           String("e"),
+			Hireable:        Bool(true),
+			Bio:             String("b"),
+			TwitterUsername: String("t"),
+			PublicRepos:     Int(1),
+			Followers:       Int(1),
+			Following:       Int(1),
+			CreatedAt:       &Timestamp{referenceTime},
+			SuspendedAt:     &Timestamp{referenceTime},
+		},
+		PackageVersion: &PackageVersion{
+			ID:       Int64(1),
+			Version:  String("ver"),
+			Summary:  String("sum"),
+			Body:     String("body"),
+			BodyHTML: String("btnhtml"),
+			Release: &PackageRelease{
+				URL:             String("url"),
+				HTMLURL:         String("hurl"),
+				ID:              Int64(1),
+				TagName:         String("tn"),
+				TargetCommitish: String("tcs"),
+				Name:            String("name"),
+				Draft:           Bool(true),
+				Author: &User{
+					Login:           String("l"),
+					ID:              Int64(1),
+					URL:             String("u"),
+					AvatarURL:       String("a"),
+					GravatarID:      String("g"),
+					Name:            String("n"),
+					Company:         String("c"),
+					Blog:            String("b"),
+					Location:        String("l"),
+					Email:           String("e"),
+					Hireable:        Bool(true),
+					Bio:             String("b"),
+					TwitterUsername: String("t"),
+					PublicRepos:     Int(1),
+					Followers:       Int(1),
+					Following:       Int(1),
+					CreatedAt:       &Timestamp{referenceTime},
+					SuspendedAt:     &Timestamp{referenceTime},
+				},
+				Prerelease:  Bool(true),
+				CreatedAt:   &Timestamp{referenceTime},
+				PublishedAt: &Timestamp{referenceTime},
+			},
+			Manifest:        String("mani"),
+			HTMLURL:         String("hurl"),
+			TagName:         String("tn"),
+			TargetCommitish: String("tcs"),
+			TargetOID:       String("tid"),
+			Draft:           Bool(true),
+			Prerelease:      Bool(true),
+			CreatedAt:       &Timestamp{referenceTime},
+			UpdatedAt:       &Timestamp{referenceTime},
+			PackageFiles: []*PackageFile{
+				{
+					DownloadURL: String("durl"),
+					ID:          Int64(1),
+					Name:        String("name"),
+					SHA256:      String("sha256"),
+					SHA1:        String("sha1"),
+					MD5:         String("md5"),
+					ContentType: String("ct"),
+					State:       String("state"),
+					Author: &User{
+						Login:           String("l"),
+						ID:              Int64(1),
+						URL:             String("u"),
+						AvatarURL:       String("a"),
+						GravatarID:      String("g"),
+						Name:            String("n"),
+						Company:         String("c"),
+						Blog:            String("b"),
+						Location:        String("l"),
+						Email:           String("e"),
+						Hireable:        Bool(true),
+						Bio:             String("b"),
+						TwitterUsername: String("t"),
+						PublicRepos:     Int(1),
+						Followers:       Int(1),
+						Following:       Int(1),
+						CreatedAt:       &Timestamp{referenceTime},
+						SuspendedAt:     &Timestamp{referenceTime},
+					},
+					Size:      Int64(1),
+					CreatedAt: &Timestamp{referenceTime},
+					UpdatedAt: &Timestamp{referenceTime},
+				},
+			},
+			Author: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			InstallationCommand: String("ic"),
+		},
+		Registry: &PackageRegistry{
+			AboutURL: String("aurl"),
+			Name:     String("name"),
+			Type:     String("type"),
+			URL:      String("url"),
+			Vendor:   String("vendor"),
+		},
+	}
+
+	want := `{
+		"id": 1,
+		"name": "name",
+		"package_type": "pt",
+		"html_url": "hurl",
+		"created_at": ` + referenceTimeStr + `,
+		"updated_at": ` + referenceTimeStr + `,
+		"owner": {
+			"login": "l",
+			"id": 1,
+			"avatar_url": "a",
+			"gravatar_id": "g",
+			"name": "n",
+			"company": "c",
+			"blog": "b",
+			"location": "l",
+			"email": "e",
+			"hireable": true,
+			"bio": "b",
+			"twitter_username": "t",
+			"public_repos": 1,
+			"followers": 1,
+			"following": 1,
+			"created_at": ` + referenceTimeStr + `,
+			"suspended_at": ` + referenceTimeStr + `,
+			"url": "u"
+		},
+		"package_version": {
+			"id": 1,
+			"version": "ver",
+			"summary": "sum",
+			"body": "body",
+			"body_html": "btnhtml",
+			"release": {
+				"url": "url",
+				"html_url": "hurl",
+				"id": 1,
+				"tag_name": "tn",
+				"target_commitish": "tcs",
+				"name": "name",
+				"draft": true,
+				"author": {
+					"login": "l",
+					"id": 1,
+					"avatar_url": "a",
+					"gravatar_id": "g",
+					"name": "n",
+					"company": "c",
+					"blog": "b",
+					"location": "l",
+					"email": "e",
+					"hireable": true,
+					"bio": "b",
+					"twitter_username": "t",
+					"public_repos": 1,
+					"followers": 1,
+					"following": 1,
+					"created_at": ` + referenceTimeStr + `,
+					"suspended_at": ` + referenceTimeStr + `,
+					"url": "u"
+				},
+				"prerelease": true,
+				"created_at": ` + referenceTimeStr + `,
+				"published_at": ` + referenceTimeStr + `
+			},
+			"manifest": "mani",
+			"html_url": "hurl",
+			"tag_name": "tn",
+			"target_commitish": "tcs",
+			"target_oid": "tid",
+			"draft": true,
+			"prerelease": true,
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"package_files": [
+				{
+					"download_url": "durl",
+					"id": 1,
+					"name": "name",
+					"sha256": "sha256",
+					"sha1": "sha1",
+					"md5": "md5",
+					"content_type": "ct",
+					"state": "state",
+					"author": {
+						"login": "l",
+						"id": 1,
+						"avatar_url": "a",
+						"gravatar_id": "g",
+						"name": "n",
+						"company": "c",
+						"blog": "b",
+						"location": "l",
+						"email": "e",
+						"hireable": true,
+						"bio": "b",
+						"twitter_username": "t",
+						"public_repos": 1,
+						"followers": 1,
+						"following": 1,
+						"created_at": ` + referenceTimeStr + `,
+						"suspended_at": ` + referenceTimeStr + `,
+						"url": "u"
+					},
+					"size": 1,
+					"created_at": ` + referenceTimeStr + `,
+					"updated_at": ` + referenceTimeStr + `
+				}
+			],
+			"author": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"installation_command": "ic"
+		},
+		"registry": {
+			"about_url": "aurl",
+			"name": "name",
+			"type": "type",
+			"url": "url",
+			"vendor": "vendor"
+		}
+	}`
+
+	testJSONMarshal(t, o, want)
+}