Repository: google/go-github
Pull request #3291 (2024-09-24 16:20:08)

refactor: Refactor org_custom_roles.go into multiple files
Continuation of https://github.com/google/go-github/pull/3281#issuecomment-2365259516

The `/organizations/ORGANIZATION_ID/custom_roles` endpoint has been deprecated in favor of `/orgs/ORG/custom-repository-roles`. So the ``orgs_custom_roles.go`` file should be refactored. The file also deals with Organization roles, which is a separate kind of resource to the Organization Repository Roles. For this reason this PR aims to separate the file into two:

* ``orgs_custom_repository_roles.go`` - [API docs](https://docs.github.com/en/enterprise-cloud@latest/rest/orgs/custom-roles?apiVersion=2022-11-28) - `/orgs/ORG/custom-repository-roles`
* `orgs_organization_roles.go` - [API docs](https://docs.github.com/en/rest/orgs/organization-roles?apiVersion=2022-11-28) - `/orgs/ORG/organization-roles`



Patch:
diff --git a/github/actions_workflow_runs.go b/github/actions_workflow_runs.go
index d55c01dc..122ea1d0 100644
--- a/github/actions_workflow_runs.go
+++ b/github/actions_workflow_runs.go
@@ -112,6 +112,31 @@ type ReferencedWorkflow struct {
 	Ref  *string `json:"ref,omitempty"`
 }
 
+// PendingDeployment represents the pending_deployments response.
+type PendingDeployment struct {
+	Environment           *PendingDeploymentEnvironment `json:"environment,omitempty"`
+	WaitTimer             *int64                        `json:"wait_timer,omitempty"`
+	WaitTimerStartedAt    *Timestamp                    `json:"wait_timer_started_at,omitempty"`
+	CurrentUserCanApprove *bool                         `json:"current_user_can_approve,omitempty"`
+	Reviewers             []*RequiredReviewer           `json:"reviewers,omitempty"`
+}
+
+// PendingDeploymentEnvironment represents pending deployment environment properties.
+type PendingDeploymentEnvironment struct {
+	ID      *int64  `json:"id,omitempty"`
+	NodeID  *string `json:"node_id,omitempty"`
+	Name    *string `json:"name,omitempty"`
+	URL     *string `json:"url,omitempty"`
+	HTMLURL *string `json:"html_url,omitempty"`
+}
+
+// ReviewCustomDeploymentProtectionRuleRequest specifies the parameters to ReviewCustomDeploymentProtectionRule.
+type ReviewCustomDeploymentProtectionRuleRequest struct {
+	EnvironmentName string `json:"environment_name"`
+	State           string `json:"state"`
+	Comment         string `json:"comment"`
+}
+
 func (s *ActionsService) listWorkflowRuns(ctx context.Context, endpoint string, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
 	u, err := addOptions(endpoint, opts)
 	if err != nil {
@@ -388,6 +413,28 @@ func (s *ActionsService) GetWorkflowRunUsageByID(ctx context.Context, owner, rep
 	return workflowRunUsage, resp, nil
 }
 
+// GetPendingDeployments get all deployment environments for a workflow run that are waiting for protection rules to pass.
+//
+// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#get-pending-deployments-for-a-workflow-run
+//
+//meta:operation GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments
+func (s *ActionsService) GetPendingDeployments(ctx context.Context, owner, repo string, runID int64) ([]*PendingDeployment, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/pending_deployments", owner, repo, runID)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var deployments []*PendingDeployment
+	resp, err := s.client.Do(ctx, req, &deployments)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return deployments, resp, nil
+}
+
 // PendingDeployments approve or reject pending deployments that are waiting on approval by a required reviewer.
 //
 // GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#review-pending-deployments-for-a-workflow-run
@@ -409,3 +456,20 @@ func (s *ActionsService) PendingDeployments(ctx context.Context, owner, repo str
 
 	return deployments, resp, nil
 }
+
+// ReviewCustomDeploymentProtectionRule approves or rejects custom deployment protection rules provided by a GitHub App for a workflow run.
+//
+// GitHub API docs: https://docs.github.com/rest/actions/workflow-runs#review-custom-deployment-protection-rules-for-a-workflow-run
+//
+//meta:operation POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule
+func (s *ActionsService) ReviewCustomDeploymentProtectionRule(ctx context.Context, owner, repo string, runID int64, request *ReviewCustomDeploymentProtectionRuleRequest) (*Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/deployment_protection_rule", owner, repo, runID)
+
+	req, err := s.client.NewRequest("POST", u, request)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, nil)
+	return resp, err
+}
diff --git a/github/event_types.go b/github/event_types.go
index 41d71f8d..fbe56b20 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -225,6 +225,46 @@ type DeploymentProtectionRuleEvent struct {
 	Installation          *Installation  `json:"installation,omitempty"`
 }
 
+// DeploymentReviewEvent represents a deployment review event.
+// The Webhook event name is "deployment_review".
+//
+// GitHub API docs: https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads?#deployment_review
+type DeploymentReviewEvent struct {
+	// The action performed. Possible values are: "requested", "approved", or "rejected".
+	Action *string `json:"action,omitempty"`
+
+	// The following will be populated only if requested.
+	Requester   *User   `json:"requester,omitempty"`
+	Environment *string `json:"environment,omitempty"`
+
+	// The following will be populated only if approved or rejected.
+	Approver        *User             `json:"approver,omitempty"`
+	Comment         *string           `json:"comment,omitempty"`
+	WorkflowJobRuns []*WorkflowJobRun `json:"workflow_job_runs,omitempty"`
+
+	Enterprise     *Enterprise         `json:"enterprise,omitempty"`
+	Installation   *Installation       `json:"installation,omitempty"`
+	Organization   *Organization       `json:"organization,omitempty"`
+	Repo           *Repository         `json:"repository,omitempty"`
+	Reviewers      []*RequiredReviewer `json:"reviewers,omitempty"`
+	Sender         *User               `json:"sender,omitempty"`
+	Since          *string             `json:"since,omitempty"`
+	WorkflowJobRun *WorkflowJobRun     `json:"workflow_job_run,omitempty"`
+	WorkflowRun    *WorkflowRun        `json:"workflow_run,omitempty"`
+}
+
+// WorkflowJobRun represents a workflow_job_run in a GitHub DeploymentReviewEvent.
+type WorkflowJobRun struct {
+	Conclusion  *string    `json:"conclusion,omitempty"`
+	CreatedAt   *Timestamp `json:"created_at,omitempty"`
+	Environment *string    `json:"environment,omitempty"`
+	HTMLURL     *string    `json:"html_url,omitempty"`
+	ID          *int64     `json:"id,omitempty"`
+	Name        *string    `json:"name,omitempty"`
+	Status      *string    `json:"status,omitempty"`
+	UpdatedAt   *Timestamp `json:"updated_at,omitempty"`
+}
+
 // DeploymentStatusEvent represents a deployment status.
 // The Webhook event name is "deployment_status".
 //
diff --git a/github/github-accessors.go b/github/github-accessors.go
index a025f781..f8e303c4 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -5918,6 +5918,110 @@ func (d *DeploymentRequest) GetTransientEnvironment() bool {
 	return *d.TransientEnvironment
 }
 
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (d *DeploymentReviewEvent) GetAction() string {
+	if d == nil || d.Action == nil {
+		return ""
+	}
+	return *d.Action
+}
+
+// GetApprover returns the Approver field.
+func (d *DeploymentReviewEvent) GetApprover() *User {
+	if d == nil {
+		return nil
+	}
+	return d.Approver
+}
+
+// GetComment returns the Comment field if it's non-nil, zero value otherwise.
+func (d *DeploymentReviewEvent) GetComment() string {
+	if d == nil || d.Comment == nil {
+		return ""
+	}
+	return *d.Comment
+}
+
+// GetEnterprise returns the Enterprise field.
+func (d *DeploymentReviewEvent) GetEnterprise() *Enterprise {
+	if d == nil {
+		return nil
+	}
+	return d.Enterprise
+}
+
+// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise.
+func (d *DeploymentReviewEvent) GetEnvironment() string {
+	if d == nil || d.Environment == nil {
+		return ""
+	}
+	return *d.Environment
+}
+
+// GetInstallation returns the Installation field.
+func (d *DeploymentReviewEvent) GetInstallation() *Installation {
+	if d == nil {
+		return nil
+	}
+	return d.Installation
+}
+
+// GetOrganization returns the Organization field.
+func (d *DeploymentReviewEvent) GetOrganization() *Organization {
+	if d == nil {
+		return nil
+	}
+	return d.Organization
+}
+
+// GetRepo returns the Repo field.
+func (d *DeploymentReviewEvent) GetRepo() *Repository {
+	if d == nil {
+		return nil
+	}
+	return d.Repo
+}
+
+// GetRequester returns the Requester field.
+func (d *DeploymentReviewEvent) GetRequester() *User {
+	if d == nil {
+		return nil
+	}
+	return d.Requester
+}
+
+// GetSender returns the Sender field.
+func (d *DeploymentReviewEvent) GetSender() *User {
+	if d == nil {
+		return nil
+	}
+	return d.Sender
+}
+
+// GetSince returns the Since field if it's non-nil, zero value otherwise.
+func (d *DeploymentReviewEvent) GetSince() string {
+	if d == nil || d.Since == nil {
+		return ""
+	}
+	return *d.Since
+}
+
+// GetWorkflowJobRun returns the WorkflowJobRun field.
+func (d *DeploymentReviewEvent) GetWorkflowJobRun() *WorkflowJobRun {
+	if d == nil {
+		return nil
+	}
+	return d.WorkflowJobRun
+}
+
+// GetWorkflowRun returns the WorkflowRun field.
+func (d *DeploymentReviewEvent) GetWorkflowRun() *WorkflowRun {
+	if d == nil {
+		return nil
+	}
+	return d.WorkflowRun
+}
+
 // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
 func (d *DeploymentStatus) GetCreatedAt() Timestamp {
 	if d == nil || d.CreatedAt == nil {
@@ -14422,6 +14526,78 @@ func (p *PagesUpdate) GetSource() *PagesSource {
 	return p.Source
 }
 
+// GetCurrentUserCanApprove returns the CurrentUserCanApprove field if it's non-nil, zero value otherwise.
+func (p *PendingDeployment) GetCurrentUserCanApprove() bool {
+	if p == nil || p.CurrentUserCanApprove == nil {
+		return false
+	}
+	return *p.CurrentUserCanApprove
+}
+
+// GetEnvironment returns the Environment field.
+func (p *PendingDeployment) GetEnvironment() *PendingDeploymentEnvironment {
+	if p == nil {
+		return nil
+	}
+	return p.Environment
+}
+
+// GetWaitTimer returns the WaitTimer field if it's non-nil, zero value otherwise.
+func (p *PendingDeployment) GetWaitTimer() int64 {
+	if p == nil || p.WaitTimer == nil {
+		return 0
+	}
+	return *p.WaitTimer
+}
+
+// GetWaitTimerStartedAt returns the WaitTimerStartedAt field if it's non-nil, zero value otherwise.
+func (p *PendingDeployment) GetWaitTimerStartedAt() Timestamp {
+	if p == nil || p.WaitTimerStartedAt == nil {
+		return Timestamp{}
+	}
+	return *p.WaitTimerStartedAt
+}
+
+// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
+func (p *PendingDeploymentEnvironment) GetHTMLURL() string {
+	if p == nil || p.HTMLURL == nil {
+		return ""
+	}
+	return *p.HTMLURL
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (p *PendingDeploymentEnvironment) GetID() int64 {
+	if p == nil || p.ID == nil {
+		return 0
+	}
+	return *p.ID
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (p *PendingDeploymentEnvironment) GetName() string {
+	if p == nil || p.Name == nil {
+		return ""
+	}
+	return *p.Name
+}
+
+// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
+func (p *PendingDeploymentEnvironment) GetNodeID() string {
+	if p == nil || p.NodeID == nil {
+		return ""
+	}
+	return *p.NodeID
+}
+
+// GetURL returns the URL field if it's non-nil, zero value otherwise.
+func (p *PendingDeploymentEnvironment) GetURL() string {
+	if p == nil || p.URL == nil {
+		return ""
+	}
+	return *p.URL
+}
+
 // GetAccessGrantedAt returns the AccessGrantedAt field if it's non-nil, zero value otherwise.
 func (p *PersonalAccessToken) GetAccessGrantedAt() Timestamp {
 	if p == nil || p.AccessGrantedAt == nil {
@@ -25438,6 +25614,70 @@ func (w *WorkflowJobEvent) GetWorkflowJob() *WorkflowJob {
 	return w.WorkflowJob
 }
 
+// GetConclusion returns the Conclusion field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetConclusion() string {
+	if w == nil || w.Conclusion == nil {
+		return ""
+	}
+	return *w.Conclusion
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetCreatedAt() Timestamp {
+	if w == nil || w.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *w.CreatedAt
+}
+
+// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetEnvironment() string {
+	if w == nil || w.Environment == nil {
+		return ""
+	}
+	return *w.Environment
+}
+
+// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetHTMLURL() string {
+	if w == nil || w.HTMLURL == nil {
+		return ""
+	}
+	return *w.HTMLURL
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetID() int64 {
+	if w == nil || w.ID == nil {
+		return 0
+	}
+	return *w.ID
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetName() string {
+	if w == nil || w.Name == nil {
+		return ""
+	}
+	return *w.Name
+}
+
+// GetStatus returns the Status field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetStatus() string {
+	if w == nil || w.Status == nil {
+		return ""
+	}
+	return *w.Status
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (w *WorkflowJobRun) GetUpdatedAt() Timestamp {
+	if w == nil || w.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *w.UpdatedAt
+}
+
 // GetActor returns the Actor field.
 func (w *WorkflowRun) GetActor() *User {
 	if w == nil {
diff --git a/github/messages.go b/github/messages.go
index 0385d398..30c4fca9 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -57,6 +57,7 @@ var (
 		"dependabot_alert":               &DependabotAlertEvent{},
 		"deploy_key":                     &DeployKeyEvent{},
 		"deployment":                     &DeploymentEvent{},
+		"deployment_review":              &DeploymentReviewEvent{},
 		"deployment_status":              &DeploymentStatusEvent{},
 		"deployment_protection_rule":     &DeploymentProtectionRuleEvent{},
 		"discussion":                     &DiscussionEvent{},
diff --git a/github/orgs_custom_repository_roles.go b/github/orgs_custom_repository_roles.go
new file mode 100644
index 00000000..2295e1b8
--- /dev/null
+++ b/github/orgs_custom_repository_roles.go
@@ -0,0 +1,131 @@
+// Copyright 2024 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"
+)
+
+// OrganizationCustomRepoRoles represents custom repository roles available in specified organization.
+type OrganizationCustomRepoRoles struct {
+	TotalCount      *int               `json:"total_count,omitempty"`
+	CustomRepoRoles []*CustomRepoRoles `json:"custom_roles,omitempty"`
+}
+
+// CustomRepoRoles represents custom repository roles for an organization.
+// See https://docs.github.com/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization
+// for more information.
+type CustomRepoRoles struct {
+	ID          *int64        `json:"id,omitempty"`
+	Name        *string       `json:"name,omitempty"`
+	Description *string       `json:"description,omitempty"`
+	BaseRole    *string       `json:"base_role,omitempty"`
+	Permissions []string      `json:"permissions,omitempty"`
+	Org         *Organization `json:"organization,omitempty"`
+	CreatedAt   *Timestamp    `json:"created_at,omitempty"`
+	UpdatedAt   *Timestamp    `json:"updated_at,omitempty"`
+}
+
+// CreateOrUpdateCustomRepoRoleOptions represents options required to create or update a custom repository role.
+type CreateOrUpdateCustomRepoRoleOptions struct {
+	Name        *string  `json:"name,omitempty"`
+	Description *string  `json:"description,omitempty"`
+	BaseRole    *string  `json:"base_role,omitempty"`
+	Permissions []string `json:"permissions"`
+}
+
+// ListCustomRepoRoles lists the custom repository roles available in this organization.
+// In order to see custom repository roles in an organization, the authenticated user must be an organization owner.
+//
+// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#list-custom-repository-roles-in-an-organization
+//
+//meta:operation GET /orgs/{org}/custom-repository-roles
+func (s *OrganizationsService) ListCustomRepoRoles(ctx context.Context, org string) (*OrganizationCustomRepoRoles, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/custom-repository-roles", org)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	customRepoRoles := new(OrganizationCustomRepoRoles)
+	resp, err := s.client.Do(ctx, req, customRepoRoles)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return customRepoRoles, resp, nil
+}
+
+// CreateCustomRepoRole creates a custom repository role in this organization.
+// In order to create custom repository roles in an organization, the authenticated user must be an organization owner.
+//
+// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#create-a-custom-repository-role
+//
+//meta:operation POST /orgs/{org}/custom-repository-roles
+func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, opts *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/custom-repository-roles", org)
+
+	req, err := s.client.NewRequest("POST", u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	resultingRole := new(CustomRepoRoles)
+	resp, err := s.client.Do(ctx, req, resultingRole)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return resultingRole, resp, err
+}
+
+// UpdateCustomRepoRole updates a custom repository role in this organization.
+// In order to update custom repository roles in an organization, the authenticated user must be an organization owner.
+//
+// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#update-a-custom-repository-role
+//
+//meta:operation PATCH /orgs/{org}/custom-repository-roles/{role_id}
+func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org string, roleID int64, opts *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID)
+
+	req, err := s.client.NewRequest("PATCH", u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	resultingRole := new(CustomRepoRoles)
+	resp, err := s.client.Do(ctx, req, resultingRole)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return resultingRole, resp, err
+}
+
+// DeleteCustomRepoRole deletes an existing custom repository role in this organization.
+// In order to delete custom repository roles in an organization, the authenticated user must be an organization owner.
+//
+// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#delete-a-custom-repository-role
+//
+//meta:operation DELETE /orgs/{org}/custom-repository-roles/{role_id}
+func (s *OrganizationsService) DeleteCustomRepoRole(ctx context.Context, org string, roleID int64) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	resultingRole := new(CustomRepoRoles)
+	resp, err := s.client.Do(ctx, req, resultingRole)
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, nil
+}
diff --git a/github/orgs_members.go b/github/orgs_members.go
index f776766a..d818d7f9 100644
--- a/github/orgs_members.go
+++ b/github/orgs_members.go
@@ -151,6 +151,20 @@ func (s *OrganizationsService) RemoveMember(ctx context.Context, org, user strin
 	return s.client.Do(ctx, req, nil)
 }
 
+// CancelInvite cancels an organization invitation.
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/members#cancel-an-organization-invitation
+//
+//meta:operation DELETE /orgs/{org}/invitations/{invitation_id}
+func (s *OrganizationsService) CancelInvite(ctx context.Context, org string, invitationID int64) (*Response, error) {
+	u := fmt.Sprintf("orgs/%v/invitations/%v", org, invitationID)
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(ctx, req, nil)
+}
+
 // PublicizeMembership publicizes a user's membership in an organization. (A
 // user cannot publicize the membership for another user.)
 //
diff --git a/github/orgs_custom_roles.go b/github/orgs_organization_roles.go
similarity index 68%
rename from github/orgs_custom_roles.go
rename to github/orgs_organization_roles.go
index f48fc697..695c4dad 100644
--- a/github/orgs_custom_roles.go
+++ b/github/orgs_organization_roles.go
@@ -29,26 +29,6 @@ type CustomOrgRoles struct {
 	BaseRole    *string       `json:"base_role,omitempty"`
 }
 
-// OrganizationCustomRepoRoles represents custom repository roles available in specified organization.
-type OrganizationCustomRepoRoles struct {
-	TotalCount      *int               `json:"total_count,omitempty"`
-	CustomRepoRoles []*CustomRepoRoles `json:"custom_roles,omitempty"`
-}
-
-// CustomRepoRoles represents custom repository roles for an organization.
-// See https://docs.github.com/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization
-// for more information.
-type CustomRepoRoles struct {
-	ID          *int64        `json:"id,omitempty"`
-	Name        *string       `json:"name,omitempty"`
-	Description *string       `json:"description,omitempty"`
-	BaseRole    *string       `json:"base_role,omitempty"`
-	Permissions []string      `json:"permissions,omitempty"`
-	Org         *Organization `json:"organization,omitempty"`
-	CreatedAt   *Timestamp    `json:"created_at,omitempty"`
-	UpdatedAt   *Timestamp    `json:"updated_at,omitempty"`
-}
-
 // CreateOrUpdateOrgRoleOptions represents options required to create or update a custom organization role.
 type CreateOrUpdateOrgRoleOptions struct {
 	Name        *string  `json:"name,omitempty"`
@@ -57,14 +37,6 @@ type CreateOrUpdateOrgRoleOptions struct {
 	BaseRole    *string  `json:"base_role,omitempty"`
 }
 
-// CreateOrUpdateCustomRepoRoleOptions represents options required to create or update a custom repository role.
-type CreateOrUpdateCustomRepoRoleOptions struct {
-	Name        *string  `json:"name,omitempty"`
-	Description *string  `json:"description,omitempty"`
-	BaseRole    *string  `json:"base_role,omitempty"`
-	Permissions []string `json:"permissions"`
-}
-
 // ListRoles lists the custom roles available in this organization.
 // In order to see custom roles in an organization, the authenticated user must be an organization owner.
 //
@@ -268,98 +240,6 @@ func (s *OrganizationsService) RemoveOrgRoleFromUser(ctx context.Context, org, u
 	return resp, nil
 }
 
-// ListCustomRepoRoles lists the custom repository roles available in this organization.
-// In order to see custom repository roles in an organization, the authenticated user must be an organization owner.
-//
-// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#list-custom-repository-roles-in-an-organization
-//
-//meta:operation GET /orgs/{org}/custom-repository-roles
-func (s *OrganizationsService) ListCustomRepoRoles(ctx context.Context, org string) (*OrganizationCustomRepoRoles, *Response, error) {
-	u := fmt.Sprintf("orgs/%v/custom-repository-roles", org)
-
-	req, err := s.client.NewRequest("GET", u, nil)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	customRepoRoles := new(OrganizationCustomRepoRoles)
-	resp, err := s.client.Do(ctx, req, customRepoRoles)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return customRepoRoles, resp, nil
-}
-
-// CreateCustomRepoRole creates a custom repository role in this organization.
-// In order to create custom repository roles in an organization, the authenticated user must be an organization owner.
-//
-// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#create-a-custom-repository-role
-//
-//meta:operation POST /orgs/{org}/custom-repository-roles
-func (s *OrganizationsService) CreateCustomRepoRole(ctx context.Context, org string, opts *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) {
-	u := fmt.Sprintf("orgs/%v/custom-repository-roles", org)
-
-	req, err := s.client.NewRequest("POST", u, opts)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	resultingRole := new(CustomRepoRoles)
-	resp, err := s.client.Do(ctx, req, resultingRole)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return resultingRole, resp, err
-}
-
-// UpdateCustomRepoRole updates a custom repository role in this organization.
-// In order to update custom repository roles in an organization, the authenticated user must be an organization owner.
-//
-// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#update-a-custom-repository-role
-//
-//meta:operation PATCH /orgs/{org}/custom-repository-roles/{role_id}
-func (s *OrganizationsService) UpdateCustomRepoRole(ctx context.Context, org string, roleID int64, opts *CreateOrUpdateCustomRepoRoleOptions) (*CustomRepoRoles, *Response, error) {
-	u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID)
-
-	req, err := s.client.NewRequest("PATCH", u, opts)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	resultingRole := new(CustomRepoRoles)
-	resp, err := s.client.Do(ctx, req, resultingRole)
-	if err != nil {
-		return nil, resp, err
-	}
-
-	return resultingRole, resp, err
-}
-
-// DeleteCustomRepoRole deletes an existing custom repository role in this organization.
-// In order to delete custom repository roles in an organization, the authenticated user must be an organization owner.
-//
-// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/orgs/custom-roles#delete-a-custom-repository-role
-//
-//meta:operation DELETE /orgs/{org}/custom-repository-roles/{role_id}
-func (s *OrganizationsService) DeleteCustomRepoRole(ctx context.Context, org string, roleID int64) (*Response, error) {
-	u := fmt.Sprintf("orgs/%v/custom-repository-roles/%v", org, roleID)
-
-	req, err := s.client.NewRequest("DELETE", u, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	resultingRole := new(CustomRepoRoles)
-	resp, err := s.client.Do(ctx, req, resultingRole)
-	if err != nil {
-		return resp, err
-	}
-
-	return resp, nil
-}
-
 // ListTeamsAssignedToOrgRole returns all teams assigned to a specific organization role.
 // In order to list teams assigned to an organization role, the authenticated user must be an organization owner.
 //
diff --git a/github/strings.go b/github/strings.go
index 147c515e..f5e61aa3 100644
--- a/github/strings.go
+++ b/github/strings.go
@@ -27,7 +27,7 @@ func Stringify(message interface{}) string {
 
 func stringifyValue(w *bytes.Buffer, val reflect.Value) {
 	if val.Kind() == reflect.Ptr && val.IsNil() {
-		w.Write([]byte("<nil>"))
+		w.WriteString("<nil>")
 		return
 	}
 
@@ -37,20 +37,20 @@ func stringifyValue(w *bytes.Buffer, val reflect.Value) {
 	case reflect.String:
 		fmt.Fprintf(w, `"%s"`, v)
 	case reflect.Slice:
-		w.Write([]byte{'['})
+		w.WriteByte('[')
 		for i := 0; i < v.Len(); i++ {
 			if i > 0 {
-				w.Write([]byte{' '})
+				w.WriteByte(' ')
 			}
 
 			stringifyValue(w, v.Index(i))
 		}
 
-		w.Write([]byte{']'})
+		w.WriteByte(']')
 		return
 	case reflect.Struct:
 		if v.Type().Name() != "" {
-			w.Write([]byte(v.Type().String()))
+			w.WriteString(v.Type().String())
 		}
 
 		// special handling of Timestamp values
@@ -59,7 +59,7 @@ func stringifyValue(w *bytes.Buffer, val reflect.Value) {
 			return
 		}
 
-		w.Write([]byte{'{'})
+		w.WriteByte('{')
 
 		var sep bool
 		for i := 0; i < v.NumField(); i++ {
@@ -75,17 +75,17 @@ func stringifyValue(w *bytes.Buffer, val reflect.Value) {
 			}
 
 			if sep {
-				w.Write([]byte(", "))
+				w.WriteString(", ")
 			} else {
 				sep = true
 			}
 
-			w.Write([]byte(v.Type().Field(i).Name))
-			w.Write([]byte{':'})
+			w.WriteString(v.Type().Field(i).Name)
+			w.WriteByte(':')
 			stringifyValue(w, fv)
 		}
 
-		w.Write([]byte{'}'})
+		w.WriteByte('}')
 	default:
 		if v.CanInterface() {
 			fmt.Fprint(w, v.Interface())


Test patch:
diff --git a/github/actions_workflow_runs_test.go b/github/actions_workflow_runs_test.go
index 8e01aa82..48552a73 100644
--- a/github/actions_workflow_runs_test.go
+++ b/github/actions_workflow_runs_test.go
@@ -571,6 +571,112 @@ func TestActionService_DeleteWorkflowRunLogs(t *testing.T) {
 	})
 }
 
+func TestPendingDeployment_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PendingDeployment{}, "{}")
+
+	u := &PendingDeployment{
+		Environment: &PendingDeploymentEnvironment{
+			ID:      Int64(1),
+			NodeID:  String("nid"),
+			Name:    String("n"),
+			URL:     String("u"),
+			HTMLURL: String("hu"),
+		},
+		WaitTimer:             Int64(100),
+		WaitTimerStartedAt:    &Timestamp{referenceTime},
+		CurrentUserCanApprove: Bool(false),
+		Reviewers: []*RequiredReviewer{
+			{
+				Type: String("User"),
+				Reviewer: &User{
+					Login: String("l"),
+				},
+			},
+			{
+				Type: String("Team"),
+				Reviewer: &Team{
+					Name: String("n"),
+				},
+			},
+		},
+	}
+	want := `{
+		"environment": {
+			"id": 1,
+			"node_id": "nid",
+			"name": "n",
+			"url": "u",
+			"html_url": "hu"
+		},
+		"wait_timer": 100,
+		"wait_timer_started_at": ` + referenceTimeStr + `,
+		"current_user_can_approve": false,
+		"reviewers": [
+			{
+				"type": "User",
+				"reviewer": {
+					"login": "l"
+				}
+			},
+			{
+				"type": "Team",
+				"reviewer": {
+					"name": "n"
+				}
+			}
+		]
+	}`
+	testJSONMarshal(t, u, want)
+}
+
+func TestActionsService_ReviewCustomDeploymentProtectionRule(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/runs/9444496/deployment_protection_rule", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	request := ReviewCustomDeploymentProtectionRuleRequest{
+		EnvironmentName: "production",
+		State:           "approved",
+		Comment:         "Approve deployment",
+	}
+
+	ctx := context.Background()
+	if _, err := client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "o", "r", 9444496, &request); err != nil {
+		t.Errorf("ReviewCustomDeploymentProtectionRule returned error: %v", err)
+	}
+
+	const methodName = "ReviewCustomDeploymentProtectionRule"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "\n", "\n", 9444496, &request)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Actions.ReviewCustomDeploymentProtectionRule(ctx, "o", "r", 9444496, &request)
+	})
+}
+
+func TestReviewCustomDeploymentProtectionRuleRequest_Marshal(t *testing.T) {
+	testJSONMarshal(t, &ReviewCustomDeploymentProtectionRuleRequest{}, "{}")
+
+	r := &ReviewCustomDeploymentProtectionRuleRequest{
+		EnvironmentName: "e",
+		State:           "rejected",
+		Comment:         "c",
+	}
+	want := `{
+		"environment_name": "e",
+		"state": "rejected",
+		"comment": "c"
+	}`
+	testJSONMarshal(t, r, want)
+}
+
 func TestActionsService_GetWorkflowRunUsageByID(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
@@ -1298,3 +1404,121 @@ func TestActionService_PendingDeployments(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestActionService_GetPendingDeployments(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/actions/runs/399444496/pending_deployments", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `[
+			{
+				"environment": {
+					"id": 1,
+					"node_id": "nid",
+					"name": "n",
+					"url": "u",
+					"html_url": "hu"
+				},
+				"wait_timer": 0,
+				"wait_timer_started_at": `+referenceTimeStr+`,
+				"current_user_can_approve": false,
+				"reviewers": []
+			},
+			{
+				"environment": {
+					"id": 2,
+					"node_id": "nid",
+					"name": "n",
+					"url": "u",
+					"html_url": "hu"
+				},
+				"wait_timer": 13,
+				"wait_timer_started_at": `+referenceTimeStr+`,
+				"current_user_can_approve": true,
+				"reviewers": [
+					{
+						"type": "User",
+						"reviewer": {
+							"login": "l"
+						}
+					},
+					{
+						"type": "Team",
+						"reviewer": {
+							"name": "t",
+							"slug": "s"
+						}
+					}
+				]
+			}
+		]`)
+	})
+
+	ctx := context.Background()
+	deployments, _, err := client.Actions.GetPendingDeployments(ctx, "o", "r", 399444496)
+	if err != nil {
+		t.Errorf("Actions.GetPendingDeployments returned error: %v", err)
+	}
+
+	want := []*PendingDeployment{
+		{
+			Environment: &PendingDeploymentEnvironment{
+				ID:      Int64(1),
+				NodeID:  String("nid"),
+				Name:    String("n"),
+				URL:     String("u"),
+				HTMLURL: String("hu"),
+			},
+			WaitTimer:             Int64(0),
+			WaitTimerStartedAt:    &Timestamp{referenceTime},
+			CurrentUserCanApprove: Bool(false),
+			Reviewers:             []*RequiredReviewer{},
+		},
+		{
+			Environment: &PendingDeploymentEnvironment{
+				ID:      Int64(2),
+				NodeID:  String("nid"),
+				Name:    String("n"),
+				URL:     String("u"),
+				HTMLURL: String("hu"),
+			},
+			WaitTimer:             Int64(13),
+			WaitTimerStartedAt:    &Timestamp{referenceTime},
+			CurrentUserCanApprove: Bool(true),
+			Reviewers: []*RequiredReviewer{
+				{
+					Type: String("User"),
+					Reviewer: &User{
+						Login: String("l"),
+					},
+				},
+				{
+					Type: String("Team"),
+					Reviewer: &Team{
+						Name: String("t"),
+						Slug: String("s"),
+					},
+				},
+			},
+		},
+	}
+
+	if !cmp.Equal(deployments, want) {
+		t.Errorf("Actions.GetPendingDeployments returned %+v, want %+v", deployments, want)
+	}
+
+	const methodName = "GetPendingDeployments"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Actions.GetPendingDeployments(ctx, "\n", "\n", 399444496)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Actions.GetPendingDeployments(ctx, "o", "r", 399444496)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
diff --git a/github/event_types_test.go b/github/event_types_test.go
index e2a8a39f..6f5235ef 100644
--- a/github/event_types_test.go
+++ b/github/event_types_test.go
@@ -6391,6 +6391,604 @@ func TestDeploymentProtectionRuleEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, u, want)
 }
 
+func TestDeploymentReviewEvent_Marshal(t *testing.T) {
+	testJSONMarshal(t, &DeploymentReviewEvent{}, "{}")
+
+	u := &DeploymentReviewEvent{
+		Action:      String("a"),
+		Environment: String("e"),
+		Requester: &User{
+			AvatarURL:         String("a"),
+			Email:             String("e"),
+			EventsURL:         String("e"),
+			FollowersURL:      String("f"),
+			FollowingURL:      String("f"),
+			GistsURL:          String("g"),
+			GravatarID:        String("g"),
+			HTMLURL:           String("h"),
+			ID:                Int64(1),
+			Login:             String("l"),
+			Name:              String("n"),
+			NodeID:            String("n"),
+			OrganizationsURL:  String("o"),
+			ReceivedEventsURL: String("r"),
+			ReposURL:          String("r"),
+			SiteAdmin:         Bool(false),
+			StarredURL:        String("s"),
+			SubscriptionsURL:  String("s"),
+			Type:              String("User"),
+			URL:               String("u"),
+		},
+		Reviewers: []*RequiredReviewer{
+			{
+				Type: String("User"),
+				Reviewer: &User{
+					AvatarURL:         String("a"),
+					Email:             String("e"),
+					EventsURL:         String("e"),
+					FollowersURL:      String("f"),
+					FollowingURL:      String("f"),
+					GistsURL:          String("g"),
+					GravatarID:        String("g"),
+					HTMLURL:           String("h"),
+					ID:                Int64(1),
+					Login:             String("l"),
+					Name:              String("n"),
+					NodeID:            String("n"),
+					OrganizationsURL:  String("o"),
+					ReceivedEventsURL: String("r"),
+					ReposURL:          String("r"),
+					SiteAdmin:         Bool(false),
+					StarredURL:        String("s"),
+					SubscriptionsURL:  String("s"),
+					Type:              String("User"),
+					URL:               String("u"),
+				},
+			},
+			{
+				Type: String("Team"),
+				Reviewer: &Team{
+					ID:   Int64(1),
+					Name: String("n"),
+					Slug: String("s"),
+				},
+			},
+		},
+		Enterprise: &Enterprise{
+			ID:          Int(1),
+			Slug:        String("s"),
+			Name:        String("n"),
+			NodeID:      String("nid"),
+			AvatarURL:   String("au"),
+			Description: String("d"),
+			WebsiteURL:  String("wu"),
+			HTMLURL:     String("hu"),
+			CreatedAt:   &Timestamp{referenceTime},
+			UpdatedAt:   &Timestamp{referenceTime},
+		},
+		Installation: &Installation{
+			ID:       Int64(1),
+			NodeID:   String("nid"),
+			AppID:    Int64(1),
+			AppSlug:  String("as"),
+			TargetID: Int64(1),
+			Account: &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},
+			},
+			AccessTokensURL:     String("atu"),
+			RepositoriesURL:     String("ru"),
+			HTMLURL:             String("hu"),
+			TargetType:          String("tt"),
+			SingleFileName:      String("sfn"),
+			RepositorySelection: String("rs"),
+			Events:              []string{"e"},
+			SingleFilePaths:     []string{"s"},
+			Permissions: &InstallationPermissions{
+				Actions:                       String("a"),
+				Administration:                String("ad"),
+				Checks:                        String("c"),
+				Contents:                      String("co"),
+				ContentReferences:             String("cr"),
+				Deployments:                   String("d"),
+				Environments:                  String("e"),
+				Issues:                        String("i"),
+				Metadata:                      String("md"),
+				Members:                       String("m"),
+				OrganizationAdministration:    String("oa"),
+				OrganizationHooks:             String("oh"),
+				OrganizationPlan:              String("op"),
+				OrganizationPreReceiveHooks:   String("opr"),
+				OrganizationProjects:          String("op"),
+				OrganizationSecrets:           String("os"),
+				OrganizationSelfHostedRunners: String("osh"),
+				OrganizationUserBlocking:      String("oub"),
+				Packages:                      String("pkg"),
+				Pages:                         String("pg"),
+				PullRequests:                  String("pr"),
+				RepositoryHooks:               String("rh"),
+				RepositoryProjects:            String("rp"),
+				RepositoryPreReceiveHooks:     String("rprh"),
+				Secrets:                       String("s"),
+				SecretScanningAlerts:          String("ssa"),
+				SecurityEvents:                String("se"),
+				SingleFile:                    String("sf"),
+				Statuses:                      String("s"),
+				TeamDiscussions:               String("td"),
+				VulnerabilityAlerts:           String("va"),
+				Workflows:                     String("w"),
+			},
+			CreatedAt:              &Timestamp{referenceTime},
+			UpdatedAt:              &Timestamp{referenceTime},
+			HasMultipleSingleFiles: Bool(false),
+			SuspendedBy: &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},
+			},
+			SuspendedAt: &Timestamp{referenceTime},
+		},
+		Organization: &Organization{
+			BillingEmail:                         String("be"),
+			Blog:                                 String("b"),
+			Company:                              String("c"),
+			Email:                                String("e"),
+			TwitterUsername:                      String("tu"),
+			Location:                             String("loc"),
+			Name:                                 String("n"),
+			Description:                          String("d"),
+			IsVerified:                           Bool(true),
+			HasOrganizationProjects:              Bool(true),
+			HasRepositoryProjects:                Bool(true),
+			DefaultRepoPermission:                String("drp"),
+			MembersCanCreateRepos:                Bool(true),
+			MembersCanCreateInternalRepos:        Bool(true),
+			MembersCanCreatePrivateRepos:         Bool(true),
+			MembersCanCreatePublicRepos:          Bool(false),
+			MembersAllowedRepositoryCreationType: String("marct"),
+			MembersCanCreatePages:                Bool(true),
+			MembersCanCreatePublicPages:          Bool(false),
+			MembersCanCreatePrivatePages:         Bool(true),
+		},
+		Repo: &Repository{
+			ID:   Int64(1),
+			URL:  String("s"),
+			Name: String("n"),
+		},
+		Sender: &User{
+			Login:     String("l"),
+			ID:        Int64(1),
+			NodeID:    String("n"),
+			URL:       String("u"),
+			ReposURL:  String("r"),
+			EventsURL: String("e"),
+			AvatarURL: String("a"),
+		},
+		Since: String("s"),
+		WorkflowJobRun: &WorkflowJobRun{
+			ID:          Int64(1),
+			Conclusion:  String("c"),
+			Environment: String("e"),
+			HTMLURL:     String("h"),
+			Name:        String("n"),
+			Status:      String("s"),
+			CreatedAt:   &Timestamp{referenceTime},
+			UpdatedAt:   &Timestamp{referenceTime},
+		},
+		WorkflowRun: &WorkflowRun{
+			ID:         Int64(1),
+			Name:       String("n"),
+			NodeID:     String("nid"),
+			HeadBranch: String("hb"),
+			HeadSHA:    String("hs"),
+			RunNumber:  Int(1),
+			RunAttempt: Int(1),
+			Event:      String("e"),
+			Status:     String("s"),
+			Conclusion: String("c"),
+			WorkflowID: Int64(1),
+			URL:        String("u"),
+			HTMLURL:    String("h"),
+			PullRequests: []*PullRequest{
+				{
+					URL:    String("u"),
+					ID:     Int64(1),
+					Number: Int(1),
+					Head: &PullRequestBranch{
+						Ref: String("r"),
+						SHA: String("s"),
+						Repo: &Repository{
+							ID:   Int64(1),
+							URL:  String("s"),
+							Name: String("n"),
+						},
+					},
+					Base: &PullRequestBranch{
+						Ref: String("r"),
+						SHA: String("s"),
+						Repo: &Repository{
+							ID:   Int64(1),
+							URL:  String("u"),
+							Name: String("n"),
+						},
+					},
+				},
+			},
+			CreatedAt:          &Timestamp{referenceTime},
+			UpdatedAt:          &Timestamp{referenceTime},
+			RunStartedAt:       &Timestamp{referenceTime},
+			JobsURL:            String("j"),
+			LogsURL:            String("l"),
+			CheckSuiteURL:      String("c"),
+			ArtifactsURL:       String("a"),
+			CancelURL:          String("c"),
+			RerunURL:           String("r"),
+			PreviousAttemptURL: String("p"),
+			HeadCommit: &HeadCommit{
+				Message: String("m"),
+				Author: &CommitAuthor{
+					Name:  String("n"),
+					Email: String("e"),
+					Login: String("l"),
+				},
+				URL:       String("u"),
+				Distinct:  Bool(false),
+				SHA:       String("s"),
+				ID:        String("i"),
+				TreeID:    String("tid"),
+				Timestamp: &Timestamp{referenceTime},
+				Committer: &CommitAuthor{
+					Name:  String("n"),
+					Email: String("e"),
+					Login: String("l"),
+				},
+			},
+			WorkflowURL: String("w"),
+			Repository: &Repository{
+				ID:   Int64(1),
+				URL:  String("u"),
+				Name: String("n"),
+			},
+			HeadRepository: &Repository{
+				ID:   Int64(1),
+				URL:  String("u"),
+				Name: String("n"),
+			},
+		},
+	}
+
+	want := `{
+		"action": "a",
+		"requester": {
+			"login": "l",
+			"id": 1,
+			"node_id": "n",
+			"avatar_url": "a",
+			"url": "u",
+			"html_url": "h",
+			"followers_url": "f",
+			"following_url": "f",
+			"gists_url": "g",
+			"starred_url": "s",
+			"subscriptions_url": "s",
+			"organizations_url": "o",
+			"repos_url": "r",
+			"events_url": "e",
+			"received_events_url": "r",
+			"type": "User",
+			"site_admin": false,
+			"name": "n",
+			"email": "e",
+			"gravatar_id": "g"
+		},
+		"reviewers": [
+			{
+				"type": "User",
+				"reviewer": {
+					"login": "l",
+					"id": 1,
+					"node_id": "n",
+					"avatar_url": "a",
+					"url": "u",
+					"html_url": "h",
+					"followers_url": "f",
+					"following_url": "f",
+					"gists_url": "g",
+					"starred_url": "s",
+					"subscriptions_url": "s",
+					"organizations_url": "o",
+					"repos_url": "r",
+					"events_url": "e",
+					"received_events_url": "r",
+					"type": "User",
+					"site_admin": false,
+					"name": "n",
+					"email": "e",
+					"gravatar_id": "g"
+				}
+			},
+			{
+				"type": "Team",
+				"reviewer": {
+					"id": 1,
+					"name": "n",
+					"slug": "s"
+				}
+			}
+		],
+		"repository": {
+			"id": 1,
+			"name": "n",
+			"url": "s"
+		},
+        "organization": {
+			"name": "n",
+			"company": "c",
+			"blog": "b",
+			"location": "loc",
+			"email": "e",
+			"twitter_username": "tu",
+			"description": "d",
+			"billing_email": "be",
+			"is_verified": true,
+			"has_organization_projects": true,
+			"has_repository_projects": true,
+			"default_repository_permission": "drp",
+			"members_can_create_repositories": true,
+			"members_can_create_public_repositories": false,
+			"members_can_create_private_repositories": true,
+			"members_can_create_internal_repositories": true,
+			"members_allowed_repository_creation_type": "marct",
+			"members_can_create_pages": true,
+			"members_can_create_public_pages": false,
+			"members_can_create_private_pages": true
+		},
+		"environment": "e",
+        "enterprise": {
+			"id": 1,
+			"slug": "s",
+			"name": "n",
+			"node_id": "nid",
+			"avatar_url": "au",
+			"description": "d",
+			"website_url": "wu",
+			"html_url": "hu",
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `
+		},
+		"sender": {
+			"login": "l",
+			"id": 1,
+			"node_id": "n",
+			"avatar_url": "a",
+			"url": "u",
+			"events_url": "e",
+			"repos_url": "r"
+		},
+        "installation": {
+			"id": 1,
+			"node_id": "nid",
+			"app_id": 1,
+			"app_slug": "as",
+			"target_id": 1,
+			"account": {
+				"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"
+			},
+			"access_tokens_url": "atu",
+			"repositories_url": "ru",
+			"html_url": "hu",
+			"target_type": "tt",
+			"single_file_name": "sfn",
+			"repository_selection": "rs",
+			"events": [
+				"e"
+			],
+			"single_file_paths": [
+				"s"
+			],
+			"permissions": {
+				"actions": "a",
+				"administration": "ad",
+				"checks": "c",
+				"contents": "co",
+				"content_references": "cr",
+				"deployments": "d",
+				"environments": "e",
+				"issues": "i",
+				"metadata": "md",
+				"members": "m",
+				"organization_administration": "oa",
+				"organization_hooks": "oh",
+				"organization_plan": "op",
+				"organization_pre_receive_hooks": "opr",
+				"organization_projects": "op",
+				"organization_secrets": "os",
+				"organization_self_hosted_runners": "osh",
+				"organization_user_blocking": "oub",
+				"packages": "pkg",
+				"pages": "pg",
+				"pull_requests": "pr",
+				"repository_hooks": "rh",
+				"repository_projects": "rp",
+				"repository_pre_receive_hooks": "rprh",
+				"secrets": "s",
+				"secret_scanning_alerts": "ssa",
+				"security_events": "se",
+				"single_file": "sf",
+				"statuses": "s",
+				"team_discussions": "td",
+				"vulnerability_alerts": "va",
+				"workflows": "w"
+			},
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"has_multiple_single_files": false,
+			"suspended_by": {
+				"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"
+			},
+			"suspended_at": ` + referenceTimeStr + `
+		},
+		"since": "s",
+		"workflow_job_run": {
+			"conclusion": "c",
+			"created_at": "2006-01-02T15:04:05Z",
+			"environment": "e",
+			"html_url": "h",
+			"id": 1,
+			"name": "n",
+			"status": "s",
+			"updated_at": "2006-01-02T15:04:05Z"
+		},
+		"workflow_run": {
+			"id": 1,
+			"name": "n",
+			"node_id": "nid",
+			"head_branch": "hb",
+			"head_sha": "hs",
+			"run_number": 1,
+			"run_attempt": 1,
+			"event": "e",
+			"status": "s",
+			"conclusion": "c",
+			"workflow_id": 1,
+			"url": "u",
+			"html_url": "h",
+			"pull_requests": [
+				{
+					"id": 1,
+					"number": 1,
+					"url": "u",
+					"head": {
+						"ref": "r",
+						"sha": "s",
+						"repo": {
+							"id": 1,
+							"name": "n",
+							"url": "s"
+						}
+					},
+					"base": {
+						"ref": "r",
+						"sha": "s",
+						"repo": {
+							"id": 1,
+							"name": "n",
+							"url": "u"
+						}
+					}
+				}
+			],
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"run_started_at": ` + referenceTimeStr + `,
+			"jobs_url": "j",
+			"logs_url": "l",
+			"check_suite_url": "c",
+			"artifacts_url": "a",
+			"cancel_url": "c",
+			"rerun_url": "r",
+			"previous_attempt_url": "p",
+			"head_commit": {
+				"message": "m",
+				"author": {
+					"name": "n",
+					"email": "e",
+					"username": "l"
+				},
+				"url": "u",
+				"distinct": false,
+				"sha": "s",
+				"id": "i",
+				"tree_id": "tid",
+				"timestamp": ` + referenceTimeStr + `,
+				"committer": {
+					"name": "n",
+					"email": "e",
+					"username": "l"
+				}
+			},
+			"workflow_url": "w",
+			"repository": {
+				"id": 1,
+				"name": "n",
+				"url": "u"
+			},
+			"head_repository": {
+				"id": 1,
+				"name": "n",
+				"url": "u"
+			}
+		}
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
 func TestDeploymentStatusEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, &DeploymentStatusEvent{}, "{}")
 
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index ae380160..8af9a9b5 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -6927,6 +6927,109 @@ func TestDeploymentRequest_GetTransientEnvironment(tt *testing.T) {
 	d.GetTransientEnvironment()
 }
 
+func TestDeploymentReviewEvent_GetAction(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentReviewEvent{Action: &zeroValue}
+	d.GetAction()
+	d = &DeploymentReviewEvent{}
+	d.GetAction()
+	d = nil
+	d.GetAction()
+}
+
+func TestDeploymentReviewEvent_GetApprover(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetApprover()
+	d = nil
+	d.GetApprover()
+}
+
+func TestDeploymentReviewEvent_GetComment(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentReviewEvent{Comment: &zeroValue}
+	d.GetComment()
+	d = &DeploymentReviewEvent{}
+	d.GetComment()
+	d = nil
+	d.GetComment()
+}
+
+func TestDeploymentReviewEvent_GetEnterprise(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetEnterprise()
+	d = nil
+	d.GetEnterprise()
+}
+
+func TestDeploymentReviewEvent_GetEnvironment(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentReviewEvent{Environment: &zeroValue}
+	d.GetEnvironment()
+	d = &DeploymentReviewEvent{}
+	d.GetEnvironment()
+	d = nil
+	d.GetEnvironment()
+}
+
+func TestDeploymentReviewEvent_GetInstallation(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetInstallation()
+	d = nil
+	d.GetInstallation()
+}
+
+func TestDeploymentReviewEvent_GetOrganization(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetOrganization()
+	d = nil
+	d.GetOrganization()
+}
+
+func TestDeploymentReviewEvent_GetRepo(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetRepo()
+	d = nil
+	d.GetRepo()
+}
+
+func TestDeploymentReviewEvent_GetRequester(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetRequester()
+	d = nil
+	d.GetRequester()
+}
+
+func TestDeploymentReviewEvent_GetSender(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetSender()
+	d = nil
+	d.GetSender()
+}
+
+func TestDeploymentReviewEvent_GetSince(tt *testing.T) {
+	var zeroValue string
+	d := &DeploymentReviewEvent{Since: &zeroValue}
+	d.GetSince()
+	d = &DeploymentReviewEvent{}
+	d.GetSince()
+	d = nil
+	d.GetSince()
+}
+
+func TestDeploymentReviewEvent_GetWorkflowJobRun(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetWorkflowJobRun()
+	d = nil
+	d.GetWorkflowJobRun()
+}
+
+func TestDeploymentReviewEvent_GetWorkflowRun(tt *testing.T) {
+	d := &DeploymentReviewEvent{}
+	d.GetWorkflowRun()
+	d = nil
+	d.GetWorkflowRun()
+}
+
 func TestDeploymentStatus_GetCreatedAt(tt *testing.T) {
 	var zeroValue Timestamp
 	d := &DeploymentStatus{CreatedAt: &zeroValue}
@@ -16876,6 +16979,93 @@ func TestPagesUpdate_GetSource(tt *testing.T) {
 	p.GetSource()
 }
 
+func TestPendingDeployment_GetCurrentUserCanApprove(tt *testing.T) {
+	var zeroValue bool
+	p := &PendingDeployment{CurrentUserCanApprove: &zeroValue}
+	p.GetCurrentUserCanApprove()
+	p = &PendingDeployment{}
+	p.GetCurrentUserCanApprove()
+	p = nil
+	p.GetCurrentUserCanApprove()
+}
+
+func TestPendingDeployment_GetEnvironment(tt *testing.T) {
+	p := &PendingDeployment{}
+	p.GetEnvironment()
+	p = nil
+	p.GetEnvironment()
+}
+
+func TestPendingDeployment_GetWaitTimer(tt *testing.T) {
+	var zeroValue int64
+	p := &PendingDeployment{WaitTimer: &zeroValue}
+	p.GetWaitTimer()
+	p = &PendingDeployment{}
+	p.GetWaitTimer()
+	p = nil
+	p.GetWaitTimer()
+}
+
+func TestPendingDeployment_GetWaitTimerStartedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PendingDeployment{WaitTimerStartedAt: &zeroValue}
+	p.GetWaitTimerStartedAt()
+	p = &PendingDeployment{}
+	p.GetWaitTimerStartedAt()
+	p = nil
+	p.GetWaitTimerStartedAt()
+}
+
+func TestPendingDeploymentEnvironment_GetHTMLURL(tt *testing.T) {
+	var zeroValue string
+	p := &PendingDeploymentEnvironment{HTMLURL: &zeroValue}
+	p.GetHTMLURL()
+	p = &PendingDeploymentEnvironment{}
+	p.GetHTMLURL()
+	p = nil
+	p.GetHTMLURL()
+}
+
+func TestPendingDeploymentEnvironment_GetID(tt *testing.T) {
+	var zeroValue int64
+	p := &PendingDeploymentEnvironment{ID: &zeroValue}
+	p.GetID()
+	p = &PendingDeploymentEnvironment{}
+	p.GetID()
+	p = nil
+	p.GetID()
+}
+
+func TestPendingDeploymentEnvironment_GetName(tt *testing.T) {
+	var zeroValue string
+	p := &PendingDeploymentEnvironment{Name: &zeroValue}
+	p.GetName()
+	p = &PendingDeploymentEnvironment{}
+	p.GetName()
+	p = nil
+	p.GetName()
+}
+
+func TestPendingDeploymentEnvironment_GetNodeID(tt *testing.T) {
+	var zeroValue string
+	p := &PendingDeploymentEnvironment{NodeID: &zeroValue}
+	p.GetNodeID()
+	p = &PendingDeploymentEnvironment{}
+	p.GetNodeID()
+	p = nil
+	p.GetNodeID()
+}
+
+func TestPendingDeploymentEnvironment_GetURL(tt *testing.T) {
+	var zeroValue string
+	p := &PendingDeploymentEnvironment{URL: &zeroValue}
+	p.GetURL()
+	p = &PendingDeploymentEnvironment{}
+	p.GetURL()
+	p = nil
+	p.GetURL()
+}
+
 func TestPersonalAccessToken_GetAccessGrantedAt(tt *testing.T) {
 	var zeroValue Timestamp
 	p := &PersonalAccessToken{AccessGrantedAt: &zeroValue}
@@ -29587,6 +29777,86 @@ func TestWorkflowJobEvent_GetWorkflowJob(tt *testing.T) {
 	w.GetWorkflowJob()
 }
 
+func TestWorkflowJobRun_GetConclusion(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowJobRun{Conclusion: &zeroValue}
+	w.GetConclusion()
+	w = &WorkflowJobRun{}
+	w.GetConclusion()
+	w = nil
+	w.GetConclusion()
+}
+
+func TestWorkflowJobRun_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	w := &WorkflowJobRun{CreatedAt: &zeroValue}
+	w.GetCreatedAt()
+	w = &WorkflowJobRun{}
+	w.GetCreatedAt()
+	w = nil
+	w.GetCreatedAt()
+}
+
+func TestWorkflowJobRun_GetEnvironment(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowJobRun{Environment: &zeroValue}
+	w.GetEnvironment()
+	w = &WorkflowJobRun{}
+	w.GetEnvironment()
+	w = nil
+	w.GetEnvironment()
+}
+
+func TestWorkflowJobRun_GetHTMLURL(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowJobRun{HTMLURL: &zeroValue}
+	w.GetHTMLURL()
+	w = &WorkflowJobRun{}
+	w.GetHTMLURL()
+	w = nil
+	w.GetHTMLURL()
+}
+
+func TestWorkflowJobRun_GetID(tt *testing.T) {
+	var zeroValue int64
+	w := &WorkflowJobRun{ID: &zeroValue}
+	w.GetID()
+	w = &WorkflowJobRun{}
+	w.GetID()
+	w = nil
+	w.GetID()
+}
+
+func TestWorkflowJobRun_GetName(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowJobRun{Name: &zeroValue}
+	w.GetName()
+	w = &WorkflowJobRun{}
+	w.GetName()
+	w = nil
+	w.GetName()
+}
+
+func TestWorkflowJobRun_GetStatus(tt *testing.T) {
+	var zeroValue string
+	w := &WorkflowJobRun{Status: &zeroValue}
+	w.GetStatus()
+	w = &WorkflowJobRun{}
+	w.GetStatus()
+	w = nil
+	w.GetStatus()
+}
+
+func TestWorkflowJobRun_GetUpdatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	w := &WorkflowJobRun{UpdatedAt: &zeroValue}
+	w.GetUpdatedAt()
+	w = &WorkflowJobRun{}
+	w.GetUpdatedAt()
+	w = nil
+	w.GetUpdatedAt()
+}
+
 func TestWorkflowRun_GetActor(tt *testing.T) {
 	w := &WorkflowRun{}
 	w.GetActor()
diff --git a/github/github_test.go b/github/github_test.go
index 8c6c38a8..5f5aba27 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -15,7 +15,7 @@ import (
 	"net/http/httptest"
 	"net/url"
 	"os"
-	"path"
+	"path/filepath"
 	"reflect"
 	"strconv"
 	"strings"
@@ -68,29 +68,23 @@ func setup() (client *Client, mux *http.ServeMux, serverURL string, teardown fun
 
 // openTestFile creates a new file with the given name and content for testing.
 // In order to ensure the exact file name, this function will create a new temp
-// directory, and create the file in that directory. It is the caller's
-// responsibility to remove the directory and its contents when no longer needed.
-func openTestFile(name, content string) (file *os.File, dir string, err error) {
-	dir, err = os.MkdirTemp("", "go-github")
+// directory, and create the file in that directory. The file is automatically
+// cleaned up after the test.
+func openTestFile(t *testing.T, name, content string) *os.File {
+	t.Helper()
+	fname := filepath.Join(t.TempDir(), name)
+	err := os.WriteFile(fname, []byte(content), 0600)
 	if err != nil {
-		return nil, dir, err
+		t.Fatal(err)
 	}
-
-	file, err = os.OpenFile(path.Join(dir, name), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
+	file, err := os.Open(fname)
 	if err != nil {
-		return nil, dir, err
+		t.Fatal(err)
 	}
 
-	fmt.Fprint(file, content)
-
-	// close and re-open the file to keep file.Stat() happy
-	file.Close()
-	file, err = os.Open(file.Name())
-	if err != nil {
-		return nil, dir, err
-	}
+	t.Cleanup(func() { file.Close() })
 
-	return file, dir, err
+	return file
 }
 
 func testMethod(t *testing.T, r *http.Request, want string) {
diff --git a/github/messages_test.go b/github/messages_test.go
index 4783ec68..322505f6 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -300,6 +300,10 @@ func TestParseWebHook(t *testing.T) {
 			payload:     &DeploymentProtectionRuleEvent{},
 			messageType: "deployment_protection_rule",
 		},
+		{
+			payload:     &DeploymentReviewEvent{},
+			messageType: "deployment_review",
+		},
 		{
 			payload:     &DeploymentStatusEvent{},
 			messageType: "deployment_status",
diff --git a/github/orgs_custom_repository_roles_test.go b/github/orgs_custom_repository_roles_test.go
new file mode 100644
index 00000000..58a7c97c
--- /dev/null
+++ b/github/orgs_custom_repository_roles_test.go
@@ -0,0 +1,208 @@
+// Copyright 2024 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"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/custom-repository-roles", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"total_count": 1, "custom_roles": [
+			{
+				"id": 1,
+				"name": "Developer",
+				"base_role": "write",
+				"permissions": ["delete_alerts_code_scanning"],
+				"organization": {
+					"login": "l",
+					"id": 1,
+					"node_id": "n",
+					"avatar_url": "a",
+					"html_url": "h",
+					"name": "n",
+					"company": "c",
+					"blog": "b",
+					"location": "l",
+					"email": "e"
+				},
+				"created_at": "2024-07-21T19:33:08Z",
+				"updated_at": "2024-07-21T19:33:08Z"
+			}
+		  ]
+		}`)
+	})
+
+	ctx := context.Background()
+	apps, _, err := client.Organizations.ListCustomRepoRoles(ctx, "o")
+	if err != nil {
+		t.Errorf("Organizations.ListCustomRepoRoles returned error: %v", err)
+	}
+
+	want := &OrganizationCustomRepoRoles{
+		TotalCount: Int(1),
+		CustomRepoRoles: []*CustomRepoRoles{
+			{
+				ID:          Int64(1),
+				Name:        String("Developer"),
+				BaseRole:    String("write"),
+				Permissions: []string{"delete_alerts_code_scanning"},
+				Org: &Organization{
+					Login:     String("l"),
+					ID:        Int64(1),
+					NodeID:    String("n"),
+					AvatarURL: String("a"),
+					HTMLURL:   String("h"),
+					Name:      String("n"),
+					Company:   String("c"),
+					Blog:      String("b"),
+					Location:  String("l"),
+					Email:     String("e"),
+				},
+				CreatedAt: &Timestamp{time.Date(2024, time.July, 21, 19, 33, 8, 0, time.UTC)},
+				UpdatedAt: &Timestamp{time.Date(2024, time.July, 21, 19, 33, 8, 0, time.UTC)},
+			},
+		},
+	}
+	if !cmp.Equal(apps, want) {
+		t.Errorf("Organizations.ListCustomRepoRoles returned %+v, want %+v", apps, want)
+	}
+
+	const methodName = "ListCustomRepoRoles"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.ListCustomRepoRoles(ctx, "\no")
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.ListCustomRepoRoles(ctx, "o")
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_CreateCustomRepoRole(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/custom-repository-roles", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `{"id":8030,"name":"Labeler","description":"A role for issue and PR labelers","base_role":"read","permissions":["add_label"]}`)
+	})
+
+	ctx := context.Background()
+
+	opts := &CreateOrUpdateCustomRepoRoleOptions{
+		Name:        String("Labeler"),
+		Description: String("A role for issue and PR labelers"),
+		BaseRole:    String("read"),
+		Permissions: []string{"add_label"},
+	}
+	apps, _, err := client.Organizations.CreateCustomRepoRole(ctx, "o", opts)
+	if err != nil {
+		t.Errorf("Organizations.CreateCustomRepoRole returned error: %v", err)
+	}
+
+	want := &CustomRepoRoles{ID: Int64(8030), Name: String("Labeler"), BaseRole: String("read"), Permissions: []string{"add_label"}, Description: String("A role for issue and PR labelers")}
+
+	if !cmp.Equal(apps, want) {
+		t.Errorf("Organizations.CreateCustomRepoRole returned %+v, want %+v", apps, want)
+	}
+
+	const methodName = "CreateCustomRepoRole"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.CreateCustomRepoRole(ctx, "\no", nil)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.CreateCustomRepoRole(ctx, "o", nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_UpdateCustomRepoRole(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/custom-repository-roles/8030", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PATCH")
+		fmt.Fprint(w, `{"id":8030,"name":"Updated Name","description":"Updated Description","base_role":"read","permissions":["add_label"]}`)
+	})
+
+	ctx := context.Background()
+
+	opts := &CreateOrUpdateCustomRepoRoleOptions{
+		Name:        String("Updated Name"),
+		Description: String("Updated Description"),
+	}
+	apps, _, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, opts)
+	if err != nil {
+		t.Errorf("Organizations.UpdateCustomRepoRole returned error: %v", err)
+	}
+
+	want := &CustomRepoRoles{ID: Int64(8030), Name: String("Updated Name"), BaseRole: String("read"), Permissions: []string{"add_label"}, Description: String("Updated Description")}
+
+	if !cmp.Equal(apps, want) {
+		t.Errorf("Organizations.UpdateCustomRepoRole returned %+v, want %+v", apps, want)
+	}
+
+	const methodName = "UpdateCustomRepoRole"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.UpdateCustomRepoRole(ctx, "\no", 8030, nil)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_DeleteCustomRepoRole(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/custom-repository-roles/8030", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+
+	resp, err := client.Organizations.DeleteCustomRepoRole(ctx, "o", 8030)
+	if err != nil {
+		t.Errorf("Organizations.DeleteCustomRepoRole returned error: %v", err)
+	}
+
+	if !cmp.Equal(resp.StatusCode, 204) {
+		t.Errorf("Organizations.DeleteCustomRepoRole returned  status code %+v, want %+v", resp.StatusCode, "204")
+	}
+
+	const methodName = "DeleteCustomRepoRole"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Organizations.DeleteCustomRepoRole(ctx, "\no", 8030)
+		return err
+	})
+}
diff --git a/github/orgs_members_test.go b/github/orgs_members_test.go
index 9526442c..3717802a 100644
--- a/github/orgs_members_test.go
+++ b/github/orgs_members_test.go
@@ -284,6 +284,32 @@ func TestOrganizationsService_RemoveMember(t *testing.T) {
 	})
 }
 
