Repository: google/go-github
Pull request #3263 (2024-09-13 15:09:33)

Add CancelInvite method to cancel an org invitation by ID
The https://docs.github.com/en/rest/orgs/members?apiVersion=2022-11-28#cancel-an-organization-invitation endpoint was not available, and I needed it for some tooling. 

I tried my best to stick to the standards, and added a quick test. 
Happy to update / change anything if required. 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3263?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.94%. 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 [(`8bbf034`)](https://app.codecov.io/gh/google/go-github/commit/8bbf034bc70833beba9f7c1bc103973db460cf8a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> Report is 108 commits behind head on master.

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


```diff
@@            Coverage Diff             @@
##           master    #3263      +/-   ##
==========================================
- Coverage   97.72%   92.94%   -4.78%     
==========================================
  Files         153      171      +18     
  Lines       13390    11669    -1721     
==========================================
- Hits        13085    10846    -2239     
- Misses        215      729     +514     
- Partials       90       94       +4     
```

</details>

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

DocEmmetBrown: > Please fix the errors locally by reading step 4 in CONTRIBUTING.md and sign the CLA, then we can move forward with this PR.

I ran the scripts, and everything should be OK now.
Regarding the CLA, I'm waiting for a response from within my company, I should have it early next week, and will sign it then. 
DocEmmetBrown: @gmlewis : CCLA has finally been signed for the whole `aiven.io` organization, but the action doesn't seem to find it. Anything I can do to unblock the situation ? 
gmlewis: > Anything I can do to unblock the situation ?

It looks like 3 emails were found for this PR.  You need to make sure that only the email that is part of the signed CLA is on this PR.

If you click on the "Details" link, it says this:

> The following contributors were found for this pull request:
> 
> ❌ https://github.com/google/go-github/commit/8bad333f3c53759c50a9ca07d1ed524f21b895cb Author:  @DocEmmetBrown <gu.....me​@winter.digital>, <guil........nter​@aiven.io>, <DocEmmetBrown​ @users.noreply.github.com>
> 
> (Only the first commit for a unique contributor is listed.)

So you can either fix all the commits and force-push to this PR,
or you can simply close this PR and create a new one with only the one email address that has signed the CLA.
DocEmmetBrown: After a lot of internal shenanigans, it looks like the CLA approval is now being properly reported 🥳 
@gmlewis: feel free to merge if everything else is OK for you 

Patch:
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/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/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()