Repository: google/go-github
Pull request #3215 (2024-07-20 21:55:15)

Add List fine-grained personal access tokens with access to organization resources API
Fixes: #3213.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3215?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 92.93%. Comparing base [(`2b8c7fa`)](https://app.codecov.io/gh/google/go-github/commit/2b8c7fa86fcd1cb4a89a44c8e7b5a49fe4be8a45?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`04f9a42`)](https://app.codecov.io/gh/google/go-github/commit/04f9a4246769c3b0a000f9a911f4b3c4aa6939fb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> Report is 84 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3215      +/-   ##
==========================================
- Coverage   97.72%   92.93%   -4.79%     
==========================================
  Files         153      171      +18     
  Lines       13390    11595    -1795     
==========================================
- Hits        13085    10776    -2309     
- Misses        215      726     +511     
- Partials       90       93       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/go-github/pull/3215?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).


Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 5118791c..0b121aac 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -4702,6 +4702,22 @@ func (c *CustomDeploymentProtectionRuleRequest) GetIntegrationID() int64 {
 	return *c.IntegrationID
 }
 
+// GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise.
+func (c *CustomOrgRoles) GetBaseRole() string {
+	if c == nil || c.BaseRole == nil {
+		return ""
+	}
+	return *c.BaseRole
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (c *CustomOrgRoles) GetCreatedAt() Timestamp {
+	if c == nil || c.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *c.CreatedAt
+}
+
 // GetDescription returns the Description field if it's non-nil, zero value otherwise.
 func (c *CustomOrgRoles) GetDescription() string {
 	if c == nil || c.Description == nil {
@@ -4726,6 +4742,30 @@ func (c *CustomOrgRoles) GetName() string {
 	return *c.Name
 }
 
+// GetOrg returns the Org field.
+func (c *CustomOrgRoles) GetOrg() *Organization {
+	if c == nil {
+		return nil
+	}
+	return c.Org
+}
+
+// GetSource returns the Source field if it's non-nil, zero value otherwise.
+func (c *CustomOrgRoles) GetSource() string {
+	if c == nil || c.Source == nil {
+		return ""
+	}
+	return *c.Source
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (c *CustomOrgRoles) GetUpdatedAt() Timestamp {
+	if c == nil || c.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *c.UpdatedAt
+}
+
 // GetDefaultValue returns the DefaultValue field if it's non-nil, zero value otherwise.
 func (c *CustomProperty) GetDefaultValue() string {
 	if c == nil || c.DefaultValue == nil {
@@ -4766,14 +4806,6 @@ func (c *CustomProperty) GetValuesEditableBy() string {
 	return *c.ValuesEditableBy
 }
 
-// GetValue returns the Value field if it's non-nil, zero value otherwise.
-func (c *CustomPropertyValue) GetValue() string {
-	if c == nil || c.Value == nil {
-		return ""
-	}
-	return *c.Value
-}
-
 // GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise.
 func (c *CustomRepoRoles) GetBaseRole() string {
 	if c == nil || c.BaseRole == nil {
@@ -4782,6 +4814,14 @@ func (c *CustomRepoRoles) GetBaseRole() string {
 	return *c.BaseRole
 }
 
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (c *CustomRepoRoles) GetCreatedAt() Timestamp {
+	if c == nil || c.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *c.CreatedAt
+}
+
 // GetDescription returns the Description field if it's non-nil, zero value otherwise.
 func (c *CustomRepoRoles) GetDescription() string {
 	if c == nil || c.Description == nil {
@@ -4806,6 +4846,22 @@ func (c *CustomRepoRoles) GetName() string {
 	return *c.Name
 }
 
+// GetOrg returns the Org field.
+func (c *CustomRepoRoles) GetOrg() *Organization {
+	if c == nil {
+		return nil
+	}
+	return c.Org
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (c *CustomRepoRoles) GetUpdatedAt() Timestamp {
+	if c == nil || c.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *c.UpdatedAt
+}
+
 // GetQuerySuite returns the QuerySuite field if it's non-nil, zero value otherwise.
 func (d *DefaultSetupConfiguration) GetQuerySuite() string {
 	if d == nil || d.QuerySuite == nil {
@@ -6550,22 +6606,6 @@ func (d *DraftReviewComment) GetBody() string {
 	return *d.Body
 }
 
-// GetCommitID returns the CommitID field if it's non-nil, zero value otherwise.
-func (d *DraftReviewComment) GetCommitID() string {
-	if d == nil || d.CommitID == nil {
-		return ""
-	}
-	return *d.CommitID
-}
-
-// GetInReplyTo returns the InReplyTo field if it's non-nil, zero value otherwise.
-func (d *DraftReviewComment) GetInReplyTo() int64 {
-	if d == nil || d.InReplyTo == nil {
-		return 0
-	}
-	return *d.InReplyTo
-}
-
 // GetLine returns the Line field if it's non-nil, zero value otherwise.
 func (d *DraftReviewComment) GetLine() int {
 	if d == nil || d.Line == nil {
@@ -6614,14 +6654,6 @@ func (d *DraftReviewComment) GetStartSide() string {
 	return *d.StartSide
 }
 
-// GetSubjectType returns the SubjectType field if it's non-nil, zero value otherwise.
-func (d *DraftReviewComment) GetSubjectType() string {
-	if d == nil || d.SubjectType == nil {
-		return ""
-	}
-	return *d.SubjectType
-}
-
 // GetRef returns the Ref field.
 func (e *EditBase) GetRef() *EditRef {
 	if e == nil {
@@ -14326,6 +14358,78 @@ func (p *PagesUpdate) GetSource() *PagesSource {
 	return p.Source
 }
 
+// GetAccessGrantedAt returns the AccessGrantedAt field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetAccessGrantedAt() Timestamp {
+	if p == nil || p.AccessGrantedAt == nil {
+		return Timestamp{}
+	}
+	return *p.AccessGrantedAt
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetID() int64 {
+	if p == nil || p.ID == nil {
+		return 0
+	}
+	return *p.ID
+}
+
+// GetOwner returns the Owner field.
+func (p *PersonalAccessToken) GetOwner() *User {
+	if p == nil {
+		return nil
+	}
+	return p.Owner
+}
+
+// GetPermissions returns the Permissions field.
+func (p *PersonalAccessToken) GetPermissions() *PersonalAccessTokenPermissions {
+	if p == nil {
+		return nil
+	}
+	return p.Permissions
+}
+
+// GetRepositoriesURL returns the RepositoriesURL field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetRepositoriesURL() string {
+	if p == nil || p.RepositoriesURL == nil {
+		return ""
+	}
+	return *p.RepositoriesURL
+}
+
+// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetRepositorySelection() string {
+	if p == nil || p.RepositorySelection == nil {
+		return ""
+	}
+	return *p.RepositorySelection
+}
+
+// GetTokenExpired returns the TokenExpired field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetTokenExpired() bool {
+	if p == nil || p.TokenExpired == nil {
+		return false
+	}
+	return *p.TokenExpired
+}
+
+// GetTokenExpiresAt returns the TokenExpiresAt field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetTokenExpiresAt() Timestamp {
+	if p == nil || p.TokenExpiresAt == nil {
+		return Timestamp{}
+	}
+	return *p.TokenExpiresAt
+}
+
+// GetTokenLastUsedAt returns the TokenLastUsedAt field if it's non-nil, zero value otherwise.
+func (p *PersonalAccessToken) GetTokenLastUsedAt() Timestamp {
+	if p == nil || p.TokenLastUsedAt == nil {
+		return Timestamp{}
+	}
+	return *p.TokenLastUsedAt
+}
+
 // GetOrg returns the Org map if it's non-nil, an empty map otherwise.
 func (p *PersonalAccessTokenPermissions) GetOrg() map[string]string {
 	if p == nil || p.Org == nil {
diff --git a/github/orgs_custom_roles.go b/github/orgs_custom_roles.go
index f71cb7ae..dcbd79ef 100644
--- a/github/orgs_custom_roles.go
+++ b/github/orgs_custom_roles.go
@@ -18,10 +18,15 @@ type OrganizationCustomRoles struct {
 
 // CustomOrgRoles represents custom organization role available in specified organization.
 type CustomOrgRoles struct {
-	ID          *int64   `json:"id,omitempty"`
-	Name        *string  `json:"name,omitempty"`
-	Description *string  `json:"description,omitempty"`
-	Permissions []string `json:"permissions,omitempty"`
+	ID          *int64        `json:"id,omitempty"`
+	Name        *string       `json:"name,omitempty"`
+	Description *string       `json:"description,omitempty"`
+	Permissions []string      `json:"permissions,omitempty"`
+	Org         *Organization `json:"organization,omitempty"`
+	CreatedAt   *Timestamp    `json:"created_at,omitempty"`
+	UpdatedAt   *Timestamp    `json:"updated_at,omitempty"`
+	Source      *string       `json:"source,omitempty"`
+	BaseRole    *string       `json:"base_role,omitempty"`
 }
 
 // OrganizationCustomRepoRoles represents custom repository roles available in specified organization.
@@ -34,11 +39,14 @@ type OrganizationCustomRepoRoles struct {
 // 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"`
+	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.
diff --git a/github/orgs_personal_access_tokens.go b/github/orgs_personal_access_tokens.go
index 0d786114..af083744 100644
--- a/github/orgs_personal_access_tokens.go
+++ b/github/orgs_personal_access_tokens.go
@@ -9,8 +9,106 @@ import (
 	"context"
 	"fmt"
 	"net/http"
+	"net/url"
+	"strings"
 )
 
+// PersonalAccessToken represents the minimal representation of an organization programmatic access grant.
+//
+// GitHub API docs: https://docs.github.com/en/rest/orgs/personal-access-tokens?apiVersion=2022-11-28
+type PersonalAccessToken struct {
+	// "Unique identifier of the fine-grained personal access token.
+	// The `pat_id` used to get details about an approved fine-grained personal access token.
+	ID *int64 `json:"id"`
+
+	// Owner is the GitHub user associated with the token.
+	Owner *User `json:"owner"`
+
+	// RepositorySelection is the type of repository selection requested.
+	// Possible values are: "none", "all", "subset".
+	RepositorySelection *string `json:"repository_selection"`
+
+	// URL to the list of repositories the fine-grained personal access token can access.
+	// Only follow when `repository_selection` is `subset`.
+	RepositoriesURL *string `json:"repositories_url"`
+
+	// Permissions are the permissions requested, categorized by type.
+	Permissions *PersonalAccessTokenPermissions `json:"permissions"`
+
+	// Date and time when the fine-grained personal access token was approved to access the organization.
+	AccessGrantedAt *Timestamp `json:"access_granted_at"`
+
+	// Whether the associated fine-grained personal access token has expired.
+	TokenExpired *bool `json:"token_expired"`
+
+	// Date and time when the associated fine-grained personal access token expires.
+	TokenExpiresAt *Timestamp `json:"token_expires_at"`
+
+	// Date and time when the associated fine-grained personal access token was last used for authentication.
+	TokenLastUsedAt *Timestamp `json:"token_last_used_at"`
+}
+
+// ListFineGrainedPATOptions specifies optional parameters to ListFineGrainedPersonalAccessTokens.
+type ListFineGrainedPATOptions struct {
+	// The property by which to sort the results.
+	// Default: created_at
+	// Value: created_at
+	Sort string `url:"sort,omitempty"`
+
+	// The direction to sort the results by.
+	// Default: desc
+	// Value: asc, desc
+	Direction string `url:"direction,omitempty"`
+
+	// A list of owner usernames to use to filter the results.
+	Owner []string `url:"-"`
+
+	// The name of the repository to use to filter the results.
+	Repository string `url:"repository,omitempty"`
+
+	// The permission to use to filter the results.
+	Permission string `url:"permission,omitempty"`
+
+	// Only show fine-grained personal access tokens used before the given time.
+	// This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.
+	LastUsedBefore string `url:"last_used_before,omitempty"`
+
+	// Only show fine-grained personal access tokens used after the given time.
+	// This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.
+	LastUsedAfter string `url:"last_used_after,omitempty"`
+
+	ListOptions
+}
+
+// ListFineGrainedPersonalAccessTokens lists approved fine-grained personal access tokens owned by organization members that can access organization resources.
+// Only GitHub Apps can call this API, using the `Personal access tokens` organization permissions (read).
+//
+// GitHub API docs: https://docs.github.com/rest/orgs/personal-access-tokens#list-fine-grained-personal-access-tokens-with-access-to-organization-resources
+//
+//meta:operation GET /orgs/{org}/personal-access-tokens
+func (s *OrganizationsService) ListFineGrainedPersonalAccessTokens(ctx context.Context, org string, opts *ListFineGrainedPATOptions) ([]*PersonalAccessToken, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/personal-access-tokens", org)
+	// The `owner` parameter is a special case that uses the `owner[]=...` format and needs a custom function to format it correctly.
+	u, err := addListFineGrainedPATOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest(http.MethodGet, u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pats []*PersonalAccessToken
+
+	resp, err := s.client.Do(ctx, req, &pats)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pats, resp, nil
+}
+
 // ReviewPersonalAccessTokenRequestOptions specifies the parameters to the ReviewPersonalAccessTokenRequest method.
 type ReviewPersonalAccessTokenRequestOptions struct {
 	Action string  `json:"action"`
@@ -34,3 +132,36 @@ func (s *OrganizationsService) ReviewPersonalAccessTokenRequest(ctx context.Cont
 
 	return s.client.Do(ctx, req, nil)
 }
+
+// addListFineGrainedPATOptions adds the owner parameter to the URL query string with the correct format if it is set.
+//
+// GitHub API expects the owner parameter to be a list of strings in the `owner[]=...` format.
+// For multiple owner values, the owner parameter is repeated in the query string.
+//
+// Example:
+// owner[]=user1&owner[]=user2
+// This will filter the results to only include fine-grained personal access tokens owned by `user1` and `user2`.
+//
+// This function ensures the owner parameter is formatted correctly in the URL query string.
+func addListFineGrainedPATOptions(s string, opts *ListFineGrainedPATOptions) (string, error) {
+	u, err := addOptions(s, opts)
+	if err != nil {
+		return s, err
+	}
+
+	if len(opts.Owner) > 0 {
+		ownerVals := make([]string, len(opts.Owner))
+		for i, owner := range opts.Owner {
+			ownerVals[i] = fmt.Sprintf("owner[]=%s", url.QueryEscape(owner))
+		}
+		ownerQuery := strings.Join(ownerVals, "&")
+
+		if strings.Contains(u, "?") {
+			u += "&" + ownerQuery
+		} else {
+			u += "?" + ownerQuery
+		}
+	}
+
+	return u, nil
+}
diff --git a/github/orgs_properties.go b/github/orgs_properties.go
index 124b89cb..f7416d53 100644
--- a/github/orgs_properties.go
+++ b/github/orgs_properties.go
@@ -7,6 +7,7 @@ package github
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 )
 
@@ -40,8 +41,42 @@ type RepoCustomPropertyValue struct {
 
 // CustomPropertyValue represents a custom property value.
 type CustomPropertyValue struct {
-	PropertyName string  `json:"property_name"`
-	Value        *string `json:"value,omitempty"`
+	PropertyName string      `json:"property_name"`
+	Value        interface{} `json:"value,omitempty"`
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+// This helps us handle the fact that Value can be either a string, []string, or nil.
+func (cpv *CustomPropertyValue) UnmarshalJSON(data []byte) error {
+	type aliasCustomPropertyValue CustomPropertyValue
+	aux := &struct {
+		*aliasCustomPropertyValue
+	}{
+		aliasCustomPropertyValue: (*aliasCustomPropertyValue)(cpv),
+	}
+	if err := json.Unmarshal(data, &aux); err != nil {
+		return err
+	}
+
+	switch v := aux.Value.(type) {
+	case nil:
+		cpv.Value = nil
+	case string:
+		cpv.Value = v
+	case []interface{}:
+		strSlice := make([]string, len(v))
+		for i, item := range v {
+			if str, ok := item.(string); ok {
+				strSlice[i] = str
+			} else {
+				return fmt.Errorf("non-string value in string array")
+			}
+		}
+		cpv.Value = strSlice
+	default:
+		return fmt.Errorf("unexpected value type: %T", v)
+	}
+	return nil
 }
 
 // GetAllCustomProperties gets all custom properties that are defined for the specified organization.
diff --git a/github/pulls_reviews.go b/github/pulls_reviews.go
index 28450c3d..27b8dc37 100644
--- a/github/pulls_reviews.go
+++ b/github/pulls_reviews.go
@@ -35,12 +35,9 @@ func (p PullRequestReview) String() string {
 
 // DraftReviewComment represents a comment part of the review.
 type DraftReviewComment struct {
-	Path        *string `json:"path,omitempty"`
-	Position    *int    `json:"position,omitempty"`
-	Body        *string `json:"body,omitempty"`
-	CommitID    *string `json:"commit_id,omitempty"`
-	InReplyTo   *int64  `json:"in_reply_to,omitempty"`
-	SubjectType *string `json:"subject_type,omitempty"`
+	Path     *string `json:"path,omitempty"`
+	Position *int    `json:"position,omitempty"`
+	Body     *string `json:"body,omitempty"`
 
 	// The new comfort-fade-preview fields
 	StartSide *string `json:"start_side,omitempty"`
diff --git a/github/teams.go b/github/teams.go
index 0f6cc9d1..b44e9418 100644
--- a/github/teams.go
+++ b/github/teams.go
@@ -796,6 +796,14 @@ func (s *TeamsService) RemoveTeamProjectBySlug(ctx context.Context, org, slug st
 	return s.client.Do(ctx, req, nil)
 }
 
+// ListIDPGroupsOptions specifies the optional parameters to the ListIDPGroupsInOrganization method.
+type ListIDPGroupsOptions struct {
+	// Filters the results to return only those that begin with the value specified by this parameter.
+	Query string `url:"q,omitempty"`
+
+	ListCursorOptions
+}
+
 // IDPGroupList represents a list of external identity provider (IDP) groups.
 type IDPGroupList struct {
 	Groups []*IDPGroup `json:"groups"`
@@ -813,7 +821,7 @@ type IDPGroup struct {
 // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/teams/team-sync#list-idp-groups-for-an-organization
 //
 //meta:operation GET /orgs/{org}/team-sync/groups
-func (s *TeamsService) ListIDPGroupsInOrganization(ctx context.Context, org string, opts *ListCursorOptions) (*IDPGroupList, *Response, error) {
+func (s *TeamsService) ListIDPGroupsInOrganization(ctx context.Context, org string, opts *ListIDPGroupsOptions) (*IDPGroupList, *Response, error) {
 	u := fmt.Sprintf("orgs/%v/team-sync/groups", org)
 	u, err := addOptions(u, opts)
 	if err != nil {


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 03ebada6..f41615c9 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -5533,6 +5533,26 @@ func TestCustomDeploymentProtectionRuleRequest_GetIntegrationID(tt *testing.T) {
 	c.GetIntegrationID()
 }
 
+func TestCustomOrgRoles_GetBaseRole(tt *testing.T) {
+	var zeroValue string
+	c := &CustomOrgRoles{BaseRole: &zeroValue}
+	c.GetBaseRole()
+	c = &CustomOrgRoles{}
+	c.GetBaseRole()
+	c = nil
+	c.GetBaseRole()
+}
+
+func TestCustomOrgRoles_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	c := &CustomOrgRoles{CreatedAt: &zeroValue}
+	c.GetCreatedAt()
+	c = &CustomOrgRoles{}
+	c.GetCreatedAt()
+	c = nil
+	c.GetCreatedAt()
+}
+
 func TestCustomOrgRoles_GetDescription(tt *testing.T) {
 	var zeroValue string
 	c := &CustomOrgRoles{Description: &zeroValue}
@@ -5563,6 +5583,33 @@ func TestCustomOrgRoles_GetName(tt *testing.T) {
 	c.GetName()
 }
 
+func TestCustomOrgRoles_GetOrg(tt *testing.T) {
+	c := &CustomOrgRoles{}
+	c.GetOrg()
+	c = nil
+	c.GetOrg()
+}
+
+func TestCustomOrgRoles_GetSource(tt *testing.T) {
+	var zeroValue string
+	c := &CustomOrgRoles{Source: &zeroValue}
+	c.GetSource()
+	c = &CustomOrgRoles{}
+	c.GetSource()
+	c = nil
+	c.GetSource()
+}
+
+func TestCustomOrgRoles_GetUpdatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	c := &CustomOrgRoles{UpdatedAt: &zeroValue}
+	c.GetUpdatedAt()
+	c = &CustomOrgRoles{}
+	c.GetUpdatedAt()
+	c = nil
+	c.GetUpdatedAt()
+}
+
 func TestCustomProperty_GetDefaultValue(tt *testing.T) {
 	var zeroValue string
 	c := &CustomProperty{DefaultValue: &zeroValue}
@@ -5613,16 +5660,6 @@ func TestCustomProperty_GetValuesEditableBy(tt *testing.T) {
 	c.GetValuesEditableBy()
 }
 
-func TestCustomPropertyValue_GetValue(tt *testing.T) {
-	var zeroValue string
-	c := &CustomPropertyValue{Value: &zeroValue}
-	c.GetValue()
-	c = &CustomPropertyValue{}
-	c.GetValue()
-	c = nil
-	c.GetValue()
-}
-
 func TestCustomRepoRoles_GetBaseRole(tt *testing.T) {
 	var zeroValue string
 	c := &CustomRepoRoles{BaseRole: &zeroValue}
@@ -5633,6 +5670,16 @@ func TestCustomRepoRoles_GetBaseRole(tt *testing.T) {
 	c.GetBaseRole()
 }
 
+func TestCustomRepoRoles_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	c := &CustomRepoRoles{CreatedAt: &zeroValue}
+	c.GetCreatedAt()
+	c = &CustomRepoRoles{}
+	c.GetCreatedAt()
+	c = nil
+	c.GetCreatedAt()
+}
+
 func TestCustomRepoRoles_GetDescription(tt *testing.T) {
 	var zeroValue string
 	c := &CustomRepoRoles{Description: &zeroValue}
@@ -5663,6 +5710,23 @@ func TestCustomRepoRoles_GetName(tt *testing.T) {
 	c.GetName()
 }
 
+func TestCustomRepoRoles_GetOrg(tt *testing.T) {
+	c := &CustomRepoRoles{}
+	c.GetOrg()
+	c = nil
+	c.GetOrg()
+}
+
+func TestCustomRepoRoles_GetUpdatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	c := &CustomRepoRoles{UpdatedAt: &zeroValue}
+	c.GetUpdatedAt()
+	c = &CustomRepoRoles{}
+	c.GetUpdatedAt()
+	c = nil
+	c.GetUpdatedAt()
+}
+
 func TestDefaultSetupConfiguration_GetQuerySuite(tt *testing.T) {
 	var zeroValue string
 	d := &DefaultSetupConfiguration{QuerySuite: &zeroValue}
@@ -7663,26 +7727,6 @@ func TestDraftReviewComment_GetBody(tt *testing.T) {
 	d.GetBody()
 }
 
-func TestDraftReviewComment_GetCommitID(tt *testing.T) {
-	var zeroValue string
-	d := &DraftReviewComment{CommitID: &zeroValue}
-	d.GetCommitID()
-	d = &DraftReviewComment{}
-	d.GetCommitID()
-	d = nil
-	d.GetCommitID()
-}
-
-func TestDraftReviewComment_GetInReplyTo(tt *testing.T) {
-	var zeroValue int64
-	d := &DraftReviewComment{InReplyTo: &zeroValue}
-	d.GetInReplyTo()
-	d = &DraftReviewComment{}
-	d.GetInReplyTo()
-	d = nil
-	d.GetInReplyTo()
-}
-
 func TestDraftReviewComment_GetLine(tt *testing.T) {
 	var zeroValue int
 	d := &DraftReviewComment{Line: &zeroValue}
@@ -7743,16 +7787,6 @@ func TestDraftReviewComment_GetStartSide(tt *testing.T) {
 	d.GetStartSide()
 }
 
-func TestDraftReviewComment_GetSubjectType(tt *testing.T) {
-	var zeroValue string
-	d := &DraftReviewComment{SubjectType: &zeroValue}
-	d.GetSubjectType()
-	d = &DraftReviewComment{}
-	d.GetSubjectType()
-	d = nil
-	d.GetSubjectType()
-}
-
 func TestEditBase_GetRef(tt *testing.T) {
 	e := &EditBase{}
 	e.GetRef()
@@ -16768,6 +16802,90 @@ func TestPagesUpdate_GetSource(tt *testing.T) {
 	p.GetSource()
 }
 
+func TestPersonalAccessToken_GetAccessGrantedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PersonalAccessToken{AccessGrantedAt: &zeroValue}
+	p.GetAccessGrantedAt()
+	p = &PersonalAccessToken{}
+	p.GetAccessGrantedAt()
+	p = nil
+	p.GetAccessGrantedAt()
+}
+
+func TestPersonalAccessToken_GetID(tt *testing.T) {
+	var zeroValue int64
+	p := &PersonalAccessToken{ID: &zeroValue}
+	p.GetID()
+	p = &PersonalAccessToken{}
+	p.GetID()
+	p = nil
+	p.GetID()
+}
+
+func TestPersonalAccessToken_GetOwner(tt *testing.T) {
+	p := &PersonalAccessToken{}
+	p.GetOwner()
+	p = nil
+	p.GetOwner()
+}
+
+func TestPersonalAccessToken_GetPermissions(tt *testing.T) {
+	p := &PersonalAccessToken{}
+	p.GetPermissions()
+	p = nil
+	p.GetPermissions()
+}
+
+func TestPersonalAccessToken_GetRepositoriesURL(tt *testing.T) {
+	var zeroValue string
+	p := &PersonalAccessToken{RepositoriesURL: &zeroValue}
+	p.GetRepositoriesURL()
+	p = &PersonalAccessToken{}
+	p.GetRepositoriesURL()
+	p = nil
+	p.GetRepositoriesURL()
+}
+
+func TestPersonalAccessToken_GetRepositorySelection(tt *testing.T) {
+	var zeroValue string
+	p := &PersonalAccessToken{RepositorySelection: &zeroValue}
+	p.GetRepositorySelection()
+	p = &PersonalAccessToken{}
+	p.GetRepositorySelection()
+	p = nil
+	p.GetRepositorySelection()
+}
+
+func TestPersonalAccessToken_GetTokenExpired(tt *testing.T) {
+	var zeroValue bool
+	p := &PersonalAccessToken{TokenExpired: &zeroValue}
+	p.GetTokenExpired()
+	p = &PersonalAccessToken{}
+	p.GetTokenExpired()
+	p = nil
+	p.GetTokenExpired()
+}
+
+func TestPersonalAccessToken_GetTokenExpiresAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PersonalAccessToken{TokenExpiresAt: &zeroValue}
+	p.GetTokenExpiresAt()
+	p = &PersonalAccessToken{}
+	p.GetTokenExpiresAt()
+	p = nil
+	p.GetTokenExpiresAt()
+}
+
+func TestPersonalAccessToken_GetTokenLastUsedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	p := &PersonalAccessToken{TokenLastUsedAt: &zeroValue}
+	p.GetTokenLastUsedAt()
+	p = &PersonalAccessToken{}
+	p.GetTokenLastUsedAt()
+	p = nil
+	p.GetTokenLastUsedAt()
+}
+
 func TestPersonalAccessTokenPermissions_GetOrg(tt *testing.T) {
 	zeroValue := map[string]string{}
 	p := &PersonalAccessTokenPermissions{Org: zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index c311889c..8633d837 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -388,18 +388,15 @@ func TestDiscussionComment_String(t *testing.T) {
 
 func TestDraftReviewComment_String(t *testing.T) {
 	v := DraftReviewComment{
-		Path:        String(""),
-		Position:    Int(0),
-		Body:        String(""),
-		CommitID:    String(""),
-		InReplyTo:   Int64(0),
-		SubjectType: String(""),
-		StartSide:   String(""),
-		Side:        String(""),
-		StartLine:   Int(0),
-		Line:        Int(0),
-	}
-	want := `github.DraftReviewComment{Path:"", Position:0, Body:"", CommitID:"", InReplyTo:0, SubjectType:"", StartSide:"", Side:"", StartLine:0, Line:0}`
+		Path:      String(""),
+		Position:  Int(0),
+		Body:      String(""),
+		StartSide: String(""),
+		Side:      String(""),
+		StartLine: Int(0),
+		Line:      Int(0),
+	}
+	want := `github.DraftReviewComment{Path:"", Position:0, Body:"", StartSide:"", Side:"", StartLine:0, Line:0}`
 	if got := v.String(); got != want {
 		t.Errorf("DraftReviewComment.String = %v, want %v", got, want)
 	}
diff --git a/github/orgs_custom_roles_test.go b/github/orgs_custom_roles_test.go
index c03b5a6d..338eac19 100644
--- a/github/orgs_custom_roles_test.go
+++ b/github/orgs_custom_roles_test.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/google/go-cmp/cmp"
 )
@@ -20,7 +21,30 @@ func TestOrganizationsService_ListRoles(t *testing.T) {
 
 	mux.HandleFunc("/orgs/o/organization-roles", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		fmt.Fprint(w, `{"total_count": 1, "roles": [{ "id": 1, "name": "Auditor", "permissions": ["read_audit_logs"]}]}`)
+		fmt.Fprint(w, `{"total_count": 1, "roles": [
+			{
+				"id": 1,
+				"name": "Auditor",
+				"permissions": ["read_audit_logs"],
+				"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",
+				"source": "Organization",
+				"base_role": "admin"
+			}
+			]
+		}`)
 	})
 
 	ctx := context.Background()
@@ -29,7 +53,32 @@ func TestOrganizationsService_ListRoles(t *testing.T) {
 		t.Errorf("Organizations.ListRoles returned error: %v", err)
 	}
 
-	want := &OrganizationCustomRoles{TotalCount: Int(1), CustomRepoRoles: []*CustomOrgRoles{{ID: Int64(1), Name: String("Auditor"), Permissions: []string{"read_audit_logs"}}}}
+	want := &OrganizationCustomRoles{
+		TotalCount: Int(1),
+		CustomRepoRoles: []*CustomOrgRoles{
+			{
+				ID:          Int64(1),
+				Name:        String("Auditor"),
+				Permissions: []string{"read_audit_logs"},
+				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)},
+				Source:    String("Organization"),
+				BaseRole:  String("admin"),
+			},
+		},
+	}
 	if !cmp.Equal(apps, want) {
 		t.Errorf("Organizations.ListRoles returned %+v, want %+v", apps, want)
 	}
@@ -169,7 +218,29 @@ func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) {
 
 	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"]}]}`)
+		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()
@@ -178,7 +249,31 @@ func TestOrganizationsService_ListCustomRepoRoles(t *testing.T) {
 		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"}}}}
+	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)
 	}
diff --git a/github/orgs_personal_access_tokens_test.go b/github/orgs_personal_access_tokens_test.go
index 9426385f..83f55023 100644
--- a/github/orgs_personal_access_tokens_test.go
+++ b/github/orgs_personal_access_tokens_test.go
@@ -8,12 +8,153 @@ package github
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/google/go-cmp/cmp"
 )
 
+func TestOrganizationsService_ListFineGrainedPersonalAccessTokens(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/personal-access-tokens", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		expectedQuery := map[string][]string{
+			"per_page":  {"2"},
+			"page":      {"2"},
+			"sort":      {"created_at"},
+			"direction": {"desc"},
+			"owner[]":   {"octocat", "octodog", "otherbot"},
+		}
+
+		query := r.URL.Query()
+		for key, expectedValues := range expectedQuery {
+			actualValues := query[key]
+			if len(actualValues) != len(expectedValues) {
+				t.Errorf("Expected %d values for query param %s, got %d", len(expectedValues), key, len(actualValues))
+			}
+			for i, expectedValue := range expectedValues {
+				if actualValues[i] != expectedValue {
+					t.Errorf("Expected query param %s to be %s, got %s", key, expectedValue, actualValues[i])
+				}
+			}
+		}
+
+		fmt.Fprint(w, `
+		[
+			{
+				"id": 25381,
+				"owner": {
+					"login": "octocat",
+					"id": 1,
+					"node_id": "MDQ6VXNlcjE=",
+					"avatar_url": "https://github.com/images/error/octocat_happy.gif",
+					"gravatar_id": "",
+					"url": "https://api.github.com/users/octocat",
+					"html_url": "https://github.com/octocat",
+					"followers_url": "https://api.github.com/users/octocat/followers",
+					"following_url": "https://api.github.com/users/octocat/following{/other_user}",
+					"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+					"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+					"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+					"organizations_url": "https://api.github.com/users/octocat/orgs",
+					"repos_url": "https://api.github.com/users/octocat/repos",
+					"events_url": "https://api.github.com/users/octocat/events{/privacy}",
+					"received_events_url": "https://api.github.com/users/octocat/received_events",
+					"type": "User",
+					"site_admin": false
+				},
+				"repository_selection": "all",
+				"repositories_url": "https://api.github.com/organizations/652551/personal-access-tokens/25381/repositories",
+				"permissions": {
+					"organization": {
+						"members": "read"
+					},
+					"repository": {
+						"metadata": "read"
+					}
+				},
+				"access_granted_at": "2023-05-16T08:47:09.000-07:00",
+				"token_expired": false,
+				"token_expires_at": "2023-11-16T08:47:09.000-07:00",
+				"token_last_used_at": null
+			}
+		]`)
+	})
+
+	opts := &ListFineGrainedPATOptions{
+		ListOptions: ListOptions{Page: 2, PerPage: 2},
+		Sort:        "created_at",
+		Direction:   "desc",
+		Owner:       []string{"octocat", "octodog", "otherbot"},
+	}
+	ctx := context.Background()
+	tokens, resp, err := client.Organizations.ListFineGrainedPersonalAccessTokens(ctx, "o", opts)
+	if err != nil {
+		t.Errorf("Organizations.ListFineGrainedPersonalAccessTokens returned error: %v", err)
+	}
+
+	want := []*PersonalAccessToken{
+		{
+			ID: Int64(25381),
+			Owner: &User{
+				Login:             String("octocat"),
+				ID:                Int64(1),
+				NodeID:            String("MDQ6VXNlcjE="),
+				AvatarURL:         String("https://github.com/images/error/octocat_happy.gif"),
+				GravatarID:        String(""),
+				URL:               String("https://api.github.com/users/octocat"),
+				HTMLURL:           String("https://github.com/octocat"),
+				FollowersURL:      String("https://api.github.com/users/octocat/followers"),
+				FollowingURL:      String("https://api.github.com/users/octocat/following{/other_user}"),
+				GistsURL:          String("https://api.github.com/users/octocat/gists{/gist_id}"),
+				StarredURL:        String("https://api.github.com/users/octocat/starred{/owner}{/repo}"),
+				SubscriptionsURL:  String("https://api.github.com/users/octocat/subscriptions"),
+				OrganizationsURL:  String("https://api.github.com/users/octocat/orgs"),
+				ReposURL:          String("https://api.github.com/users/octocat/repos"),
+				EventsURL:         String("https://api.github.com/users/octocat/events{/privacy}"),
+				ReceivedEventsURL: String("https://api.github.com/users/octocat/received_events"),
+				Type:              String("User"),
+				SiteAdmin:         Bool(false),
+			},
+			RepositorySelection: String("all"),
+			RepositoriesURL:     String("https://api.github.com/organizations/652551/personal-access-tokens/25381/repositories"),
+			Permissions: &PersonalAccessTokenPermissions{
+				Org:  map[string]string{"members": "read"},
+				Repo: map[string]string{"metadata": "read"},
+			},
+			AccessGrantedAt: &Timestamp{time.Date(2023, time.May, 16, 8, 47, 9, 0, time.FixedZone("PDT", -7*60*60))},
+			TokenExpired:    Bool(false),
+			TokenExpiresAt:  &Timestamp{time.Date(2023, time.November, 16, 8, 47, 9, 0, time.FixedZone("PDT", -7*60*60))},
+			TokenLastUsedAt: nil,
+		},
+	}
+	if !cmp.Equal(tokens, want) {
+		t.Errorf("Organizations.ListFineGrainedPersonalAccessTokens returned %+v, want %+v", tokens, want)
+	}
+
+	if resp == nil {
+		t.Error("Organizations.ListFineGrainedPersonalAccessTokens returned nil response")
+	}
+
+	const methodName = "ListFineGrainedPersonalAccessTokens"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.ListFineGrainedPersonalAccessTokens(ctx, "\n", opts)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.ListFineGrainedPersonalAccessTokens(ctx, "o", opts)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
 func TestOrganizationsService_ReviewPersonalAccessTokenRequest(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/orgs_properties_test.go b/github/orgs_properties_test.go
index 59866fd9..2ef1f453 100644
--- a/github/orgs_properties_test.go
+++ b/github/orgs_properties_test.go
@@ -296,6 +296,14 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
         {
           "property_name": "service",
           "value": "web"
+        },
+        {
+          "property_name": "languages",
+          "value": ["Go", "JavaScript"]
+        },
+        {
+          "property_name": "null_property",
+          "value": null
         }
 		]
         }]`)
@@ -318,11 +326,19 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
 			Properties: []*CustomPropertyValue{
 				{
 					PropertyName: "environment",
-					Value:        String("production"),
+					Value:        "production",
 				},
 				{
 					PropertyName: "service",
-					Value:        String("web"),
+					Value:        "web",
+				},
+				{
+					PropertyName: "languages",
+					Value:        []string{"Go", "JavaScript"},
+				},
+				{
+					PropertyName: "null_property",
+					Value:        nil,
 				},
 			},
 		},
@@ -348,6 +364,78 @@ func TestOrganizationsService_ListCustomPropertyValues(t *testing.T) {
 	})
 }
 
+func TestCustomPropertyValue_UnmarshalJSON(t *testing.T) {
+	tests := map[string]struct {
+		data    string
+		want    *CustomPropertyValue
+		wantErr bool
+	}{
+		"Invalid JSON": {
+			data:    `{`,
+			want:    &CustomPropertyValue{},
+			wantErr: true,
+		},
+		"String value": {
+			data: `{
+				"property_name": "environment",
+				"value": "production"
+			}`,
+			want: &CustomPropertyValue{
+				PropertyName: "environment",
+				Value:        "production",
+			},
+			wantErr: false,
+		},
+		"Array of strings value": {
+			data: `{
+				"property_name": "languages",
+				"value": ["Go", "JavaScript"]
+			}`,
+			want: &CustomPropertyValue{
+				PropertyName: "languages",
+				Value:        []string{"Go", "JavaScript"},
+			},
+			wantErr: false,
+		},
+		"Non-string value in array": {
+			data: `{
+				"property_name": "languages",
+				"value": ["Go", 42]
+			}`,
+			want: &CustomPropertyValue{
+				PropertyName: "languages",
+				Value:        nil,
+			},
+			wantErr: true,
+		},
+		"Unexpected value type": {
+			data: `{
+				"property_name": "environment",
+				"value": {"invalid": "type"}
+			}`,
+			want: &CustomPropertyValue{
+				PropertyName: "environment",
+				Value:        nil,
+			},
+			wantErr: true,
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			cpv := &CustomPropertyValue{}
+			err := cpv.UnmarshalJSON([]byte(tc.data))
+			if (err != nil) != tc.wantErr {
+				t.Errorf("CustomPropertyValue.UnmarshalJSON error = %v, wantErr %v", err, tc.wantErr)
+				return
+			}
+			if !tc.wantErr && !cmp.Equal(tc.want, cpv) {
+				t.Errorf("CustomPropertyValue.UnmarshalJSON expected %+v, got %+v", tc.want, cpv)
+			}
+		})
+	}
+}
+
 func TestOrganizationsService_CreateOrUpdateRepoCustomPropertyValues(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/repos_properties_test.go b/github/repos_properties_test.go
index 5ce05c26..4434856c 100644
--- a/github/repos_properties_test.go
+++ b/github/repos_properties_test.go
@@ -28,6 +28,14 @@ func TestRepositoriesService_GetAllCustomPropertyValues(t *testing.T) {
         {
           "property_name": "service",
           "value": "web"
+        },
+        {
+          "property_name": "languages",
+          "value": ["Go", "JavaScript"]
+        },
+        {
+          "property_name": "null_property",
+          "value": null
         }
 		]`)
 	})
@@ -41,11 +49,19 @@ func TestRepositoriesService_GetAllCustomPropertyValues(t *testing.T) {
 	want := []*CustomPropertyValue{
 		{
 			PropertyName: "environment",
-			Value:        String("production"),
+			Value:        "production",
 		},
 		{
 			PropertyName: "service",
-			Value:        String("web"),
+			Value:        "web",
+		},
+		{
+			PropertyName: "languages",
+			Value:        []string{"Go", "JavaScript"},
+		},
+		{
+			PropertyName: "null_property",
+			Value:        nil,
 		},
 	}
 
@@ -77,7 +93,7 @@ func TestRepositoriesService_CreateOrUpdateCustomProperties(t *testing.T) {
 	RepoCustomProperty := []*CustomPropertyValue{
 		{
 			PropertyName: "environment",
-			Value:        String("production"),
+			Value:        "production",
 		},
 	}
 	_, err := client.Repositories.CreateOrUpdateCustomProperties(ctx, "usr", "r", RepoCustomProperty)
diff --git a/github/teams_test.go b/github/teams_test.go
index 516c88f4..bb4fa4ea 100644
--- a/github/teams_test.go
+++ b/github/teams_test.go
@@ -1302,11 +1302,15 @@ func TestTeamsService_ListIDPGroupsInOrganization(t *testing.T) {
 		testMethod(t, r, "GET")
 		testFormValues(t, r, values{
 			"page": "url-encoded-next-page-token",
+			"q":    "n",
 		})
 		fmt.Fprint(w, `{"groups": [{"group_id": "1",  "group_name": "n", "group_description": "d"}]}`)
 	})
 
-	opt := &ListCursorOptions{Page: "url-encoded-next-page-token"}
+	opt := &ListIDPGroupsOptions{
+		Query:             "n",
+		ListCursorOptions: ListCursorOptions{Page: "url-encoded-next-page-token"},
+	}
 	ctx := context.Background()
 	groups, _, err := client.Teams.ListIDPGroupsInOrganization(ctx, "o", opt)
 	if err != nil {