Repository: google/go-github
Pull request #3529 (2025-03-22 11:39:30)

fix: Isolate HTTP transports in parallel tests to prevent connection issues
## Summary  

This PR addresses an issue where parallel tests share the default HTTP transport, leading to intermittent "connection broken" errors due to race conditions in connection reuse.  

## Root Cause  

Tests create their own clients and servers via `setup(t)`, but they unintentionally share Go's default transport when no explicit transport is specified. As a result:  
- `server.Close()` triggers `CloseIdleConnections()` on the default transport.  
- This closes all idle connections, affecting other tests running in parallel.  
- If another test is in the middle of using a connection from the shared pool, it encounters a "connection broken" error.  

## Solution  

- Ensure each test uses an **isolated HTTP transport** instead of the shared default transport.  
- This prevents unintended connection closures from affecting other tests running concurrently.  
- Improves test stability by eliminating race conditions in connection reuse.  

## References  

- Related discussion: https://github.com/google/go-github/issues/3527


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3529?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.28%. Comparing base [(`49e644b`)](https://app.codecov.io/gh/google/go-github/commit/49e644be5a24ce199ac98aa576b04afe8d4db95d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`5082ea1`)](https://app.codecov.io/gh/google/go-github/commit/5082ea1afebabff1530ea375c5b9ee6bdbee1acb?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    #3529      +/-   ##
==========================================
- Coverage   91.30%   91.28%   -0.02%     
==========================================
  Files         184      184              
  Lines       16143    16143              
==========================================
- Hits        14739    14736       -3     
- Misses       1230     1231       +1     
- Partials      174      176       +2     
```

</details>

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

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 4aa0bec2..0a632ac3 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -9,7 +9,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
-    - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+    - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
       with:
         go-version: 1.x
         cache-dependency-path: "**/go.sum"
diff --git a/github/meta.go b/github/meta.go
index cc90b618..69235356 100644
--- a/github/meta.go
+++ b/github/meta.go
@@ -52,6 +52,10 @@ type APIMeta struct {
 	// GitHub Actions will originate from.
 	Actions []string `json:"actions,omitempty"`
 
+	// An array of IP addresses in CIDR format specifying the IP addresses
+	// GitHub Action macOS runner will originate from.
+	ActionsMacos []string `json:"actions_macos,omitempty"`
+
 	// An array of IP addresses in CIDR format specifying the IP addresses
 	// Dependabot will originate from.
 	Dependabot []string `json:"dependabot,omitempty"`
diff --git a/github/reactions.go b/github/reactions.go
index e9823220..5233f78d 100644
--- a/github/reactions.go
+++ b/github/reactions.go
@@ -48,9 +48,8 @@ func (r Reaction) String() string {
 	return Stringify(r)
 }
 
-// ListCommentReactionOptions specifies the optional parameters to the
-// ReactionsService.ListCommentReactions method.
-type ListCommentReactionOptions struct {
+// ListReactionOptions specifies the optional parameters to the list reactions endpoints.
+type ListReactionOptions struct {
 	// Content restricts the returned comment reactions to only those with the given type.
 	// Omit this parameter to list all reactions to a commit comment.
 	// Possible values are: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
@@ -64,7 +63,7 @@ type ListCommentReactionOptions struct {
 // GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-a-commit-comment
 //
 //meta:operation GET /repos/{owner}/{repo}/comments/{comment_id}/reactions
-func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListCommentReactionOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -144,7 +143,7 @@ func (s *ReactionsService) DeleteCommentReactionByID(ctx context.Context, repoID
 // GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-an-issue
 //
 //meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/reactions
-func (s *ReactionsService) ListIssueReactions(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListIssueReactions(ctx context.Context, owner, repo string, number int, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -224,7 +223,7 @@ func (s *ReactionsService) DeleteIssueReactionByID(ctx context.Context, repoID,
 // GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-an-issue-comment
 //
 //meta:operation GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions
-func (s *ReactionsService) ListIssueCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListIssueCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -304,7 +303,7 @@ func (s *ReactionsService) DeleteIssueCommentReactionByID(ctx context.Context, r
 // GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-a-pull-request-review-comment
 //
 //meta:operation GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions
-func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -384,7 +383,7 @@ func (s *ReactionsService) DeletePullRequestCommentReactionByID(ctx context.Cont
 // GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-a-team-discussion-legacy
 //
 //meta:operation GET /teams/{team_id}/discussions/{discussion_number}/reactions
-func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opts *ListOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -460,7 +459,7 @@ func (s *ReactionsService) DeleteTeamDiscussionReactionByOrgIDAndTeamID(ctx cont
 // GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-a-team-discussion-comment-legacy
 //
 //meta:operation GET /teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}/reactions
-func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opts *ListOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber)
 	u, err := addOptions(u, opts)
 	if err != nil {
@@ -569,3 +568,54 @@ func (s *ReactionsService) CreateReleaseReaction(ctx context.Context, owner, rep
 
 	return m, resp, nil
 }
+
+// ListReleaseReactions lists the reactions for a release.
+//
+// GitHub API docs: https://docs.github.com/rest/reactions/reactions#list-reactions-for-a-release
+//
+//meta:operation GET /repos/{owner}/{repo}/releases/{release_id}/reactions
+func (s *ReactionsService) ListReleaseReactions(ctx context.Context, owner, repo string, releaseID int64, opts *ListReactionOptions) ([]*Reaction, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/releases/%v/reactions", owner, repo, releaseID)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: remove custom Accept headers when APIs fully launch.
+	req.Header.Set("Accept", mediaTypeReactionsPreview)
+
+	var m []*Reaction
+	resp, err := s.client.Do(ctx, req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, nil
+}
+
+// DeleteReleaseReaction deletes the reaction for a release.
+//
+// GitHub API docs: https://docs.github.com/rest/reactions/reactions#delete-a-release-reaction
+//
+//meta:operation DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}
+func (s *ReactionsService) DeleteReleaseReaction(ctx context.Context, owner, repo string, releaseID, reactionID int64) (*Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/releases/%v/reactions/%v", owner, repo, releaseID, reactionID)
+
+	return s.deleteReaction(ctx, u)
+}
+
+// DeleteReleaseReactionByID deletes the reaction for a release by repository ID.
+//
+// GitHub API docs: https://docs.github.com/rest/reactions/reactions#delete-a-release-reaction
+//
+//meta:operation DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}
+func (s *ReactionsService) DeleteReleaseReactionByID(ctx context.Context, repoID, releaseID, reactionID int64) (*Response, error) {
+	u := fmt.Sprintf("repositories/%v/releases/%v/reactions/%v", repoID, releaseID, reactionID)
+
+	return s.deleteReaction(ctx, u)
+}
diff --git a/tools/go.mod b/tools/go.mod
index 826d918a..71b1540c 100644
--- a/tools/go.mod
+++ b/tools/go.mod
@@ -4,7 +4,7 @@ go 1.23.0
 
 require (
 	github.com/alecthomas/kong v1.9.0
-	github.com/getkin/kin-openapi v0.130.0
+	github.com/getkin/kin-openapi v0.131.0
 	github.com/google/go-cmp v0.7.0
 	github.com/google/go-github/v70 v70.0.0
 	golang.org/x/sync v0.12.0
diff --git a/tools/go.sum b/tools/go.sum
index 7d64c2b8..5cdc8ecb 100644
--- a/tools/go.sum
+++ b/tools/go.sum
@@ -6,8 +6,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/getkin/kin-openapi v0.130.0 h1:Sz8GTHfscqdsQCT/OJDSV3eNvEjZ8iUOlXbFxkG1Av0=
-github.com/getkin/kin-openapi v0.130.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
+github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE=
+github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
 github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
 github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 71b1cd8b..86fdd739 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -42,7 +42,7 @@ jobs:
     runs-on: ${{ matrix.platform }}
 
     steps:
-      - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
+      - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
         with:
           go-version: ${{ matrix.go-version }}
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -54,7 +54,7 @@ jobs:
           echo "go-mod-cache=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
 
       - name: Cache go modules
-        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
+        uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
         with:
           path: |
             ${{ steps.cache-paths.outputs.go-cache }}
diff --git a/github/github_test.go b/github/github_test.go
index 46245ff7..dd028215 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -57,9 +57,29 @@ func setup(t *testing.T) (client *Client, mux *http.ServeMux, serverURL string)
 	// server is a test HTTP server used to provide mock API responses.
 	server := httptest.NewServer(apiHandler)
 
+	// Create a custom transport with isolated connection pool
+	transport := &http.Transport{
+		// Controls connection reuse - false allows reuse, true forces new connections for each request
+		DisableKeepAlives: false,
+		// Maximum concurrent connections per host (active + idle)
+		MaxConnsPerHost: 10,
+		// Maximum idle connections maintained per host for reuse
+		MaxIdleConnsPerHost: 5,
+		// Maximum total idle connections across all hosts
+		MaxIdleConns: 20,
+		// How long an idle connection remains in the pool before being closed
+		IdleConnTimeout: 20 * time.Second,
+	}
+
+	// Create HTTP client with the isolated transport
+	httpClient := &http.Client{
+		Transport: transport,
+		Timeout:   30 * time.Second,
+	}
 	// client is the GitHub client being tested and is
 	// configured to use test server.
-	client = NewClient(nil)
+	client = NewClient(httpClient)
+
 	url, _ := url.Parse(server.URL + baseURLPath + "/")
 	client.BaseURL = url
 	client.UploadURL = url
diff --git a/github/meta_test.go b/github/meta_test.go
index 71eb2149..ffe701f0 100644
--- a/github/meta_test.go
+++ b/github/meta_test.go
@@ -26,6 +26,7 @@ func TestAPIMeta_Marshal(t *testing.T) {
 		Importer:                         []string{"i"},
 		GithubEnterpriseImporter:         []string{"gei"},
 		Actions:                          []string{"a"},
+		ActionsMacos:                     []string{"192.0.2.1/32", "198.51.100.0/24"},
 		Dependabot:                       []string{"d"},
 		SSHKeyFingerprints:               map[string]string{"a": "f"},
 		SSHKeys:                          []string{"k"},
@@ -58,6 +59,7 @@ func TestAPIMeta_Marshal(t *testing.T) {
 		"importer":["i"],
 		"github_enterprise_importer":["gei"],
 		"actions":["a"],
+    "actions_macos":["192.0.2.1/32", "198.51.100.0/24"],
 		"dependabot":["d"],
 		"ssh_key_fingerprints":{"a":"f"},
 		"ssh_keys":["k"],
@@ -75,7 +77,7 @@ func TestMetaService_Get(t *testing.T) {
 
 	mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		fmt.Fprint(w, `{"web":["w"],"api":["a"],"hooks":["h"], "git":["g"], "pages":["p"], "importer":["i"], "github_enterprise_importer": ["gei"], "actions":["a"], "dependabot":["d"], "verifiable_password_authentication": true, "domains":{"website":["*.github.com","*.github.dev","*.github.io","*.githubassets.com","*.githubusercontent.com"],"artifact_attestations":{"trust_domain":"","services":["*.actions.githubusercontent.com","tuf-repo.github.com","fulcio.githubapp.com","timestamp.githubapp.com"]}}}`)
+		fmt.Fprint(w, `{"web":["w"],"api":["a"],"hooks":["h"], "git":["g"], "pages":["p"], "importer":["i"], "github_enterprise_importer": ["gei"], "actions":["a"], "actions_macos": ["192.0.2.1/32", "198.51.100.0/24"], "dependabot":["d"], "verifiable_password_authentication": true, "domains":{"website":["*.github.com","*.github.dev","*.github.io","*.githubassets.com","*.githubusercontent.com"],"artifact_attestations":{"trust_domain":"","services":["*.actions.githubusercontent.com","tuf-repo.github.com","fulcio.githubapp.com","timestamp.githubapp.com"]}}}`)
 	})
 
 	ctx := context.Background()
@@ -91,6 +93,7 @@ func TestMetaService_Get(t *testing.T) {
 		Importer:                 []string{"i"},
 		GithubEnterpriseImporter: []string{"gei"},
 		Actions:                  []string{"a"},
+		ActionsMacos:             []string{"192.0.2.1/32", "198.51.100.0/24"},
 		Dependabot:               []string{"d"},
 		API:                      []string{"a"},
 		Web:                      []string{"w"},
diff --git a/github/reactions_test.go b/github/reactions_test.go
index 5758a705..7ac5e363 100644
--- a/github/reactions_test.go
+++ b/github/reactions_test.go
@@ -79,7 +79,7 @@ func TestReactionsService_ListCommentReactions(t *testing.T) {
 		fmt.Fprint(w, `[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)
 	})
 
