Repository: google/go-github
Pull request #2062 (2021-08-27 00:28:49)

Implement SCIM
- Implement SCIM Service

API Docs: https://docs.github.com/en/rest/reference/scim#list-scim-provisioned-identities--parameters
Fixes: #2030.


Patch:
diff --git a/.gitignore b/.gitignore
index 3d439783..aea73858 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ coverage.out
 # intellij files
 .idea/
 vendor/
-.DS_Store
\ No newline at end of file
+.DS_Store
+.vscode
\ No newline at end of file
diff --git a/github/github-accessors.go b/github/github-accessors.go
index bd94ba94..884c49f8 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -1188,6 +1188,46 @@ func (a *AuthorizationUpdateRequest) GetNoteURL() string {
 	return *a.NoteURL
 }
 
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (a *Autolink) GetID() int64 {
+	if a == nil || a.ID == nil {
+		return 0
+	}
+	return *a.ID
+}
+
+// GetKeyPrefix returns the KeyPrefix field if it's non-nil, zero value otherwise.
+func (a *Autolink) GetKeyPrefix() string {
+	if a == nil || a.KeyPrefix == nil {
+		return ""
+	}
+	return *a.KeyPrefix
+}
+
+// GetURLTemplate returns the URLTemplate field if it's non-nil, zero value otherwise.
+func (a *Autolink) GetURLTemplate() string {
+	if a == nil || a.URLTemplate == nil {
+		return ""
+	}
+	return *a.URLTemplate
+}
+
+// GetKeyPrefix returns the KeyPrefix field if it's non-nil, zero value otherwise.
+func (a *AutolinkOptions) GetKeyPrefix() string {
+	if a == nil || a.KeyPrefix == nil {
+		return ""
+	}
+	return *a.KeyPrefix
+}
+
+// GetURLTemplate returns the URLTemplate field if it's non-nil, zero value otherwise.
+func (a *AutolinkOptions) GetURLTemplate() string {
+	if a == nil || a.URLTemplate == nil {
+		return ""
+	}
+	return *a.URLTemplate
+}
+
 // GetAppID returns the AppID field if it's non-nil, zero value otherwise.
 func (a *AutoTriggerCheck) GetAppID() int64 {
 	if a == nil || a.AppID == nil {
@@ -7180,6 +7220,30 @@ func (l *ListRepositories) GetTotalCount() int {
 	return *l.TotalCount
 }
 
+// GetCount returns the Count field if it's non-nil, zero value otherwise.
+func (l *ListSCIMProvisionedIdentitiesOptions) GetCount() int {
+	if l == nil || l.Count == nil {
+		return 0
+	}
+	return *l.Count
+}
+
+// GetFilter returns the Filter field if it's non-nil, zero value otherwise.
+func (l *ListSCIMProvisionedIdentitiesOptions) GetFilter() string {
+	if l == nil || l.Filter == nil {
+		return ""
+	}
+	return *l.Filter
+}
+
+// GetStartIndex returns the StartIndex field if it's non-nil, zero value otherwise.
+func (l *ListSCIMProvisionedIdentitiesOptions) GetStartIndex() int {
+	if l == nil || l.StartIndex == nil {
+		return 0
+	}
+	return *l.StartIndex
+}
+
 // GetEndColumn returns the EndColumn field if it's non-nil, zero value otherwise.
 func (l *Location) GetEndColumn() int {
 	if l == nil || l.EndColumn == nil {
@@ -12564,6 +12628,14 @@ func (r *RepositoriesSearchResult) GetTotal() int {
 	return *r.Total
 }
 
+// GetAllowAutoMerge returns the AllowAutoMerge field if it's non-nil, zero value otherwise.
+func (r *Repository) GetAllowAutoMerge() bool {
+	if r == nil || r.AllowAutoMerge == nil {
+		return false
+	}
+	return *r.AllowAutoMerge
+}
+
 // GetAllowMergeCommit returns the AllowMergeCommit field if it's non-nil, zero value otherwise.
 func (r *Repository) GetAllowMergeCommit() bool {
 	if r == nil || r.AllowMergeCommit == nil {
@@ -14572,6 +14644,54 @@ func (r *RunnerLabels) GetType() string {
 	return *r.Type
 }
 
+// GetActive returns the Active field if it's non-nil, zero value otherwise.
+func (s *SCIMUserAttributes) GetActive() bool {
+	if s == nil || s.Active == nil {
+		return false
+	}
+	return *s.Active
+}
+
+// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise.
+func (s *SCIMUserAttributes) GetDisplayName() string {
+	if s == nil || s.DisplayName == nil {
+		return ""
+	}
+	return *s.DisplayName
+}
+
+// GetExternalID returns the ExternalID field if it's non-nil, zero value otherwise.
+func (s *SCIMUserAttributes) GetExternalID() string {
+	if s == nil || s.ExternalID == nil {
+		return ""
+	}
+	return *s.ExternalID
+}
+
+// GetPrimary returns the Primary field if it's non-nil, zero value otherwise.
+func (s *SCIMUserEmail) GetPrimary() bool {
+	if s == nil || s.Primary == nil {
+		return false
+	}
+	return *s.Primary
+}
+
+// GetType returns the Type field if it's non-nil, zero value otherwise.
+func (s *SCIMUserEmail) GetType() string {
+	if s == nil || s.Type == nil {
+		return ""
+	}
+	return *s.Type
+}
+
+// GetFormatted returns the Formatted field if it's non-nil, zero value otherwise.
+func (s *SCIMUserName) GetFormatted() string {
+	if s == nil || s.Formatted == nil {
+		return ""
+	}
+	return *s.Formatted
+}
+
 // GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise.
 func (s *SelectedReposList) GetTotalCount() int {
 	if s == nil || s.TotalCount == nil {
@@ -16116,6 +16236,14 @@ func (t *TreeEntry) GetURL() string {
 	return *t.URL
 }
 
+// GetPath returns the Path field if it's non-nil, zero value otherwise.
+func (u *UpdateAttributeForSCIMUserOperations) GetPath() string {
+	if u == nil || u.Path == nil {
+		return ""
+	}
+	return *u.Path
+}
+
 // GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise.
 func (u *UpdateCheckRunOptions) GetCompletedAt() Timestamp {
 	if u == nil || u.CompletedAt == nil {
diff --git a/github/github.go b/github/github.go
index 56b63d17..b83e4306 100644
--- a/github/github.go
+++ b/github/github.go
@@ -181,6 +181,7 @@ type Client struct {
 	PullRequests   *PullRequestsService
 	Reactions      *ReactionsService
 	Repositories   *RepositoriesService
+	SCIM           *SCIMService
 	Search         *SearchService
 	Teams          *TeamsService
 	Users          *UsersService
@@ -308,6 +309,7 @@ func NewClient(httpClient *http.Client) *Client {
 	c.PullRequests = (*PullRequestsService)(&c.common)
 	c.Reactions = (*ReactionsService)(&c.common)
 	c.Repositories = (*RepositoriesService)(&c.common)
+	c.SCIM = (*SCIMService)(&c.common)
 	c.Search = (*SearchService)(&c.common)
 	c.Teams = (*TeamsService)(&c.common)
 	c.Users = (*UsersService)(&c.common)
diff --git a/github/repos.go b/github/repos.go
index eaadbb43..aab18d4d 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -60,6 +60,7 @@ type Repository struct {
 	AllowRebaseMerge    *bool           `json:"allow_rebase_merge,omitempty"`
 	AllowSquashMerge    *bool           `json:"allow_squash_merge,omitempty"`
 	AllowMergeCommit    *bool           `json:"allow_merge_commit,omitempty"`
+	AllowAutoMerge      *bool           `json:"allow_auto_merge,omitempty"`
 	DeleteBranchOnMerge *bool           `json:"delete_branch_on_merge,omitempty"`
 	Topics              []string        `json:"topics,omitempty"`
 	Archived            *bool           `json:"archived,omitempty"`
@@ -320,6 +321,7 @@ type createRepoRequest struct {
 	AllowSquashMerge    *bool   `json:"allow_squash_merge,omitempty"`
 	AllowMergeCommit    *bool   `json:"allow_merge_commit,omitempty"`
 	AllowRebaseMerge    *bool   `json:"allow_rebase_merge,omitempty"`
+	AllowAutoMerge      *bool   `json:"allow_auto_merge,omitempty"`
 	DeleteBranchOnMerge *bool   `json:"delete_branch_on_merge,omitempty"`
 }
 
@@ -362,6 +364,7 @@ func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repo
 		AllowSquashMerge:    repo.AllowSquashMerge,
 		AllowMergeCommit:    repo.AllowMergeCommit,
 		AllowRebaseMerge:    repo.AllowRebaseMerge,
+		AllowAutoMerge:      repo.AllowAutoMerge,
 		DeleteBranchOnMerge: repo.DeleteBranchOnMerge,
 	}
 
diff --git a/github/repos_autolinks.go b/github/repos_autolinks.go
new file mode 100644
index 00000000..b6404783
--- /dev/null
+++ b/github/repos_autolinks.go
@@ -0,0 +1,102 @@
+// 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 (
+	"context"
+	"fmt"
+)
+
+// AutolinkOptions specifies parameters for RepositoriesService.AddAutolink method.
+type AutolinkOptions struct {
+	KeyPrefix   *string `json:"key_prefix,omitempty"`
+	URLTemplate *string `json:"url_template,omitempty"`
+}
+
+// Autolink represents autolinks to external resources like JIRA issues and Zendesk tickets.
+type Autolink struct {
+	ID          *int64  `json:"id,omitempty"`
+	KeyPrefix   *string `json:"key_prefix,omitempty"`
+	URLTemplate *string `json:"url_template,omitempty"`
+}
+
+// ListAutolinks returns a list of autolinks configured for the given repository.
+// Information about autolinks are only available to repository administrators.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/repos#list-all-autolinks-of-a-repository
+func (s *RepositoriesService) ListAutolinks(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Autolink, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/autolinks", owner, repo)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var autolinks []*Autolink
+	resp, err := s.client.Do(ctx, req, &autolinks)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return autolinks, resp, nil
+}
+
+// AddAutolink creates an autolink reference for a repository.
+// Users with admin access to the repository can create an autolink.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/repos#create-an-autolink-reference-for-a-repository
+func (s *RepositoriesService) AddAutolink(ctx context.Context, owner, repo string, opts *AutolinkOptions) (*Autolink, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/autolinks", owner, repo)
+	req, err := s.client.NewRequest("POST", u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	al := new(Autolink)
+	resp, err := s.client.Do(ctx, req, al)
+	if err != nil {
+		return nil, resp, err
+	}
+	return al, resp, nil
+}
+
+// GetAutolink returns a single autolink reference by ID that was configured for the given repository.
+// Information about autolinks are only available to repository administrators.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/repos#get-an-autolink-reference-of-a-repository
+func (s *RepositoriesService) GetAutolink(ctx context.Context, owner, repo string, id int64) (*Autolink, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/autolinks/%v", owner, repo, id)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var autolink *Autolink
+	resp, err := s.client.Do(ctx, req, &autolink)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return autolink, resp, nil
+}
+
+// DeleteAutolink deletes a single autolink reference by ID that was configured for the given repository.
+// Information about autolinks are only available to repository administrators.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/repos#delete-an-autolink-reference-from-a-repository
+func (s *RepositoriesService) DeleteAutolink(ctx context.Context, owner, repo string, id int64) (*Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/autolinks/%v", owner, repo, id)
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
diff --git a/github/scim.go b/github/scim.go
new file mode 100644
index 00000000..7a12d85b
--- /dev/null
+++ b/github/scim.go
@@ -0,0 +1,163 @@
+// 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 (
+	"context"
+	"encoding/json"
+	"fmt"
+)
+
+// SCIMService provides access to SCIM related functions in the
+// GitHub API.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim
+type SCIMService service
+
+// SCIMUserAttributes represents supported SCIM User attributes.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#supported-scim-user-attributes
+type SCIMUserAttributes struct {
+	UserName    string           `json:"userName"`              // Configured by the admin. Could be an email, login, or username. (Required.)
+	Name        SCIMUserName     `json:"name"`                  // (Required.)
+	DisplayName *string          `json:"displayName,omitempty"` // The name of the user, suitable for display to end-users. (Optional.)
+	Emails      []*SCIMUserEmail `json:"emails"`                // User emails. (Required.)
+	Schemas     []string         `json:"schemas,omitempty"`     // (Optional.)
+	ExternalID  *string          `json:"externalId,omitempty"`  // (Optional.)
+	Groups      []string         `json:"groups,omitempty"`      // (Optional.)
+	Active      *bool            `json:"active,omitempty"`      // (Optional.)
+}
+
+// SCIMUserName represents SCIM user information.
+type SCIMUserName struct {
+	GivenName  string  `json:"givenName"`           // The first name of the user. (Required.)
+	FamilyName string  `json:"familyName"`          // The family name of the user. (Required.)
+	Formatted  *string `json:"formatted,omitempty"` // (Optional.)
+}
+
+//SCIMUserEmail represents SCIM user email.
+type SCIMUserEmail struct {
+	Value   string  `json:"value"`             // (Required.)
+	Primary *bool   `json:"primary,omitempty"` // (Optional.)
+	Type    *string `json:"type,omitempty"`    // (Optional.)
+}
+
+// ListSCIMProvisionedIdentitiesOptions represents options for ListSCIMProvisionedIdentities.
+//
+// Github API docs: https://docs.github.com/en/rest/reference/scim#list-scim-provisioned-identities--parameters
+type ListSCIMProvisionedIdentitiesOptions struct {
+	StartIndex *int `json:"startIndex,omitempty"` // Used for pagination: the index of the first result to return. (Optional.)
+	Count      *int `json:"count,omitempty"`      // Used for pagination: the number of results to return. (Optional.)
+	// Filter results using the equals query parameter operator (eq).
+	// You can filter results that are equal to id, userName, emails, and external_id.
+	// For example, to search for an identity with the userName Octocat, you would use this query: ?filter=userName%20eq%20\"Octocat\".
+	// To filter results for the identity with the email octocat@github.com, you would use this query: ?filter=emails%20eq%20\"octocat@github.com\".
+	// (Optional.)
+	Filter *string `json:"filter,omitempty"`
+}
+
+// ListSCIMProvisionedIdentities lists SCIM provisioned identities.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#list-scim-provisioned-identities
+func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org string, opts *ListSCIMProvisionedIdentitiesOptions) (*Response, error) {
+	u := fmt.Sprintf("scim/v2/organizations/%v/Users", org)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, err
+	}
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
+
+// ProvisionAndInviteSCIMUser provisions organization membership for a user, and sends an activation email to the email address.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#provision-and-invite-a-scim-user
+func (s *SCIMService) ProvisionAndInviteSCIMUser(ctx context.Context, org string, opts *SCIMUserAttributes) (*Response, error) {
+	u := fmt.Sprintf("scim/v2/organizations/%v/Users", org)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, err
+	}
+	req, err := s.client.NewRequest("POST", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
+
+// GetSCIMProvisioningInfoForUser returns SCIM provisioning information for a user.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#get-scim-provisioning-information-for-a-user
+func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, scimUserID string) (*Response, error) {
+	u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
+
+// UpdateProvisionedOrgMembership updates a provisioned organization membership.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#update-a-provisioned-organization-membership
+func (s *SCIMService) UpdateProvisionedOrgMembership(ctx context.Context, org, scimUserID string, opts *SCIMUserAttributes) (*Response, error) {
+	u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, err
+	}
+	req, err := s.client.NewRequest("PUT", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
+
+// UpdateAttributeForSCIMUserOptions represents options for UpdateAttributeForSCIMUser.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#update-an-attribute-for-a-scim-user--parameters
+type UpdateAttributeForSCIMUserOptions struct {
+	Schemas    []string                             `json:"schemas,omitempty"` // (Optional.)
+	Operations UpdateAttributeForSCIMUserOperations `json:"operations"`        // Set of operations to be performed. (Required.)
+}
+
+// UpdateAttributeForSCIMUserOperations represents operations for UpdateAttributeForSCIMUser.
+type UpdateAttributeForSCIMUserOperations struct {
+	Op    string          `json:"op"`              // (Required.)
+	Path  *string         `json:"path,omitempty"`  // (Optional.)
+	Value json.RawMessage `json:"value,omitempty"` // (Optional.)
+}
+
+// UpdateAttributeForSCIMUser updates an attribute for an SCIM user.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#update-an-attribute-for-a-scim-user
+func (s *SCIMService) UpdateAttributeForSCIMUser(ctx context.Context, org, scimUserID string, opts *UpdateAttributeForSCIMUserOptions) (*Response, error) {
+	u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, err
+	}
+	req, err := s.client.NewRequest("PATCH", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
+
+// DeleteSCIMUserFromOrg deletes SCIM user from an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/scim#delete-a-scim-user-from-an-organization
+func (s *SCIMService) DeleteSCIMUserFromOrg(ctx context.Context, org, scimUserID string) (*Response, error) {
+	u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID)
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 39ae72b2..f360911c 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -72,7 +72,7 @@ jobs:
 
     - name: Upload coverage to Codecov
       if: ${{ matrix.update-coverage }}
-      uses: codecov/codecov-action@51d810878be5422784e86451c0e7c14e5860ec47 #v2.0.2
+      uses: codecov/codecov-action@5a8bb4701eca7ba3673f21664b887f652c58d0a3 #v2.0.3
 
     - name: Ensure go generate produces a zero diff for update-urls
       shell: bash
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 4264ce8b..06b53b65 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -1417,6 +1417,56 @@ func TestAuthorizationUpdateRequest_GetNoteURL(tt *testing.T) {
 	a.GetNoteURL()
 }
 
+func TestAutolink_GetID(tt *testing.T) {
+	var zeroValue int64
+	a := &Autolink{ID: &zeroValue}
+	a.GetID()
+	a = &Autolink{}
+	a.GetID()
+	a = nil
+	a.GetID()
+}
+
+func TestAutolink_GetKeyPrefix(tt *testing.T) {
+	var zeroValue string
+	a := &Autolink{KeyPrefix: &zeroValue}
+	a.GetKeyPrefix()
+	a = &Autolink{}
+	a.GetKeyPrefix()
+	a = nil
+	a.GetKeyPrefix()
+}
+
+func TestAutolink_GetURLTemplate(tt *testing.T) {
+	var zeroValue string
+	a := &Autolink{URLTemplate: &zeroValue}
+	a.GetURLTemplate()
+	a = &Autolink{}
+	a.GetURLTemplate()
+	a = nil
+	a.GetURLTemplate()
+}
+
+func TestAutolinkOptions_GetKeyPrefix(tt *testing.T) {
+	var zeroValue string
+	a := &AutolinkOptions{KeyPrefix: &zeroValue}
+	a.GetKeyPrefix()
+	a = &AutolinkOptions{}
+	a.GetKeyPrefix()
+	a = nil
+	a.GetKeyPrefix()
+}
+
+func TestAutolinkOptions_GetURLTemplate(tt *testing.T) {
+	var zeroValue string
+	a := &AutolinkOptions{URLTemplate: &zeroValue}
+	a.GetURLTemplate()
+	a = &AutolinkOptions{}
+	a.GetURLTemplate()
+	a = nil
+	a.GetURLTemplate()
+}
+
 func TestAutoTriggerCheck_GetAppID(tt *testing.T) {
 	var zeroValue int64
 	a := &AutoTriggerCheck{AppID: &zeroValue}
@@ -8427,6 +8477,36 @@ func TestListRepositories_GetTotalCount(tt *testing.T) {
 	l.GetTotalCount()
 }
 
+func TestListSCIMProvisionedIdentitiesOptions_GetCount(tt *testing.T) {
+	var zeroValue int
+	l := &ListSCIMProvisionedIdentitiesOptions{Count: &zeroValue}
+	l.GetCount()
+	l = &ListSCIMProvisionedIdentitiesOptions{}
+	l.GetCount()
+	l = nil
+	l.GetCount()
+}
+
+func TestListSCIMProvisionedIdentitiesOptions_GetFilter(tt *testing.T) {
+	var zeroValue string
+	l := &ListSCIMProvisionedIdentitiesOptions{Filter: &zeroValue}
+	l.GetFilter()
+	l = &ListSCIMProvisionedIdentitiesOptions{}
+	l.GetFilter()
+	l = nil
+	l.GetFilter()
+}
+
+func TestListSCIMProvisionedIdentitiesOptions_GetStartIndex(tt *testing.T) {
+	var zeroValue int
+	l := &ListSCIMProvisionedIdentitiesOptions{StartIndex: &zeroValue}
+	l.GetStartIndex()
+	l = &ListSCIMProvisionedIdentitiesOptions{}
+	l.GetStartIndex()
+	l = nil
+	l.GetStartIndex()
+}
+
 func TestLocation_GetEndColumn(tt *testing.T) {
 	var zeroValue int
 	l := &Location{EndColumn: &zeroValue}
@@ -14641,6 +14721,16 @@ func TestRepositoriesSearchResult_GetTotal(tt *testing.T) {
 	r.GetTotal()
 }
 
+func TestRepository_GetAllowAutoMerge(tt *testing.T) {
+	var zeroValue bool
+	r := &Repository{AllowAutoMerge: &zeroValue}
+	r.GetAllowAutoMerge()
+	r = &Repository{}
+	r.GetAllowAutoMerge()
+	r = nil
+	r.GetAllowAutoMerge()
+}
+
 func TestRepository_GetAllowMergeCommit(tt *testing.T) {
 	var zeroValue bool
 	r := &Repository{AllowMergeCommit: &zeroValue}
@@ -17046,6 +17136,66 @@ func TestRunnerLabels_GetType(tt *testing.T) {
 	r.GetType()
 }
 
+func TestSCIMUserAttributes_GetActive(tt *testing.T) {
+	var zeroValue bool
+	s := &SCIMUserAttributes{Active: &zeroValue}
+	s.GetActive()
+	s = &SCIMUserAttributes{}
+	s.GetActive()
+	s = nil
+	s.GetActive()
+}
+
+func TestSCIMUserAttributes_GetDisplayName(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMUserAttributes{DisplayName: &zeroValue}
+	s.GetDisplayName()
+	s = &SCIMUserAttributes{}
+	s.GetDisplayName()
+	s = nil
+	s.GetDisplayName()
+}
+
+func TestSCIMUserAttributes_GetExternalID(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMUserAttributes{ExternalID: &zeroValue}
+	s.GetExternalID()
+	s = &SCIMUserAttributes{}
+	s.GetExternalID()
+	s = nil
+	s.GetExternalID()
+}
+
+func TestSCIMUserEmail_GetPrimary(tt *testing.T) {
+	var zeroValue bool
+	s := &SCIMUserEmail{Primary: &zeroValue}
+	s.GetPrimary()
+	s = &SCIMUserEmail{}
+	s.GetPrimary()
+	s = nil
+	s.GetPrimary()
+}
+
+func TestSCIMUserEmail_GetType(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMUserEmail{Type: &zeroValue}
+	s.GetType()
+	s = &SCIMUserEmail{}
+	s.GetType()
+	s = nil
+	s.GetType()
+}
+
+func TestSCIMUserName_GetFormatted(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMUserName{Formatted: &zeroValue}
+	s.GetFormatted()
+	s = &SCIMUserName{}
+	s.GetFormatted()
+	s = nil
+	s.GetFormatted()
+}
+
 func TestSelectedReposList_GetTotalCount(tt *testing.T) {
 	var zeroValue int
 	s := &SelectedReposList{TotalCount: &zeroValue}
@@ -18847,6 +18997,16 @@ func TestTreeEntry_GetURL(tt *testing.T) {
 	t.GetURL()
 }
 
+func TestUpdateAttributeForSCIMUserOperations_GetPath(tt *testing.T) {
+	var zeroValue string
+	u := &UpdateAttributeForSCIMUserOperations{Path: &zeroValue}
+	u.GetPath()
+	u = &UpdateAttributeForSCIMUserOperations{}
+	u.GetPath()
+	u = nil
+	u.GetPath()
+}
+
 func TestUpdateCheckRunOptions_GetCompletedAt(tt *testing.T) {
 	var zeroValue Timestamp
 	u := &UpdateCheckRunOptions{CompletedAt: &zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index f56d7d4d..f3cab123 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -1422,6 +1422,7 @@ func TestRepository_String(t *testing.T) {
 		AllowRebaseMerge:    Bool(false),
 		AllowSquashMerge:    Bool(false),
 		AllowMergeCommit:    Bool(false),
+		AllowAutoMerge:      Bool(false),
 		DeleteBranchOnMerge: Bool(false),
 		Archived:            Bool(false),
 		Disabled:            Bool(false),
@@ -1475,7 +1476,7 @@ func TestRepository_String(t *testing.T) {
 		TeamsURL:            String(""),
 		Visibility:          String(""),
 	}
-	want := `github.Repository{ID:0, NodeID:"", Owner:github.User{}, Name:"", FullName:"", Description:"", Homepage:"", CodeOfConduct:github.CodeOfConduct{}, DefaultBranch:"", MasterBranch:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PushedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HTMLURL:"", CloneURL:"", GitURL:"", MirrorURL:"", SSHURL:"", SVNURL:"", Language:"", Fork:false, ForksCount:0, NetworkCount:0, OpenIssuesCount:0, OpenIssues:0, StargazersCount:0, SubscribersCount:0, WatchersCount:0, Watchers:0, Size:0, AutoInit:false, Parent:github.Repository{}, Source:github.Repository{}, TemplateRepository:github.Repository{}, Organization:github.Organization{}, AllowRebaseMerge:false, AllowSquashMerge:false, AllowMergeCommit:false, DeleteBranchOnMerge:false, Archived:false, Disabled:false, License:github.License{}, Private:false, HasIssues:false, HasWiki:false, HasPages:false, HasProjects:false, HasDownloads:false, IsTemplate:false, LicenseTemplate:"", GitignoreTemplate:"", TeamID:0, URL:"", ArchiveURL:"", AssigneesURL:"", BlobsURL:"", BranchesURL:"", CollaboratorsURL:"", CommentsURL:"", CommitsURL:"", CompareURL:"", ContentsURL:"", ContributorsURL:"", DeploymentsURL:"", DownloadsURL:"", EventsURL:"", ForksURL:"", GitCommitsURL:"", GitRefsURL:"", GitTagsURL:"", HooksURL:"", IssueCommentURL:"", IssueEventsURL:"", IssuesURL:"", KeysURL:"", LabelsURL:"", LanguagesURL:"", MergesURL:"", MilestonesURL:"", NotificationsURL:"", PullsURL:"", ReleasesURL:"", StargazersURL:"", StatusesURL:"", SubscribersURL:"", SubscriptionURL:"", TagsURL:"", TreesURL:"", TeamsURL:"", Visibility:""}`
+	want := `github.Repository{ID:0, NodeID:"", Owner:github.User{}, Name:"", FullName:"", Description:"", Homepage:"", CodeOfConduct:github.CodeOfConduct{}, DefaultBranch:"", MasterBranch:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PushedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HTMLURL:"", CloneURL:"", GitURL:"", MirrorURL:"", SSHURL:"", SVNURL:"", Language:"", Fork:false, ForksCount:0, NetworkCount:0, OpenIssuesCount:0, OpenIssues:0, StargazersCount:0, SubscribersCount:0, WatchersCount:0, Watchers:0, Size:0, AutoInit:false, Parent:github.Repository{}, Source:github.Repository{}, TemplateRepository:github.Repository{}, Organization:github.Organization{}, AllowRebaseMerge:false, AllowSquashMerge:false, AllowMergeCommit:false, AllowAutoMerge:false, DeleteBranchOnMerge:false, Archived:false, Disabled:false, License:github.License{}, Private:false, HasIssues:false, HasWiki:false, HasPages:false, HasProjects:false, HasDownloads:false, IsTemplate:false, LicenseTemplate:"", GitignoreTemplate:"", TeamID:0, URL:"", ArchiveURL:"", AssigneesURL:"", BlobsURL:"", BranchesURL:"", CollaboratorsURL:"", CommentsURL:"", CommitsURL:"", CompareURL:"", ContentsURL:"", ContributorsURL:"", DeploymentsURL:"", DownloadsURL:"", EventsURL:"", ForksURL:"", GitCommitsURL:"", GitRefsURL:"", GitTagsURL:"", HooksURL:"", IssueCommentURL:"", IssueEventsURL:"", IssuesURL:"", KeysURL:"", LabelsURL:"", LanguagesURL:"", MergesURL:"", MilestonesURL:"", NotificationsURL:"", PullsURL:"", ReleasesURL:"", StargazersURL:"", StatusesURL:"", SubscribersURL:"", SubscriptionURL:"", TagsURL:"", TreesURL:"", TeamsURL:"", Visibility:""}`
 	if got := v.String(); got != want {
 		t.Errorf("Repository.String = %v, want %v", got, want)
 	}
diff --git a/github/repos_autolinks_test.go b/github/repos_autolinks_test.go
new file mode 100644
index 00000000..b6121d2c
--- /dev/null
+++ b/github/repos_autolinks_test.go
@@ -0,0 +1,154 @@
+// 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 (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestRepositoriesService_ListAutolinks(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/autolinks", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"page": "2"})
+		fmt.Fprintf(w, `[{"id":1, "key_prefix": "TICKET-", "url_template": "https://example.com/TICKET?query=<num>"}, {"id":2, "key_prefix": "STORY-", "url_template": "https://example.com/STORY?query=<num>"}]`)
+	})
+
+	opt := &ListOptions{
+		Page: 2,
+	}
+	ctx := context.Background()
+	autolinks, _, err := client.Repositories.ListAutolinks(ctx, "o", "r", opt)
+	if err != nil {
+		t.Errorf("Repositories.ListAutolinks returned error: %v", err)
+	}
+
+	want := []*Autolink{
+		{ID: Int64(1), KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=<num>")},
+		{ID: Int64(2), KeyPrefix: String("STORY-"), URLTemplate: String("https://example.com/STORY?query=<num>")},
+	}
+
+	if !cmp.Equal(autolinks, want) {
+		t.Errorf("Repositories.ListAutolinks returned %+v, want %+v", autolinks, want)
+	}
+
+	const methodName = "ListAutolinks"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.ListAutolinks(ctx, "\n", "\n", opt)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.ListAutolinks(ctx, "o", "r", opt)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_AddAutolink(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	opt := &AutolinkOptions{KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=<num>")}
+	mux.HandleFunc("/repos/o/r/autolinks", func(w http.ResponseWriter, r *http.Request) {
+		v := new(AutolinkOptions)
+		json.NewDecoder(r.Body).Decode(v)
+		testMethod(t, r, "POST")
+		if !cmp.Equal(v, opt) {
+			t.Errorf("Request body = %+v, want %+v", v, opt)
+		}
+		w.WriteHeader(http.StatusOK)
+		w.Write([]byte(`{"key_prefix": "TICKET-","url_template": "https://example.com/TICKET?query=<num>"}`))
+	})
+	ctx := context.Background()
+	autolink, _, err := client.Repositories.AddAutolink(ctx, "o", "r", opt)
+	if err != nil {
+		t.Errorf("Repositories.AddAutolink returned error: %v", err)
+	}
+	want := &Autolink{
+		KeyPrefix:   String("TICKET-"),
+		URLTemplate: String("https://example.com/TICKET?query=<num>"),
+	}
+
+	if !cmp.Equal(autolink, want) {
+		t.Errorf("AddAutolink returned %+v, want %+v", autolink, want)
+	}
+
+	const methodName = "AddAutolink"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.AddAutolink(ctx, "\n", "\n", opt)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.AddAutolink(ctx, "o", "r", opt)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_GetAutolink(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/autolinks/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprintf(w, `{"id":1, "key_prefix": "TICKET-", "url_template": "https://example.com/TICKET?query=<num>"}`)
+	})
+
+	ctx := context.Background()
+	autolink, _, err := client.Repositories.GetAutolink(ctx, "o", "r", 1)
+	if err != nil {
+		t.Errorf("Repositories.GetAutolink returned error: %v", err)
+	}
+
+	want := &Autolink{ID: Int64(1), KeyPrefix: String("TICKET-"), URLTemplate: String("https://example.com/TICKET?query=<num>")}
+	if !cmp.Equal(autolink, want) {
+		t.Errorf("Repositories.GetAutolink returned %+v, want %+v", autolink, want)
+	}
+
+	const methodName = "GetAutolink"
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.GetAutolink(ctx, "o", "r", 2)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_DeleteAutolink(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/autolinks/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	_, err := client.Repositories.DeleteAutolink(ctx, "o", "r", 1)
+	if err != nil {
+		t.Errorf("Repositories.DeleteAutolink returned error: %v", err)
+	}
+
+	const methodName = "DeleteAutolink"
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Repositories.DeleteAutolink(ctx, "o", "r", 2)
+	})
+}
diff --git a/github/repos_invitations_test.go b/github/repos_invitations_test.go
index e05ff3cd..74bda793 100644
--- a/github/repos_invitations_test.go
+++ b/github/repos_invitations_test.go
@@ -111,3 +111,55 @@ func TestRepositoriesService_UpdateInvitation(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestRepositoryInvitation_Marshal(t *testing.T) {
+	testJSONMarshal(t, &RepositoryInvitation{}, "{}")
+
+	r := &RepositoryInvitation{
+		ID: Int64(1),
+		Repo: &Repository{
+			ID:   Int64(1),
+			Name: String("n"),
+			URL:  String("u"),
+		},
+		Invitee: &User{
+			ID:   Int64(1),
+			Name: String("n"),
+			URL:  String("u"),
+		},
+		Inviter: &User{
+			ID:   Int64(1),
+			Name: String("n"),
+			URL:  String("u"),
+		},
+		Permissions: String("p"),
+		CreatedAt:   &Timestamp{referenceTime},
+		URL:         String("u"),
+		HTMLURL:     String("h"),
+	}
+
+	want := `{
+		"id":1,
+		"repository":{
+			"id":1,
+			"name":"n",
+			"url":"u"
+		},
+		"invitee":{
+			"id":1,
+			"name":"n",
+			"url":"u"
+		},
+		"inviter":{
+			"id":1,
+			"name":"n",
+			"url":"u"
+		},
+		"permissions":"p",
+		"created_at":` + referenceTimeStr + `,
+		"url":"u",
+		"html_url":"h"
+	}`
+
+	testJSONMarshal(t, r, want)
+}
diff --git a/github/repos_merging_test.go b/github/repos_merging_test.go
index 0d099552..05263100 100644
--- a/github/repos_merging_test.go
+++ b/github/repos_merging_test.go
@@ -62,3 +62,21 @@ func TestRepositoriesService_Merge(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestRepositoryMergeRequest_Marshal(t *testing.T) {
+	testJSONMarshal(t, &RepositoryMergeRequest{}, "{}")
+
+	u := &RepositoryMergeRequest{
+		Base:          String("base"),
+		Head:          String("head"),
+		CommitMessage: String("cm"),
+	}
+
+	want := `{
+		"base": "base",
+		"head": "head",
+		"commit_message": "cm"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_pages_test.go b/github/repos_pages_test.go
index 6a4d2ad5..85069f94 100644
--- a/github/repos_pages_test.go
+++ b/github/repos_pages_test.go
@@ -357,3 +357,133 @@ func TestRepositoriesService_RequestPageBuild(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestPagesSource_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PagesSource{}, "{}")
+
+	u := &PagesSource{
+		Branch: String("branch"),
+		Path:   String("path"),
+	}
+
+	want := `{
+		"branch": "branch",
+		"path": "path"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestPagesError_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PagesError{}, "{}")
+
+	u := &PagesError{
+		Message: String("message"),
+	}
+
+	want := `{
+		"message": "message"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestPagesUpdate_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PagesUpdate{}, "{}")
+
+	u := &PagesUpdate{
+		CNAME:  String("cname"),
+		Source: String("src"),
+	}
+
+	want := `{
+		"cname": "cname",
+		"source": "src"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestPages_Marshal(t *testing.T) {
+	testJSONMarshal(t, &Pages{}, "{}")
+
+	u := &Pages{
+		URL:       String("url"),
+		Status:    String("status"),
+		CNAME:     String("cname"),
+		Custom404: Bool(false),
+		HTMLURL:   String("hurl"),
+		Source: &PagesSource{
+			Branch: String("branch"),
+			Path:   String("path"),
+		},
+	}
+
+	want := `{
+		"url": "url",
+		"status": "status",
+		"cname": "cname",
+		"custom_404": false,
+		"html_url": "hurl",
+		"source": {
+			"branch": "branch",
+			"path": "path"
+		}
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestPagesBuild_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PagesBuild{}, "{}")
+
+	u := &PagesBuild{
+		URL:    String("url"),
+		Status: String("status"),
+		Error: &PagesError{
+			Message: String("message"),
+		},
+		Pusher:    &User{ID: Int64(1)},
+		Commit:    String("commit"),
+		Duration:  Int(1),
+		CreatedAt: &Timestamp{referenceTime},
+		UpdatedAt: &Timestamp{referenceTime},
+	}
+
+	want := `{
+		"url": "url",
+		"status": "status",
+		"error": {
+			"message": "message"
+		},
+		"pusher": {
+			"id": 1
+		},
+		"commit": "commit",
+		"duration": 1,
+		"created_at": ` + referenceTimeStr + `,
+		"updated_at": ` + referenceTimeStr + `
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestCreatePagesRequest_Marshal(t *testing.T) {
+	testJSONMarshal(t, &createPagesRequest{}, "{}")
+
+	u := &createPagesRequest{
+		Source: &PagesSource{
+			Branch: String("branch"),
+			Path:   String("path"),
+		},
+	}
+
+	want := `{
+		"source": {
+			"branch": "branch",
+			"path": "path"
+		}
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_prereceive_hooks_test.go b/github/repos_prereceive_hooks_test.go
index 90178bd5..303e0e12 100644
--- a/github/repos_prereceive_hooks_test.go
+++ b/github/repos_prereceive_hooks_test.go
@@ -194,3 +194,23 @@ func TestRepositoriesService_DeletePreReceiveHook_invalidOwner(t *testing.T) {
 	_, err := client.Repositories.DeletePreReceiveHook(ctx, "%", "%", 1)
 	testURLParseError(t, err)
 }
+
+func TestPreReceiveHook_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PreReceiveHook{}, "{}")
+
+	u := &PreReceiveHook{
+		ID:          Int64(1),
+		Name:        String("name"),
+		Enforcement: String("e"),
+		ConfigURL:   String("curl"),
+	}
+
+	want := `{
+		"id": 1,
+		"name": "name",
+		"enforcement": "e",
+		"configuration_url": "curl"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_releases_test.go b/github/repos_releases_test.go
index 9f1fbe8d..69041c0c 100644
--- a/github/repos_releases_test.go
+++ b/github/repos_releases_test.go
@@ -653,3 +653,125 @@ func TestRepositoriesService_UploadReleaseAsset(t *testing.T) {
 		})
 	}
 }
+
+func TestRepositoryReleaseRequest_Marshal(t *testing.T) {
+	testJSONMarshal(t, &repositoryReleaseRequest{}, "{}")
+
+	u := &repositoryReleaseRequest{
+		TagName:                String("tn"),
+		TargetCommitish:        String("tc"),
+		Name:                   String("name"),
+		Body:                   String("body"),
+		Draft:                  Bool(false),
+		Prerelease:             Bool(false),
+		DiscussionCategoryName: String("dcn"),
+	}
+
+	want := `{
+		"tag_name": "tn",
+		"target_commitish": "tc",
+		"name": "name",
+		"body": "body",
+		"draft": false,
+		"prerelease": false,
+		"discussion_category_name": "dcn"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestReleaseAsset_Marshal(t *testing.T) {
+	testJSONMarshal(t, &ReleaseAsset{}, "{}")
+
+	u := &ReleaseAsset{
+		ID:                 Int64(1),
+		URL:                String("url"),
+		Name:               String("name"),
+		Label:              String("label"),
+		State:              String("state"),
+		ContentType:        String("ct"),
+		Size:               Int(1),
+		DownloadCount:      Int(1),
+		CreatedAt:          &Timestamp{referenceTime},
+		UpdatedAt:          &Timestamp{referenceTime},
+		BrowserDownloadURL: String("bdu"),
+		Uploader:           &User{ID: Int64(1)},
+		NodeID:             String("nid"),
+	}
+
+	want := `{
+		"id": 1,
+		"url": "url",
+		"name": "name",
+		"label": "label",
+		"state": "state",
+		"content_type": "ct",
+		"size": 1,
+		"download_count": 1,
+		"created_at": ` + referenceTimeStr + `,
+		"updated_at": ` + referenceTimeStr + `,
+		"browser_download_url": "bdu",
+		"uploader": {
+			"id": 1
+		},
+		"node_id": "nid"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestRepositoryRelease_Marshal(t *testing.T) {
+	testJSONMarshal(t, &RepositoryRelease{}, "{}")
+
+	u := &RepositoryRelease{
+		TagName:                String("tn"),
+		TargetCommitish:        String("tc"),
+		Name:                   String("name"),
+		Body:                   String("body"),
+		Draft:                  Bool(false),
+		Prerelease:             Bool(false),
+		DiscussionCategoryName: String("dcn"),
+		ID:                     Int64(1),
+		CreatedAt:              &Timestamp{referenceTime},
+		PublishedAt:            &Timestamp{referenceTime},
+		URL:                    String("url"),
+		HTMLURL:                String("hurl"),
+		AssetsURL:              String("aurl"),
+		Assets:                 []*ReleaseAsset{{ID: Int64(1)}},
+		UploadURL:              String("uurl"),
+		ZipballURL:             String("zurl"),
+		TarballURL:             String("turl"),
+		Author:                 &User{ID: Int64(1)},
+		NodeID:                 String("nid"),
+	}
+
+	want := `{
+		"tag_name": "tn",
+		"target_commitish": "tc",
+		"name": "name",
+		"body": "body",
+		"draft": false,
+		"prerelease": false,
+		"discussion_category_name": "dcn",
+		"id": 1,
+		"created_at": ` + referenceTimeStr + `,
+		"published_at": ` + referenceTimeStr + `,
+		"url": "url",
+		"html_url": "hurl",
+		"assets_url": "aurl",
+		"assets": [
+			{
+				"id": 1
+			}
+		],
+		"upload_url": "uurl",
+		"zipball_url": "zurl",
+		"tarball_url": "turl",
+		"author": {
+			"id": 1
+		},
+		"node_id": "nid"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_stats_test.go b/github/repos_stats_test.go
index efb36674..1babaca6 100644
--- a/github/repos_stats_test.go
+++ b/github/repos_stats_test.go
@@ -327,3 +327,93 @@ func TestRepositoriesService_AcceptedError(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestRepositoryParticipation_Marshal(t *testing.T) {
+	testJSONMarshal(t, &RepositoryParticipation{}, "{}")
+
+	u := &RepositoryParticipation{
+		All:   []int{1},
+		Owner: []int{1},
+	}
+
+	want := `{
+		"all": [1],
+		"owner": [1]
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestWeeklyCommitActivity_Marshal(t *testing.T) {
+	testJSONMarshal(t, &WeeklyCommitActivity{}, "{}")
+
+	u := &WeeklyCommitActivity{
+		Days:  []int{1},
+		Total: Int(1),
+		Week:  &Timestamp{referenceTime},
+	}
+
+	want := `{
+		"days": [
+			1
+		],
+		"total": 1,
+		"week": ` + referenceTimeStr + `
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestWeeklyStats_Marshal(t *testing.T) {
+	testJSONMarshal(t, &WeeklyStats{}, "{}")
+
+	u := &WeeklyStats{
+		Week:      &Timestamp{referenceTime},
+		Additions: Int(1),
+		Deletions: Int(1),
+		Commits:   Int(1),
+	}
+
+	want := `{
+		"w": ` + referenceTimeStr + `,
+		"a": 1,
+		"d": 1,
+		"c": 1
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestContributorStats_Marshal(t *testing.T) {
+	testJSONMarshal(t, &ContributorStats{}, "{}")
+
+	u := &ContributorStats{
+		Author: &Contributor{ID: Int64(1)},
+		Total:  Int(1),
+		Weeks: []*WeeklyStats{
+			{
+				Week:      &Timestamp{referenceTime},
+				Additions: Int(1),
+				Deletions: Int(1),
+				Commits:   Int(1),
+			},
+		},
+	}
+
+	want := `{
+		"author": {
+			"id": 1
+		},
+		"total": 1,
+		"weeks": [
+			{
+				"w": ` + referenceTimeStr + `,
+				"a": 1,
+				"d": 1,
+				"c": 1
+			}
+		]
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_statuses_test.go b/github/repos_statuses_test.go
index 3fa2ca7c..62fe3336 100644
--- a/github/repos_statuses_test.go
+++ b/github/repos_statuses_test.go
@@ -149,3 +149,95 @@ func TestRepositoriesService_GetCombinedStatus(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestRepoStatus_Marshal(t *testing.T) {
+	testJSONMarshal(t, &RepoStatus{}, "{}")
+
+	u := &RepoStatus{
+		ID:          Int64(1),
+		NodeID:      String("nid"),
+		URL:         String("url"),
+		State:       String("state"),
+		TargetURL:   String("turl"),
+		Description: String("desc"),
+		Context:     String("ctx"),
+		AvatarURL:   String("aurl"),
+		Creator:     &User{ID: Int64(1)},
+		CreatedAt:   &referenceTime,
+		UpdatedAt:   &referenceTime,
+	}
+
+	want := `{
+		"id": 1,
+		"node_id": "nid",
+		"url": "url",
+		"state": "state",
+		"target_url": "turl",
+		"description": "desc",
+		"context": "ctx",
+		"avatar_url": "aurl",
+		"creator": {
+			"id": 1
+		},
+		"created_at": ` + referenceTimeStr + `,
+		"updated_at": ` + referenceTimeStr + `
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestCombinedStatus_Marshal(t *testing.T) {
+	testJSONMarshal(t, &CombinedStatus{}, "{}")
+
+	u := &CombinedStatus{
+		State:      String("state"),
+		Name:       String("name"),
+		SHA:        String("sha"),
+		TotalCount: Int(1),
+		Statuses: []*RepoStatus{
+			{
+				ID:          Int64(1),
+				NodeID:      String("nid"),
+				URL:         String("url"),
+				State:       String("state"),
+				TargetURL:   String("turl"),
+				Description: String("desc"),
+				Context:     String("ctx"),
+				AvatarURL:   String("aurl"),
+				Creator:     &User{ID: Int64(1)},
+				CreatedAt:   &referenceTime,
+				UpdatedAt:   &referenceTime,
+			},
+		},
+		CommitURL:     String("curl"),
+		RepositoryURL: String("rurl"),
+	}
+
+	want := `{
+		"state": "state",
+		"name": "name",
+		"sha": "sha",
+		"total_count": 1,
+		"statuses": [
+			{
+				"id": 1,
+				"node_id": "nid",
+				"url": "url",
+				"state": "state",
+				"target_url": "turl",
+				"description": "desc",
+				"context": "ctx",
+				"avatar_url": "aurl",
+				"creator": {
+					"id": 1
+				},
+				"created_at": ` + referenceTimeStr + `,
+				"updated_at": ` + referenceTimeStr + `
+			}
+		],
+		"commit_url": "curl",
+		"repository_url": "rurl"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/scim_test.go b/github/scim_test.go
new file mode 100644
index 00000000..3338a28a
--- /dev/null
+++ b/github/scim_test.go
@@ -0,0 +1,194 @@
+// 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 (
+	"context"
+	"net/http"
+	"testing"
+)
+
+func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/scim/v2/organizations/o/Users", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ctx := context.Background()
+	opts := &ListSCIMProvisionedIdentitiesOptions{}
+	_, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts)
+	if err != nil {
+		t.Errorf("SCIM.ListSCIMProvisionedIdentities returned error: %v", err)
+	}
+
+	const methodName = "ListSCIMProvisionedIdentities"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.SCIM.ListSCIMProvisionedIdentities(ctx, "\n", opts)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts)
+	})
+}
+
+func TestSCIMService_ProvisionAndInviteSCIMUser(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/scim/v2/organizations/o/Users", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ctx := context.Background()
+	opts := &SCIMUserAttributes{
+		UserName: "userName",
+		Name: SCIMUserName{
+			GivenName:  "givenName",
+			FamilyName: "familyName",
+		},
+		Emails: []*SCIMUserEmail{
+			{
+				Value: "octocat@github.com",
+			},
+		},
+	}
+	_, err := client.SCIM.ProvisionAndInviteSCIMUser(ctx, "o", opts)
+	if err != nil {
+		t.Errorf("SCIM.ListSCIMProvisionedIdentities returned error: %v", err)
+	}
+
+	const methodName = "ProvisionAndInviteSCIMUser"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.SCIM.ProvisionAndInviteSCIMUser(ctx, "\n", opts)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.SCIM.ProvisionAndInviteSCIMUser(ctx, "o", opts)
+	})
+}
+
+func TestSCIMService_GetSCIMProvisioningInfoForUser(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ctx := context.Background()
+	_, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123")
+	if err != nil {
+		t.Errorf("SCIM.GetSCIMProvisioningInfoForUser returned error: %v", err)
+	}
+
+	const methodName = "GetSCIMProvisioningInfoForUser"
+	testBadOptions(t, methodName, func() error {
+		_, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "\n", "123")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123")
+	})
+}
+
+func TestSCIMService_UpdateProvisionedOrgMembership(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		w.WriteHeader(http.StatusOK)
+	})
+
+	ctx := context.Background()
+	opts := &SCIMUserAttributes{
+		UserName: "userName",
+		Name: SCIMUserName{
+			GivenName:  "givenName",
+			FamilyName: "familyName",
+		},
+		Emails: []*SCIMUserEmail{
+			{
+				Value: "octocat@github.com",
+			},
+		},
+	}
+	_, err := client.SCIM.UpdateProvisionedOrgMembership(ctx, "o", "123", opts)
+	if err != nil {
+		t.Errorf("SCIM.UpdateProvisionedOrgMembership returned error: %v", err)
+	}
+
+	const methodName = "UpdateProvisionedOrgMembership"
+	testBadOptions(t, methodName, func() error {
+		_, err := client.SCIM.UpdateProvisionedOrgMembership(ctx, "\n", "123", opts)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.SCIM.UpdateProvisionedOrgMembership(ctx, "o", "123", opts)
+	})
+}
+
+func TestSCIMService_UpdateAttributeForSCIMUser(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PATCH")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	opts := &UpdateAttributeForSCIMUserOptions{}
+	_, err := client.SCIM.UpdateAttributeForSCIMUser(ctx, "o", "123", opts)
+	if err != nil {
+		t.Errorf("SCIM.UpdateAttributeForSCIMUser returned error: %v", err)
+	}
+
+	const methodName = "UpdateAttributeForSCIMUser"
+	testBadOptions(t, methodName, func() error {
+		_, err := client.SCIM.UpdateAttributeForSCIMUser(ctx, "\n", "123", opts)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.SCIM.UpdateAttributeForSCIMUser(ctx, "o", "123", opts)
+	})
+}
+
+func TestSCIMService_DeleteSCIMUserFromOrg(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	_, err := client.SCIM.DeleteSCIMUserFromOrg(ctx, "o", "123")
+	if err != nil {
+		t.Errorf("SCIM.DeleteSCIMUserFromOrg returned error: %v", err)
+	}
+
+	const methodName = "DeleteSCIMUserFromOrg"
+	testBadOptions(t, methodName, func() error {
+		_, err := client.SCIM.DeleteSCIMUserFromOrg(ctx, "\n", "")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.SCIM.DeleteSCIMUserFromOrg(ctx, "o", "123")
+	})
+}