+func TestOrganizationsService_CancelInvite(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/invitations/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	_, err := client.Organizations.CancelInvite(ctx, "o", 1)
+	if err != nil {
+		t.Errorf("Organizations.CancelInvite returned error: %v", err)
+	}
+
+	const methodName = "CancelInvite"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Organizations.CancelInvite(ctx, "\n", 1)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Organizations.CancelInvite(ctx, "o", 1)
+	})
+}
+
 func TestOrganizationsService_RemoveMember_invalidOrg(t *testing.T) {
 	client, _, _, teardown := setup()
 	defer teardown()
diff --git a/github/orgs_custom_roles_test.go b/github/orgs_organization_roles_test.go
similarity index 72%
rename from github/orgs_custom_roles_test.go
rename to github/orgs_organization_roles_test.go
index dad11594..3e2998fd 100644
--- a/github/orgs_custom_roles_test.go
+++ b/github/orgs_organization_roles_test.go
@@ -427,198 +427,6 @@ func TestOrganizationsService_RemoveOrgRoleFromUser(t *testing.T) {
 	})
 }
 
-func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) {
-	client, mux, _, teardown := setup()
-	defer teardown()
-
-	mux.HandleFunc("/orgs/o/custom-repository-roles", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "GET")
-		fmt.Fprint(w, `{"total_count": 1, "custom_roles": [
-			{
-				"id": 1,
-				"name": "Developer",
-				"base_role": "write",
-				"permissions": ["delete_alerts_code_scanning"],
-				"organization": {
-					"login": "l",
-					"id": 1,
-					"node_id": "n",
-					"avatar_url": "a",
-					"html_url": "h",
-					"name": "n",
-					"company": "c",
-					"blog": "b",
-					"location": "l",
-					"email": "e"
-				},
-				"created_at": "2024-07-21T19:33:08Z",
-				"updated_at": "2024-07-21T19:33:08Z"
-			}
-		  ]
-		}`)
-	})
-
-	ctx := context.Background()
-	apps, _, err := client.Organizations.ListCustomRepoRoles(ctx, "o")
-	if err != nil {
-		t.Errorf("Organizations.ListCustomRepoRoles returned error: %v", err)
-	}
-
-	want := &OrganizationCustomRepoRoles{
-		TotalCount: Int(1),
-		CustomRepoRoles: []*CustomRepoRoles{
-			{
-				ID:          Int64(1),
-				Name:        String("Developer"),
-				BaseRole:    String("write"),
-				Permissions: []string{"delete_alerts_code_scanning"},
-				Org: &Organization{
-					Login:     String("l"),
-					ID:        Int64(1),
-					NodeID:    String("n"),
-					AvatarURL: String("a"),
-					HTMLURL:   String("h"),
-					Name:      String("n"),
-					Company:   String("c"),
-					Blog:      String("b"),
-					Location:  String("l"),
-					Email:     String("e"),
-				},
-				CreatedAt: &Timestamp{time.Date(2024, time.July, 21, 19, 33, 8, 0, time.UTC)},
-				UpdatedAt: &Timestamp{time.Date(2024, time.July, 21, 19, 33, 8, 0, time.UTC)},
-			},
-		},
-	}
-	if !cmp.Equal(apps, want) {
-		t.Errorf("Organizations.ListCustomRepoRoles returned %+v, want %+v", apps, want)
-	}
-
-	const methodName = "ListCustomRepoRoles"
-	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Organizations.ListCustomRepoRoles(ctx, "\no")
-		return err
-	})
-
-	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Organizations.ListCustomRepoRoles(ctx, "o")
-		if got != nil {
-			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
-		}
-		return resp, err
-	})
-}
-
-func TestOrganizationsService_CreateCustomRepoRole(t *testing.T) {
-	client, mux, _, teardown := setup()
-	defer teardown()
-
-	mux.HandleFunc("/orgs/o/custom-repository-roles", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
-		fmt.Fprint(w, `{"id":8030,"name":"Labeler","description":"A role for issue and PR labelers","base_role":"read","permissions":["add_label"]}`)
-	})
-
-	ctx := context.Background()
-
-	opts := &CreateOrUpdateCustomRepoRoleOptions{
-		Name:        String("Labeler"),
-		Description: String("A role for issue and PR labelers"),
-		BaseRole:    String("read"),
-		Permissions: []string{"add_label"},
-	}
-	apps, _, err := client.Organizations.CreateCustomRepoRole(ctx, "o", opts)
-	if err != nil {
-		t.Errorf("Organizations.CreateCustomRepoRole returned error: %v", err)
-	}
-
-	want := &CustomRepoRoles{ID: Int64(8030), Name: String("Labeler"), BaseRole: String("read"), Permissions: []string{"add_label"}, Description: String("A role for issue and PR labelers")}
-
-	if !cmp.Equal(apps, want) {
-		t.Errorf("Organizations.CreateCustomRepoRole returned %+v, want %+v", apps, want)
-	}
-
-	const methodName = "CreateCustomRepoRole"
-	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Organizations.CreateCustomRepoRole(ctx, "\no", nil)
-		return err
-	})
-
-	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Organizations.CreateCustomRepoRole(ctx, "o", nil)
-		if got != nil {
-			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
-		}
-		return resp, err
-	})
-}
-
-func TestOrganizationsService_UpdateCustomRepoRole(t *testing.T) {
-	client, mux, _, teardown := setup()
-	defer teardown()
-
-	mux.HandleFunc("/orgs/o/custom-repository-roles/8030", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "PATCH")
-		fmt.Fprint(w, `{"id":8030,"name":"Updated Name","description":"Updated Description","base_role":"read","permissions":["add_label"]}`)
-	})
-
-	ctx := context.Background()
-
-	opts := &CreateOrUpdateCustomRepoRoleOptions{
-		Name:        String("Updated Name"),
-		Description: String("Updated Description"),
-	}
-	apps, _, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, opts)
-	if err != nil {
-		t.Errorf("Organizations.UpdateCustomRepoRole returned error: %v", err)
-	}
-
-	want := &CustomRepoRoles{ID: Int64(8030), Name: String("Updated Name"), BaseRole: String("read"), Permissions: []string{"add_label"}, Description: String("Updated Description")}
-
-	if !cmp.Equal(apps, want) {
-		t.Errorf("Organizations.UpdateCustomRepoRole returned %+v, want %+v", apps, want)
-	}
-
-	const methodName = "UpdateCustomRepoRole"
-	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Organizations.UpdateCustomRepoRole(ctx, "\no", 8030, nil)
-		return err
-	})
-
-	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Organizations.UpdateCustomRepoRole(ctx, "o", 8030, nil)
-		if got != nil {
-			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
-		}
-		return resp, err
-	})
-}
-
-func TestOrganizationsService_DeleteCustomRepoRole(t *testing.T) {
-	client, mux, _, teardown := setup()
-	defer teardown()
-
-	mux.HandleFunc("/orgs/o/custom-repository-roles/8030", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "DELETE")
-		w.WriteHeader(http.StatusNoContent)
-	})
-
-	ctx := context.Background()
-
-	resp, err := client.Organizations.DeleteCustomRepoRole(ctx, "o", 8030)
-	if err != nil {
-		t.Errorf("Organizations.DeleteCustomRepoRole returned error: %v", err)
-	}
-
-	if !cmp.Equal(resp.StatusCode, 204) {
-		t.Errorf("Organizations.DeleteCustomRepoRole returned  status code %+v, want %+v", resp.StatusCode, "204")
-	}
-
-	const methodName = "DeleteCustomRepoRole"
-	testBadOptions(t, methodName, func() (err error) {
-		_, err = client.Organizations.DeleteCustomRepoRole(ctx, "\no", 8030)
-		return err
-	})
-}
-
 func TestOrganizationsService_ListTeamsAssignedToOrgRole(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/repos_releases_test.go b/github/repos_releases_test.go
index d7efd096..2cca2bd5 100644
--- a/github/repos_releases_test.go
+++ b/github/repos_releases_test.go
@@ -12,7 +12,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"os"
 	"strings"
 	"testing"
 
@@ -707,11 +706,7 @@ func TestRepositoriesService_UploadReleaseAsset(t *testing.T) {
 			fmt.Fprintf(w, `{"id":1}`)
 		})
 
-		file, dir, err := openTestFile(test.fileName, "Upload me !\n")
-		if err != nil {
-			t.Fatalf("Unable to create temp file: %v", err)
-		}
-		defer os.RemoveAll(dir)
+		file := openTestFile(t, test.fileName, "Upload me !\n")
 
 		ctx := context.Background()
 		asset, _, err := client.Repositories.UploadReleaseAsset(ctx, "o", "r", int64(key), test.uploadOpts, file)