-	opt := &ListCommentReactionOptions{Content: "+1"}
+	opt := &ListReactionOptions{Content: "+1"}
 	ctx := context.Background()
 	reactions, _, err := client.Reactions.ListCommentReactions(ctx, "o", "r", 1, opt)
 	if err != nil {
@@ -149,13 +149,15 @@ func TestReactionsService_ListIssueReactions(t *testing.T) {
 	mux.HandleFunc("/repos/o/r/issues/1/reactions", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+		testFormValues(t, r, values{"content": "+1"})
 
 		w.WriteHeader(http.StatusOK)
 		assertWrite(t, w, []byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
 	})
 
+	opt := &ListReactionOptions{Content: "+1"}
 	ctx := context.Background()
-	got, _, err := client.Reactions.ListIssueReactions(ctx, "o", "r", 1, nil)
+	got, _, err := client.Reactions.ListIssueReactions(ctx, "o", "r", 1, opt)
 	if err != nil {
 		t.Errorf("ListIssueReactions returned error: %v", err)
 	}
@@ -173,7 +175,7 @@ func TestReactionsService_ListIssueReactions_coverage(t *testing.T) {
 
 	const methodName = "ListIssueReactions"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Reactions.ListIssueReactions(ctx, "\n", "\n", -1, &ListOptions{})
+		_, _, err = client.Reactions.ListIssueReactions(ctx, "\n", "\n", -1, &ListReactionOptions{})
 		return err
 	})
 
@@ -230,13 +232,15 @@ func TestReactionsService_ListIssueCommentReactions(t *testing.T) {
 	mux.HandleFunc("/repos/o/r/issues/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+		testFormValues(t, r, values{"content": "+1"})
 
 		w.WriteHeader(http.StatusOK)
 		assertWrite(t, w, []byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
 	})
 
+	opt := &ListReactionOptions{Content: "+1"}
 	ctx := context.Background()
-	got, _, err := client.Reactions.ListIssueCommentReactions(ctx, "o", "r", 1, nil)
+	got, _, err := client.Reactions.ListIssueCommentReactions(ctx, "o", "r", 1, opt)
 	if err != nil {
 		t.Errorf("ListIssueCommentReactions returned error: %v", err)
 	}
@@ -254,7 +258,7 @@ func TestReactionsService_ListIssueCommentReactions_coverage(t *testing.T) {
 
 	const methodName = "ListIssueCommentReactions"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Reactions.ListIssueCommentReactions(ctx, "\n", "\n", -1, &ListOptions{})
+		_, _, err = client.Reactions.ListIssueCommentReactions(ctx, "\n", "\n", -1, &ListReactionOptions{})
 		return err
 	})
 
@@ -311,13 +315,15 @@ func TestReactionsService_ListPullRequestCommentReactions(t *testing.T) {
 	mux.HandleFunc("/repos/o/r/pulls/comments/1/reactions", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+		testFormValues(t, r, values{"content": "+1"})
 
 		w.WriteHeader(http.StatusOK)
 		assertWrite(t, w, []byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
 	})
 
+	opt := &ListReactionOptions{Content: "+1"}
 	ctx := context.Background()
-	got, _, err := client.Reactions.ListPullRequestCommentReactions(ctx, "o", "r", 1, nil)
+	got, _, err := client.Reactions.ListPullRequestCommentReactions(ctx, "o", "r", 1, opt)
 	if err != nil {
 		t.Errorf("ListPullRequestCommentReactions returned error: %v", err)
 	}
@@ -335,7 +341,7 @@ func TestReactionsService_ListPullRequestCommentReactions_coverage(t *testing.T)
 
 	const methodName = "ListPullRequestCommentReactions"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Reactions.ListPullRequestCommentReactions(ctx, "\n", "\n", -1, &ListOptions{})
+		_, _, err = client.Reactions.ListPullRequestCommentReactions(ctx, "\n", "\n", -1, &ListReactionOptions{})
 		return err
 	})
 
