Repository: google/go-github
Pull request #3467 (2025-02-08 00:38:40)

feat: Add ListProvisionedScimGroupsForEnterprise inside SCIM service with test
This pull request introduces several enhancements to the SCIM (System for Cross-domain Identity Management) functionality in the `github/scim.go` file. The key changes include the addition of new types to represent SCIM group attributes and provisioned groups, as well as a new method to list provisioned SCIM groups for an enterprise. Additionally, a corresponding test function has been added to ensure the correctness of the new method.

### Enhancements to SCIM functionality:

* Added `SCIMGroupAttributes` type to represent supported SCIM group attributes, including `DisplayName`, `Members`, `Schemas`, `ExternalID`, `ID`, and `Meta`. (`[github/scim.goR20-R38](diffhunk://#diff-c2dda2100fd0c700da11a5d96582391b4923658db7e717627d248ae1c029c5fbR20-R38)`)
* Introduced `SCIMProvisionedGroups` type to represent the result of calling `ListSCIMProvisionedIdentities`, including `Schemas`, `TotalResults`, `ItemsPerPage`, `StartIndex`, and `Resources`. (`[github/scim.goR78-R86](diffhunk://#diff-c2dda2100fd0c700da11a5d96582391b4923658db7e717627d248ae1c029c5fbR78-R86)`)
* Added `ListProvisionedScimGroupsForEnterprise` method to list provisioned SCIM groups for an enterprise, including the necessary API documentation reference. (`[github/scim.goR248-R269](diffhunk://#diff-c2dda2100fd0c700da11a5d96582391b4923658db7e717627d248ae1c029c5fbR248-R269)`)

### Testing:

* Added `TestSCIMService_ListSCIMProvisionedGroups` function to test the new `ListProvisionedScimGroupsForEnterprise` method, ensuring it handles responses correctly and validates various scenarios. (`[github/scim_test.goR124-R217](diffhunk://#diff-2723dd506a61e0f7abe849217ded7c4aa5dfef10455f14ed10a423c65f4dac49R124-R217)`)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3467?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 91.04%. Comparing base [(`0ccbfaf`)](https://app.codecov.io/gh/google/go-github/commit/0ccbfafab39fab2446aea0a517d5708a713c79a9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`e790795`)](https://app.codecov.io/gh/google/go-github/commit/e790795ed5e81df7b4b7ac0c1b7f6b8dd1c7fbf4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

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


```diff
@@           Coverage Diff           @@
##           master    #3467   +/-   ##
=======================================
  Coverage   91.03%   91.04%           
=======================================
  Files         179      179           
  Lines       15538    15551   +13     
=======================================
+ Hits        14145    14158   +13     
  Misses       1221     1221           
  Partials      172      172           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/go-github/pull/3467?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).

gmlewis: Please run step 4 of CONTRIBUTING.md and push the results  (`./script/generate.sh`, `./script/lint.sh`, and `./script/test.sh`).
cyberious: @gmlewis updated it. I apologize for not seeing that earlier.
gmlewis: Just FYI - As described in CONTRIBUTING.md, there is no need to force-push in this repo, as we always squash-and-merge so that the commit history is clean. In fact, we discourage it because it makes it more challenging for reviewers to see what changed since their last review.
However, for this tiny PR, it really makes no difference, so no big deal... but just wanted to let you know in case you make larger PRs in the future, which are totally welcome.  :smile: :heart:
ohpintu90: A helpful app...

Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 16859591..6270bc1f 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -23894,6 +23894,46 @@ func (s *ScanningAnalysis) GetWarning() string {
 	return *s.Warning
 }
 
+// GetDisplay returns the Display field if it's non-nil, zero value otherwise.
+func (s *SCIMDisplayReference) GetDisplay() string {
+	if s == nil || s.Display == nil {
+		return ""
+	}
+	return *s.Display
+}
+
+// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise.
+func (s *SCIMGroupAttributes) GetDisplayName() string {
+	if s == nil || s.DisplayName == nil {
+		return ""
+	}
+	return *s.DisplayName
+}
+
+// GetExternalID returns the ExternalID field if it's non-nil, zero value otherwise.
+func (s *SCIMGroupAttributes) GetExternalID() string {
+	if s == nil || s.ExternalID == nil {
+		return ""
+	}
+	return *s.ExternalID
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (s *SCIMGroupAttributes) GetID() string {
+	if s == nil || s.ID == nil {
+		return ""
+	}
+	return *s.ID
+}
+
+// GetMeta returns the Meta field.
+func (s *SCIMGroupAttributes) GetMeta() *SCIMMeta {
+	if s == nil {
+		return nil
+	}
+	return s.Meta
+}
+
 // GetCreated returns the Created field if it's non-nil, zero value otherwise.
 func (s *SCIMMeta) GetCreated() Timestamp {
 	if s == nil || s.Created == nil {
@@ -23926,6 +23966,30 @@ func (s *SCIMMeta) GetResourceType() string {
 	return *s.ResourceType
 }
 
+// GetItemsPerPage returns the ItemsPerPage field if it's non-nil, zero value otherwise.
+func (s *SCIMProvisionedGroups) GetItemsPerPage() int {
+	if s == nil || s.ItemsPerPage == nil {
+		return 0
+	}
+	return *s.ItemsPerPage
+}
+
+// GetStartIndex returns the StartIndex field if it's non-nil, zero value otherwise.
+func (s *SCIMProvisionedGroups) GetStartIndex() int {
+	if s == nil || s.StartIndex == nil {
+		return 0
+	}
+	return *s.StartIndex
+}
+
+// GetTotalResults returns the TotalResults field if it's non-nil, zero value otherwise.
+func (s *SCIMProvisionedGroups) GetTotalResults() int {
+	if s == nil || s.TotalResults == nil {
+		return 0
+	}
+	return *s.TotalResults
+}
+
 // GetItemsPerPage returns the ItemsPerPage field if it's non-nil, zero value otherwise.
 func (s *SCIMProvisionedIdentities) GetItemsPerPage() int {
 	if s == nil || s.ItemsPerPage == nil {
diff --git a/github/scim.go b/github/scim.go
index 4b34c166..70f063ca 100644
--- a/github/scim.go
+++ b/github/scim.go
@@ -17,6 +17,26 @@ import (
 // GitHub API docs: https://docs.github.com/rest/scim
 type SCIMService service
 
+// SCIMGroupAttributes represents supported SCIM Group attributes.
+//
+// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/scim#list-provisioned-scim-groups-for-an-enterprise
+type SCIMGroupAttributes struct {
+	DisplayName *string                 `json:"displayName,omitempty"` // The name of the group, suitable for display to end-users. (Optional.)
+	Members     []*SCIMDisplayReference `json:"members,omitempty"`     // (Optional.)
+	Schemas     []string                `json:"schemas,omitempty"`     // (Optional.)
+	ExternalID  *string                 `json:"externalId,omitempty"`  // (Optional.)
+	// Only populated as a result of calling ListSCIMProvisionedIdentitiesOptions:
+	ID   *string   `json:"id,omitempty"`
+	Meta *SCIMMeta `json:"meta,omitempty"`
+}
+
+// SCIMDisplayReference represents a JSON SCIM (System for Cross-domain Identity Management) resource.
+type SCIMDisplayReference struct {
+	Value   string  `json:"value"`             // (Required.)
+	Ref     string  `json:"$ref"`              // (Required.)
+	Display *string `json:"display,omitempty"` // (Optional.)
+}
+
 // SCIMUserAttributes represents supported SCIM User attributes.
 //
 // GitHub API docs: https://docs.github.com/rest/scim#supported-scim-user-attributes
@@ -56,6 +76,15 @@ type SCIMMeta struct {
 	Location     *string    `json:"location,omitempty"`
 }
 
+// SCIMProvisionedGroups represents the result of calling ListSCIMProvisionedGroupsForEnterprise.
+type SCIMProvisionedGroups struct {
+	Schemas      []string               `json:"schemas,omitempty"`
+	TotalResults *int                   `json:"totalResults,omitempty"`
+	ItemsPerPage *int                   `json:"itemsPerPage,omitempty"`
+	StartIndex   *int                   `json:"startIndex,omitempty"`
+	Resources    []*SCIMGroupAttributes `json:"Resources,omitempty"`
+}
+
 // SCIMProvisionedIdentities represents the result of calling ListSCIMProvisionedIdentities.
 type SCIMProvisionedIdentities struct {
 	Schemas      []string              `json:"schemas,omitempty"`
@@ -217,3 +246,25 @@ func (s *SCIMService) DeleteSCIMUserFromOrg(ctx context.Context, org, scimUserID
 
 	return s.client.Do(ctx, req, nil)
 }
+
+// ListSCIMProvisionedGroupsForEnterprise lists SCIM provisioned groups for an enterprise.
+//
+// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#list-provisioned-scim-groups-for-an-enterprise
+//
+//meta:operation GET /scim/v2/enterprises/{enterprise}/Groups
+func (s *SCIMService) ListSCIMProvisionedGroupsForEnterprise(ctx context.Context, enterprise string, opts *ListSCIMProvisionedIdentitiesOptions) (*SCIMProvisionedGroups, *Response, error) {
+	u := fmt.Sprintf("scim/v2/enterprises/%v/Groups", enterprise)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	groups := new(SCIMProvisionedGroups)
+	resp, err := s.client.Do(ctx, req, groups)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return groups, resp, nil
+}


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index cee65415..b3be0c11 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -30744,6 +30744,58 @@ func TestScanningAnalysis_GetWarning(tt *testing.T) {
 	s.GetWarning()
 }
 
+func TestSCIMDisplayReference_GetDisplay(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	s := &SCIMDisplayReference{Display: &zeroValue}
+	s.GetDisplay()
+	s = &SCIMDisplayReference{}
+	s.GetDisplay()
+	s = nil
+	s.GetDisplay()
+}
+
+func TestSCIMGroupAttributes_GetDisplayName(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	s := &SCIMGroupAttributes{DisplayName: &zeroValue}
+	s.GetDisplayName()
+	s = &SCIMGroupAttributes{}
+	s.GetDisplayName()
+	s = nil
+	s.GetDisplayName()
+}
+
+func TestSCIMGroupAttributes_GetExternalID(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	s := &SCIMGroupAttributes{ExternalID: &zeroValue}
+	s.GetExternalID()
+	s = &SCIMGroupAttributes{}
+	s.GetExternalID()
+	s = nil
+	s.GetExternalID()
+}
+
+func TestSCIMGroupAttributes_GetID(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	s := &SCIMGroupAttributes{ID: &zeroValue}
+	s.GetID()
+	s = &SCIMGroupAttributes{}
+	s.GetID()
+	s = nil
+	s.GetID()
+}
+
+func TestSCIMGroupAttributes_GetMeta(tt *testing.T) {
+	tt.Parallel()
+	s := &SCIMGroupAttributes{}
+	s.GetMeta()
+	s = nil
+	s.GetMeta()
+}
+
 func TestSCIMMeta_GetCreated(tt *testing.T) {
 	tt.Parallel()
 	var zeroValue Timestamp
@@ -30788,6 +30840,39 @@ func TestSCIMMeta_GetResourceType(tt *testing.T) {
 	s.GetResourceType()
 }
 
+func TestSCIMProvisionedGroups_GetItemsPerPage(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue int
+	s := &SCIMProvisionedGroups{ItemsPerPage: &zeroValue}
+	s.GetItemsPerPage()
+	s = &SCIMProvisionedGroups{}
+	s.GetItemsPerPage()
+	s = nil
+	s.GetItemsPerPage()
+}
+
+func TestSCIMProvisionedGroups_GetStartIndex(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue int
+	s := &SCIMProvisionedGroups{StartIndex: &zeroValue}
+	s.GetStartIndex()
+	s = &SCIMProvisionedGroups{}
+	s.GetStartIndex()
+	s = nil
+	s.GetStartIndex()
+}
+
+func TestSCIMProvisionedGroups_GetTotalResults(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue int
+	s := &SCIMProvisionedGroups{TotalResults: &zeroValue}
+	s.GetTotalResults()
+	s = &SCIMProvisionedGroups{}
+	s.GetTotalResults()
+	s = nil
+	s.GetTotalResults()
+}
+
 func TestSCIMProvisionedIdentities_GetItemsPerPage(tt *testing.T) {
 	tt.Parallel()
 	var zeroValue int
diff --git a/github/scim_test.go b/github/scim_test.go
index a6c806cb..42a6f233 100644
--- a/github/scim_test.go
+++ b/github/scim_test.go
@@ -121,6 +121,100 @@ func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) {
 	})
 }
 
+func TestSCIMService_ListSCIMProvisionedGroups(t *testing.T) {
+	t.Parallel()
+	client, mux, _ := setup(t)
+
+	mux.HandleFunc("/scim/v2/enterprises/o/Groups", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(`{
+			"schemas": [
+			  "urn:ietf:params:scim:api:messages:2.0:ListResponse"
+			],
+			"totalResults": 1,
+			"itemsPerPage": 1,
+			"startIndex": 1,
+			"Resources": [
+			  {
+				"schemas": [
+				  "urn:ietf:params:scim:schemas:core:2.0:Group"
+				],
+				"id": "123e4567-e89b-12d3-a456-426614174000",
+				"externalId": "00u1dhhb1fkIGP7RL1d8",
+				"displayName": "Mona Octocat",
+				"meta": {
+				  "resourceType": "Group",
+				  "created": "2018-02-13T15:05:24.000-00:00",
+				  "lastModified": "2018-02-13T15:05:24.000-00:00",
+				  "location": "https://api.github.com/scim/v2/enterprises/octo/Groups/123e4567-e89b-12d3-a456-426614174000"
+				},
+				"members": [
+				  {
+					"value": "5fc0c238-1112-11e8-8e45-920c87bdbd75",
+					"$ref": "https://api.github.com/scim/v2/enterprises/octo/Users/5fc0c238-1112-11e8-8e45-920c87bdbd75",
+					"display": "Mona Octocat"
+				  }
+				]
+			  }
+			]
+		  }`))
+	})
+
+	ctx := context.Background()
+	opts := &ListSCIMProvisionedIdentitiesOptions{}
+	groups, _, err := client.SCIM.ListSCIMProvisionedGroupsForEnterprise(ctx, "o", opts)
+	if err != nil {
+		t.Errorf("SCIM.ListSCIMProvisionedIdentities returned error: %v", err)
+	}
+
+	date := Timestamp{time.Date(2018, time.February, 13, 15, 5, 24, 0, time.UTC)}
+	want := SCIMProvisionedGroups{
+		Schemas:      []string{"urn:ietf:params:scim:api:messages:2.0:ListResponse"},
+		TotalResults: Ptr(1),
+		ItemsPerPage: Ptr(1),
+		StartIndex:   Ptr(1),
+		Resources: []*SCIMGroupAttributes{
+			{
+				ID: Ptr("123e4567-e89b-12d3-a456-426614174000"),
+				Meta: &SCIMMeta{
+					ResourceType: Ptr("Group"),
+					Created:      &date,
+					LastModified: &date,
+					Location:     Ptr("https://api.github.com/scim/v2/enterprises/octo/Groups/123e4567-e89b-12d3-a456-426614174000"),
+				},
+
+				DisplayName: Ptr("Mona Octocat"),
+				Schemas:     []string{"urn:ietf:params:scim:schemas:core:2.0:Group"},
+				ExternalID:  Ptr("00u1dhhb1fkIGP7RL1d8"),
+				Members: []*SCIMDisplayReference{
+					{
+						Value:   "5fc0c238-1112-11e8-8e45-920c87bdbd75",
+						Ref:     "https://api.github.com/scim/v2/enterprises/octo/Users/5fc0c238-1112-11e8-8e45-920c87bdbd75",
+						Display: Ptr("Mona Octocat"),
+					},
+				},
+			},
+		},
+	}
+
+	if !cmp.Equal(groups, &want) {
+		diff := cmp.Diff(groups, want)
+		t.Errorf("SCIM.ListSCIMProvisionedGroupsForEnterprise returned %+v, want %+v: diff %+v", groups, want, diff)
+	}
+
+	const methodName = "ListSCIMProvisionedGroupsForEnterprise"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.SCIM.ListSCIMProvisionedGroupsForEnterprise(ctx, "\n", opts)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		_, r, err := client.SCIM.ListSCIMProvisionedGroupsForEnterprise(ctx, "o", opts)
+		return r, err
+	})
+}
+
 func TestSCIMService_ProvisionAndInviteSCIMUser(t *testing.T) {
 	t.Parallel()
 	client, mux, _ := setup(t)