Repository: google/go-github
Pull request #2474 (2022-09-21 13:35:14)

Return proper responses for SCIM provisioned identities
This PR adds return responses to `ListSCIMProvisionedIdentities` and `GetSCIMProvisioningInfoForUser`, as before it was not possible to get the output of those calls.
Fixes https://github.com/google/go-github/issues/2338

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/2474?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#2474](https://codecov.io/gh/google/go-github/pull/2474?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (7018cf3) into [master](https://codecov.io/gh/google/go-github/commit/182338c7045a79434039ac77f1a104cb7d0d433b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (182338c) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master    #2474   +/-   ##
=======================================
  Coverage   98.03%   98.03%           
=======================================
  Files         122      122           
  Lines       10695    10705   +10     
=======================================
+ Hits        10485    10495   +10     
  Misses        144      144           
  Partials       66       66           
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/2474?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/scim.go](https://codecov.io/gh/google/go-github/pull/2474/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL3NjaW0uZ28=) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

gmlewis: Thank you, @wesleimp !
Merging.

Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 7b670cb9..0c87a56e 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -16582,6 +16582,62 @@ func (s *ScanningAnalysis) GetWarning() string {
 	return *s.Warning
 }
 
+// GetCreated returns the Created field if it's non-nil, zero value otherwise.
+func (s *SCIMMeta) GetCreated() Timestamp {
+	if s == nil || s.Created == nil {
+		return Timestamp{}
+	}
+	return *s.Created
+}
+
+// GetLastModified returns the LastModified field if it's non-nil, zero value otherwise.
+func (s *SCIMMeta) GetLastModified() Timestamp {
+	if s == nil || s.LastModified == nil {
+		return Timestamp{}
+	}
+	return *s.LastModified
+}
+
+// GetLocation returns the Location field if it's non-nil, zero value otherwise.
+func (s *SCIMMeta) GetLocation() string {
+	if s == nil || s.Location == nil {
+		return ""
+	}
+	return *s.Location
+}
+
+// GetResourceType returns the ResourceType field if it's non-nil, zero value otherwise.
+func (s *SCIMMeta) GetResourceType() string {
+	if s == nil || s.ResourceType == nil {
+		return ""
+	}
+	return *s.ResourceType
+}
+
+// GetItemsPerPage returns the ItemsPerPage field if it's non-nil, zero value otherwise.
+func (s *SCIMProvisionedIdentities) 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 *SCIMProvisionedIdentities) 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 *SCIMProvisionedIdentities) GetTotalResults() int {
+	if s == nil || s.TotalResults == nil {
+		return 0
+	}
+	return *s.TotalResults
+}
+
 // GetActive returns the Active field if it's non-nil, zero value otherwise.
 func (s *SCIMUserAttributes) GetActive() bool {
 	if s == nil || s.Active == nil {
@@ -16606,6 +16662,22 @@ func (s *SCIMUserAttributes) GetExternalID() string {
 	return *s.ExternalID
 }
 
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (s *SCIMUserAttributes) GetID() string {
+	if s == nil || s.ID == nil {
+		return ""
+	}
+	return *s.ID
+}
+
+// GetMeta returns the Meta field.
+func (s *SCIMUserAttributes) GetMeta() *SCIMMeta {
+	if s == nil {
+		return nil
+	}
+	return s.Meta
+}
+
 // GetPrimary returns the Primary field if it's non-nil, zero value otherwise.
 func (s *SCIMUserEmail) GetPrimary() bool {
 	if s == nil || s.Primary == nil {
diff --git a/github/scim.go b/github/scim.go
index 9462fb6b..70f81ed1 100644
--- a/github/scim.go
+++ b/github/scim.go
@@ -29,6 +29,9 @@ type SCIMUserAttributes struct {
 	ExternalID  *string          `json:"externalId,omitempty"`  // (Optional.)
 	Groups      []string         `json:"groups,omitempty"`      // (Optional.)
 	Active      *bool            `json:"active,omitempty"`      // (Optional.)
+	// Only populated as a result of calling ListSCIMProvisionedIdentitiesOptions or GetSCIMProvisioningInfoForUser:
+	ID   *string   `json:"id,omitempty"`
+	Meta *SCIMMeta `json:"meta,omitempty"`
 }
 
 // SCIMUserName represents SCIM user information.
@@ -45,6 +48,23 @@ type SCIMUserEmail struct {
 	Type    *string `json:"type,omitempty"`    // (Optional.)
 }
 
+// SCIMMeta represents metadata about the SCIM resource.
+type SCIMMeta struct {
+	ResourceType *string    `json:"resourceType,omitempty"`
+	Created      *Timestamp `json:"created,omitempty"`
+	LastModified *Timestamp `json:"lastModified,omitempty"`
+	Location     *string    `json:"location,omitempty"`
+}
+
+// SCIMProvisionedIdentities represents the result of calling ListSCIMProvisionedIdentities.
+type SCIMProvisionedIdentities struct {
+	Schemas      []string              `json:"schemas,omitempty"`
+	TotalResults *int                  `json:"totalResults,omitempty"`
+	ItemsPerPage *int                  `json:"itemsPerPage,omitempty"`
+	StartIndex   *int                  `json:"startIndex,omitempty"`
+	Resources    []*SCIMUserAttributes `json:"Resources,omitempty"`
+}
+
 // ListSCIMProvisionedIdentitiesOptions represents options for ListSCIMProvisionedIdentities.
 //
 // Github API docs: https://docs.github.com/en/rest/scim#list-scim-provisioned-identities--parameters
@@ -62,17 +82,25 @@ type ListSCIMProvisionedIdentitiesOptions struct {
 // ListSCIMProvisionedIdentities lists SCIM provisioned identities.
 //
 // GitHub API docs: https://docs.github.com/en/rest/scim#list-scim-provisioned-identities
-func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org string, opts *ListSCIMProvisionedIdentitiesOptions) (*Response, error) {
+func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org string, opts *ListSCIMProvisionedIdentitiesOptions) (*SCIMProvisionedIdentities, *Response, error) {
 	u := fmt.Sprintf("scim/v2/organizations/%v/Users", org)
 	u, err := addOptions(u, opts)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
+
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	return s.client.Do(ctx, req, nil)
+
+	identities := new(SCIMProvisionedIdentities)
+	resp, err := s.client.Do(ctx, req, identities)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return identities, resp, nil
 }
 
 // ProvisionAndInviteSCIMUser provisions organization membership for a user, and sends an activation email to the email address.
@@ -84,23 +112,32 @@ func (s *SCIMService) ProvisionAndInviteSCIMUser(ctx context.Context, org string
 	if err != nil {
 		return nil, err
 	}
+
 	req, err := s.client.NewRequest("POST", u, nil)
 	if err != nil {
 		return nil, err
 	}
+
 	return s.client.Do(ctx, req, nil)
 }
 
 // GetSCIMProvisioningInfoForUser returns SCIM provisioning information for a user.
 //
 // GitHub API docs: https://docs.github.com/en/rest/scim#supported-scim-user-attributes
-func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, scimUserID string) (*Response, error) {
+func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, scimUserID string) (*SCIMUserAttributes, *Response, error) {
 	u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID)
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	return s.client.Do(ctx, req, nil)
+
+	user := new(SCIMUserAttributes)
+	resp, err := s.client.Do(ctx, req, &user)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return user, resp, nil
 }
 
 // UpdateProvisionedOrgMembership updates a provisioned organization membership.
@@ -112,10 +149,12 @@ func (s *SCIMService) UpdateProvisionedOrgMembership(ctx context.Context, org, s
 	if err != nil {
 		return nil, err
 	}
+
 	req, err := s.client.NewRequest("PUT", u, nil)
 	if err != nil {
 		return nil, err
 	}
+
 	return s.client.Do(ctx, req, nil)
 }
 
@@ -143,10 +182,12 @@ func (s *SCIMService) UpdateAttributeForSCIMUser(ctx context.Context, org, scimU
 	if err != nil {
 		return nil, err
 	}
+
 	req, err := s.client.NewRequest("PATCH", u, nil)
 	if err != nil {
 		return nil, err
 	}
+
 	return s.client.Do(ctx, req, nil)
 }
 
@@ -159,5 +200,6 @@ func (s *SCIMService) DeleteSCIMUserFromOrg(ctx context.Context, org, scimUserID
 	if err != nil {
 		return nil, err
 	}
+
 	return s.client.Do(ctx, req, nil)
 }


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index e51ccba7..086db726 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -19351,6 +19351,76 @@ func TestScanningAnalysis_GetWarning(tt *testing.T) {
 	s.GetWarning()
 }
 
+func TestSCIMMeta_GetCreated(tt *testing.T) {
+	var zeroValue Timestamp
+	s := &SCIMMeta{Created: &zeroValue}
+	s.GetCreated()
+	s = &SCIMMeta{}
+	s.GetCreated()
+	s = nil
+	s.GetCreated()
+}
+
+func TestSCIMMeta_GetLastModified(tt *testing.T) {
+	var zeroValue Timestamp
+	s := &SCIMMeta{LastModified: &zeroValue}
+	s.GetLastModified()
+	s = &SCIMMeta{}
+	s.GetLastModified()
+	s = nil
+	s.GetLastModified()
+}
+
+func TestSCIMMeta_GetLocation(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMMeta{Location: &zeroValue}
+	s.GetLocation()
+	s = &SCIMMeta{}
+	s.GetLocation()
+	s = nil
+	s.GetLocation()
+}
+
+func TestSCIMMeta_GetResourceType(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMMeta{ResourceType: &zeroValue}
+	s.GetResourceType()
+	s = &SCIMMeta{}
+	s.GetResourceType()
+	s = nil
+	s.GetResourceType()
+}
+
+func TestSCIMProvisionedIdentities_GetItemsPerPage(tt *testing.T) {
+	var zeroValue int
+	s := &SCIMProvisionedIdentities{ItemsPerPage: &zeroValue}
+	s.GetItemsPerPage()
+	s = &SCIMProvisionedIdentities{}
+	s.GetItemsPerPage()
+	s = nil
+	s.GetItemsPerPage()
+}
+
+func TestSCIMProvisionedIdentities_GetStartIndex(tt *testing.T) {
+	var zeroValue int
+	s := &SCIMProvisionedIdentities{StartIndex: &zeroValue}
+	s.GetStartIndex()
+	s = &SCIMProvisionedIdentities{}
+	s.GetStartIndex()
+	s = nil
+	s.GetStartIndex()
+}
+
+func TestSCIMProvisionedIdentities_GetTotalResults(tt *testing.T) {
+	var zeroValue int
+	s := &SCIMProvisionedIdentities{TotalResults: &zeroValue}
+	s.GetTotalResults()
+	s = &SCIMProvisionedIdentities{}
+	s.GetTotalResults()
+	s = nil
+	s.GetTotalResults()
+}
+
 func TestSCIMUserAttributes_GetActive(tt *testing.T) {
 	var zeroValue bool
 	s := &SCIMUserAttributes{Active: &zeroValue}
@@ -19381,6 +19451,23 @@ func TestSCIMUserAttributes_GetExternalID(tt *testing.T) {
 	s.GetExternalID()
 }
 
+func TestSCIMUserAttributes_GetID(tt *testing.T) {
+	var zeroValue string
+	s := &SCIMUserAttributes{ID: &zeroValue}
+	s.GetID()
+	s = &SCIMUserAttributes{}
+	s.GetID()
+	s = nil
+	s.GetID()
+}
+
+func TestSCIMUserAttributes_GetMeta(tt *testing.T) {
+	s := &SCIMUserAttributes{}
+	s.GetMeta()
+	s = nil
+	s.GetMeta()
+}
+
 func TestSCIMUserEmail_GetPrimary(tt *testing.T) {
 	var zeroValue bool
 	s := &SCIMUserEmail{Primary: &zeroValue}
diff --git a/github/scim_test.go b/github/scim_test.go
index 328bc209..7c6e1407 100644
--- a/github/scim_test.go
+++ b/github/scim_test.go
@@ -9,6 +9,9 @@ import (
 	"context"
 	"net/http"
 	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
 )
 
 func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) {
@@ -18,23 +21,102 @@ func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) {
 	mux.HandleFunc("/scim/v2/organizations/o/Users", 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:User"
+				],
+				"id": "5fc0c238-1112-11e8-8e45-920c87bdbd75",
+				"externalId": "00u1dhhb1fkIGP7RL1d8",
+				"userName": "octocat@github.com",
+				"displayName": "Mona Octocat",
+				"name": {
+				  "givenName": "Mona",
+				  "familyName": "Octocat",
+				  "formatted": "Mona Octocat"
+				},
+				"emails": [
+				  {
+					"value": "octocat@github.com",
+					"primary": true
+				  }
+				],
+				"active": true,
+				"meta": {
+				  "resourceType": "User",
+				  "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/organizations/octo-org/Users/5fc0c238-1112-11e8-8e45-920c87bdbd75"
+				}
+			  }
+			]
+		  }`))
 	})
 
 	ctx := context.Background()
 	opts := &ListSCIMProvisionedIdentitiesOptions{}
-	_, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts)
+	identities, _, err := client.SCIM.ListSCIMProvisionedIdentities(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 := SCIMProvisionedIdentities{
+		Schemas:      []string{"urn:ietf:params:scim:api:messages:2.0:ListResponse"},
+		TotalResults: Int(1),
+		ItemsPerPage: Int(1),
+		StartIndex:   Int(1),
+		Resources: []*SCIMUserAttributes{
+			{
+				ID: String("5fc0c238-1112-11e8-8e45-920c87bdbd75"),
+				Meta: &SCIMMeta{
+					ResourceType: String("User"),
+					Created:      &date,
+					LastModified: &date,
+					Location:     String("https://api.github.com/scim/v2/organizations/octo-org/Users/5fc0c238-1112-11e8-8e45-920c87bdbd75"),
+				},
+				UserName: "octocat@github.com",
+				Name: SCIMUserName{
+					GivenName:  "Mona",
+					FamilyName: "Octocat",
+					Formatted:  String("Mona Octocat"),
+				},
+				DisplayName: String("Mona Octocat"),
+				Emails: []*SCIMUserEmail{
+					{
+						Value:   "octocat@github.com",
+						Primary: Bool(true),
+					},
+				},
+				Schemas:    []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
+				ExternalID: String("00u1dhhb1fkIGP7RL1d8"),
+				Groups:     nil,
+				Active:     Bool(true),
+			},
+		},
+	}
+
+	if !cmp.Equal(identities, &want) {
+		diff := cmp.Diff(identities, want)
+		t.Errorf("SCIM.ListSCIMProvisionedIdentities returned %+v, want %+v: diff %+v", identities, want, diff)
+	}
+
 	const methodName = "ListSCIMProvisionedIdentities"
 	testBadOptions(t, methodName, func() (err error) {
-		_, err = client.SCIM.ListSCIMProvisionedIdentities(ctx, "\n", opts)
+		_, _, err = client.SCIM.ListSCIMProvisionedIdentities(ctx, "\n", opts)
 		return err
 	})
 
 	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		return client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts)
+		_, r, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts)
+		return r, err
 	})
 }
 
@@ -83,22 +165,89 @@ func TestSCIMService_GetSCIMProvisioningInfoForUser(t *testing.T) {
 	mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(`{
+			"schemas": [
+			  "urn:ietf:params:scim:schemas:core:2.0:User"
+			],
+			"id": "edefdfedf-050c-11e7-8d32",
+			"externalId": "a7d0f98382",
+			"userName": "mona.octocat@okta.example.com",
+			"displayName": "Mona Octocat",
+			"name": {
+			  "givenName": "Mona",
+			  "familyName": "Octocat",
+			  "formatted": "Mona Octocat"
+			},
+			"emails": [
+			  {
+				"value": "mona.octocat@okta.example.com",
+				"primary": true
+			  },
+			  {
+				"value": "mona@octocat.github.com"
+			  }
+			],
+			"active": true,
+			"meta": {
+			  "resourceType": "User",
+			  "created": "2017-03-09T16:11:13-00:00",
+			  "lastModified": "2017-03-09T16:11:13-00:00",
+			  "location": "https://api.github.com/scim/v2/organizations/octo-org/Users/edefdfedf-050c-11e7-8d32"
+			}
+		  }`))
 	})
 
 	ctx := context.Background()
-	_, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123")
+	user, _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123")
 	if err != nil {
 		t.Errorf("SCIM.GetSCIMProvisioningInfoForUser returned error: %v", err)
 	}
 
+	date := Timestamp{time.Date(2017, time.March, 9, 16, 11, 13, 0, time.UTC)}
+	want := SCIMUserAttributes{
+		ID: String("edefdfedf-050c-11e7-8d32"),
+		Meta: &SCIMMeta{
+			ResourceType: String("User"),
+			Created:      &date,
+			LastModified: &date,
+			Location:     String("https://api.github.com/scim/v2/organizations/octo-org/Users/edefdfedf-050c-11e7-8d32"),
+		},
+		UserName: "mona.octocat@okta.example.com",
+		Name: SCIMUserName{
+			GivenName:  "Mona",
+			FamilyName: "Octocat",
+			Formatted:  String("Mona Octocat"),
+		},
+		DisplayName: String("Mona Octocat"),
+		Emails: []*SCIMUserEmail{
+			{
+				Value:   "mona.octocat@okta.example.com",
+				Primary: Bool(true),
+			},
+			{
+				Value: "mona@octocat.github.com",
+			},
+		},
+		Schemas:    []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
+		ExternalID: String("a7d0f98382"),
+		Groups:     nil,
+		Active:     Bool(true),
+	}
+
+	if !cmp.Equal(user, &want) {
+		diff := cmp.Diff(user, want)
+		t.Errorf("SCIM.ListSCIMProvisionedIdentities returned %+v, want %+v: diff %+v", user, want, diff)
+	}
+
 	const methodName = "GetSCIMProvisioningInfoForUser"
 	testBadOptions(t, methodName, func() error {
-		_, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "\n", "123")
+		_, _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "\n", "123")
 		return err
 	})
 
 	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		return client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123")
+		_, r, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123")
+		return r, err
 	})
 }