@@ -392,13 +398,15 @@ func TestReactionsService_ListTeamDiscussionReactions(t *testing.T) {
 	mux.HandleFunc("/teams/1/discussions/2/reactions", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+		testFormValues(t, r, values{"content": "+1"})
 
 		w.WriteHeader(http.StatusOK)
 		assertWrite(t, w, []byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
 	})
 
+	opt := &ListReactionOptions{Content: "+1"}
 	ctx := context.Background()
-	got, _, err := client.Reactions.ListTeamDiscussionReactions(ctx, 1, 2, nil)
+	got, _, err := client.Reactions.ListTeamDiscussionReactions(ctx, 1, 2, opt)
 	if err != nil {
 		t.Errorf("ListTeamDiscussionReactions returned error: %v", err)
 	}
@@ -416,7 +424,7 @@ func TestReactionsService_ListTeamDiscussionReactions_coverage(t *testing.T) {
 
 	const methodName = "ListTeamDiscussionReactions"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Reactions.ListTeamDiscussionReactions(ctx, -1, -2, &ListOptions{})
+		_, _, err = client.Reactions.ListTeamDiscussionReactions(ctx, -1, -2, &ListReactionOptions{})
 		return err
 	})
 
@@ -473,13 +481,15 @@ func TestReactionService_ListTeamDiscussionCommentReactions(t *testing.T) {
 	mux.HandleFunc("/teams/1/discussions/2/comments/3/reactions", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+		testFormValues(t, r, values{"content": "+1"})
 
 		w.WriteHeader(http.StatusOK)
 		assertWrite(t, w, []byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
 	})
 
+	opt := &ListReactionOptions{Content: "+1"}
 	ctx := context.Background()
-	got, _, err := client.Reactions.ListTeamDiscussionCommentReactions(ctx, 1, 2, 3, nil)
+	got, _, err := client.Reactions.ListTeamDiscussionCommentReactions(ctx, 1, 2, 3, opt)
 	if err != nil {
 		t.Errorf("ListTeamDiscussionCommentReactions returned error: %v", err)
 	}
@@ -497,7 +507,7 @@ func TestReactionService_ListTeamDiscussionCommentReactions_coverage(t *testing.
 
 	const methodName = "ListTeamDiscussionCommentReactions"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Reactions.ListTeamDiscussionCommentReactions(ctx, -1, -2, -3, &ListOptions{})
+		_, _, err = client.Reactions.ListTeamDiscussionCommentReactions(ctx, -1, -2, -3, &ListReactionOptions{})
 		return err
 	})
 
@@ -908,3 +918,103 @@ func TestReactionService_CreateReleaseReaction(t *testing.T) {
 		return resp, err
 	})
 }
+
+func TestReactionsService_ListReleaseReactions(t *testing.T) {
+	t.Parallel()
+	client, mux, _ := setup(t)
+
+	mux.HandleFunc("/repos/o/r/releases/1/reactions", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+		testFormValues(t, r, values{"content": "+1"})
+
+		w.WriteHeader(http.StatusOK)
+		assertWrite(t, w, []byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
+	})
+
+	opt := &ListReactionOptions{Content: "+1"}
+	ctx := context.Background()
+	got, _, err := client.Reactions.ListReleaseReactions(ctx, "o", "r", 1, opt)
+	if err != nil {
+		t.Errorf("ListReleaseReactions returned error: %v", err)
+	}
+	want := []*Reaction{{ID: Ptr(int64(1)), User: &User{Login: Ptr("l"), ID: Ptr(int64(2))}, Content: Ptr("+1")}}
+	if !cmp.Equal(got, want) {
+		t.Errorf("ListReleaseReactions = %+v, want %+v", got, want)
+	}
+}
+
+func TestReactionsService_ListReleaseReactions_coverage(t *testing.T) {
+	t.Parallel()
+	client, _, _ := setup(t)
+
+	ctx := context.Background()
+
+	const methodName = "ListReleaseReactions"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Reactions.ListReleaseReactions(ctx, "\n", "\n", -1, &ListReactionOptions{})
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Reactions.ListReleaseReactions(ctx, "o", "r", 1, nil)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestReactionsService_DeleteReleaseReaction(t *testing.T) {
+	t.Parallel()
+	client, mux, _ := setup(t)
+
+	mux.HandleFunc("/repos/o/r/releases/1/reactions/2", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	if _, err := client.Reactions.DeleteReleaseReaction(ctx, "o", "r", 1, 2); err != nil {
+		t.Errorf("DeleteReleaseReaction returned error: %v", err)
+	}
+
+	const methodName = "DeleteReleaseReaction"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Reactions.DeleteReleaseReaction(ctx, "\n", "\n", -1, -2)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Reactions.DeleteReleaseReaction(ctx, "o", "r", 1, 2)
+	})
+}
+
+func TestReactionsService_DeleteReleaseReactionByRepoID(t *testing.T) {
+	t.Parallel()
+	client, mux, _ := setup(t)
+
+	mux.HandleFunc("/repositories/1/releases/2/reactions/3", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	ctx := context.Background()
+	if _, err := client.Reactions.DeleteReleaseReactionByID(ctx, 1, 2, 3); err != nil {
+		t.Errorf("DeleteReleaseReactionByRepoID returned error: %v", err)
+	}
+
+	const methodName = "DeleteReleaseReactionByID"
+	testBadOptions(t, methodName, func() (err error) {
+		_, err = client.Reactions.DeleteIssueReactionByID(ctx, -1, -2, -3)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		return client.Reactions.DeleteIssueReactionByID(ctx, 1, 2, 3)
+	})
+}