github.com/google/go-github/v68@v68.0.0/github/repos_releases_test.go (about)

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/google/go-cmp/cmp"
    19  )
    20  
    21  func TestRepositoriesService_ListReleases(t *testing.T) {
    22  	t.Parallel()
    23  	client, mux, _ := setup(t)
    24  
    25  	mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) {
    26  		testMethod(t, r, "GET")
    27  		testFormValues(t, r, values{"page": "2"})
    28  		fmt.Fprint(w, `[{"id":1}]`)
    29  	})
    30  
    31  	opt := &ListOptions{Page: 2}
    32  	ctx := context.Background()
    33  	releases, _, err := client.Repositories.ListReleases(ctx, "o", "r", opt)
    34  	if err != nil {
    35  		t.Errorf("Repositories.ListReleases returned error: %v", err)
    36  	}
    37  	want := []*RepositoryRelease{{ID: Ptr(int64(1))}}
    38  	if !cmp.Equal(releases, want) {
    39  		t.Errorf("Repositories.ListReleases returned %+v, want %+v", releases, want)
    40  	}
    41  
    42  	const methodName = "ListReleases"
    43  	testBadOptions(t, methodName, func() (err error) {
    44  		_, _, err = client.Repositories.ListReleases(ctx, "\n", "\n", opt)
    45  		return err
    46  	})
    47  
    48  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
    49  		got, resp, err := client.Repositories.ListReleases(ctx, "o", "r", opt)
    50  		if got != nil {
    51  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
    52  		}
    53  		return resp, err
    54  	})
    55  }
    56  
    57  func TestRepositoriesService_GenerateReleaseNotes(t *testing.T) {
    58  	t.Parallel()
    59  	client, mux, _ := setup(t)
    60  
    61  	mux.HandleFunc("/repos/o/r/releases/generate-notes", func(w http.ResponseWriter, r *http.Request) {
    62  		testMethod(t, r, "POST")
    63  		testBody(t, r, `{"tag_name":"v1.0.0"}`+"\n")
    64  		fmt.Fprint(w, `{"name":"v1.0.0","body":"**Full Changelog**: https://github.com/o/r/compare/v0.9.0...v1.0.0"}`)
    65  	})
    66  
    67  	opt := &GenerateNotesOptions{
    68  		TagName: "v1.0.0",
    69  	}
    70  	ctx := context.Background()
    71  	releases, _, err := client.Repositories.GenerateReleaseNotes(ctx, "o", "r", opt)
    72  	if err != nil {
    73  		t.Errorf("Repositories.GenerateReleaseNotes returned error: %v", err)
    74  	}
    75  	want := &RepositoryReleaseNotes{
    76  		Name: "v1.0.0",
    77  		Body: "**Full Changelog**: https://github.com/o/r/compare/v0.9.0...v1.0.0",
    78  	}
    79  	if !cmp.Equal(releases, want) {
    80  		t.Errorf("Repositories.GenerateReleaseNotes returned %+v, want %+v", releases, want)
    81  	}
    82  
    83  	const methodName = "GenerateReleaseNotes"
    84  	testBadOptions(t, methodName, func() (err error) {
    85  		_, _, err = client.Repositories.GenerateReleaseNotes(ctx, "\n", "\n", opt)
    86  		return err
    87  	})
    88  
    89  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
    90  		got, resp, err := client.Repositories.GenerateReleaseNotes(ctx, "o", "r", opt)
    91  		if got != nil {
    92  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
    93  		}
    94  		return resp, err
    95  	})
    96  }
    97  
    98  func TestRepositoriesService_GetRelease(t *testing.T) {
    99  	t.Parallel()
   100  	client, mux, _ := setup(t)
   101  
   102  	mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) {
   103  		testMethod(t, r, "GET")
   104  		fmt.Fprint(w, `{"id":1,"author":{"login":"l"}}`)
   105  	})
   106  
   107  	ctx := context.Background()
   108  	release, resp, err := client.Repositories.GetRelease(ctx, "o", "r", 1)
   109  	if err != nil {
   110  		t.Errorf("Repositories.GetRelease returned error: %v\n%v", err, resp.Body)
   111  	}
   112  
   113  	want := &RepositoryRelease{ID: Ptr(int64(1)), Author: &User{Login: Ptr("l")}}
   114  	if !cmp.Equal(release, want) {
   115  		t.Errorf("Repositories.GetRelease returned %+v, want %+v", release, want)
   116  	}
   117  
   118  	const methodName = "GetRelease"
   119  	testBadOptions(t, methodName, func() (err error) {
   120  		_, _, err = client.Repositories.GetRelease(ctx, "\n", "\n", 1)
   121  		return err
   122  	})
   123  
   124  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   125  		got, resp, err := client.Repositories.GetRelease(ctx, "o", "r", 1)
   126  		if got != nil {
   127  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   128  		}
   129  		return resp, err
   130  	})
   131  }
   132  
   133  func TestRepositoriesService_GetLatestRelease(t *testing.T) {
   134  	t.Parallel()
   135  	client, mux, _ := setup(t)
   136  
   137  	mux.HandleFunc("/repos/o/r/releases/latest", func(w http.ResponseWriter, r *http.Request) {
   138  		testMethod(t, r, "GET")
   139  		fmt.Fprint(w, `{"id":3}`)
   140  	})
   141  
   142  	ctx := context.Background()
   143  	release, resp, err := client.Repositories.GetLatestRelease(ctx, "o", "r")
   144  	if err != nil {
   145  		t.Errorf("Repositories.GetLatestRelease returned error: %v\n%v", err, resp.Body)
   146  	}
   147  
   148  	want := &RepositoryRelease{ID: Ptr(int64(3))}
   149  	if !cmp.Equal(release, want) {
   150  		t.Errorf("Repositories.GetLatestRelease returned %+v, want %+v", release, want)
   151  	}
   152  
   153  	const methodName = "GetLatestRelease"
   154  	testBadOptions(t, methodName, func() (err error) {
   155  		_, _, err = client.Repositories.GetLatestRelease(ctx, "\n", "\n")
   156  		return err
   157  	})
   158  
   159  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   160  		got, resp, err := client.Repositories.GetLatestRelease(ctx, "o", "r")
   161  		if got != nil {
   162  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   163  		}
   164  		return resp, err
   165  	})
   166  }
   167  
   168  func TestRepositoriesService_GetReleaseByTag(t *testing.T) {
   169  	t.Parallel()
   170  	client, mux, _ := setup(t)
   171  
   172  	mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) {
   173  		testMethod(t, r, "GET")
   174  		fmt.Fprint(w, `{"id":13}`)
   175  	})
   176  
   177  	ctx := context.Background()
   178  	release, resp, err := client.Repositories.GetReleaseByTag(ctx, "o", "r", "foo")
   179  	if err != nil {
   180  		t.Errorf("Repositories.GetReleaseByTag returned error: %v\n%v", err, resp.Body)
   181  	}
   182  
   183  	want := &RepositoryRelease{ID: Ptr(int64(13))}
   184  	if !cmp.Equal(release, want) {
   185  		t.Errorf("Repositories.GetReleaseByTag returned %+v, want %+v", release, want)
   186  	}
   187  
   188  	const methodName = "GetReleaseByTag"
   189  	testBadOptions(t, methodName, func() (err error) {
   190  		_, _, err = client.Repositories.GetReleaseByTag(ctx, "\n", "\n", "foo")
   191  		return err
   192  	})
   193  
   194  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   195  		got, resp, err := client.Repositories.GetReleaseByTag(ctx, "o", "r", "foo")
   196  		if got != nil {
   197  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   198  		}
   199  		return resp, err
   200  	})
   201  }
   202  
   203  func TestRepositoriesService_CreateRelease(t *testing.T) {
   204  	t.Parallel()
   205  	client, mux, _ := setup(t)
   206  
   207  	input := &RepositoryRelease{
   208  		Name:                   Ptr("v1.0"),
   209  		DiscussionCategoryName: Ptr("General"),
   210  		GenerateReleaseNotes:   Ptr(true),
   211  		// Fields to be removed:
   212  		ID:          Ptr(int64(2)),
   213  		CreatedAt:   &Timestamp{referenceTime},
   214  		PublishedAt: &Timestamp{referenceTime},
   215  		URL:         Ptr("http://url/"),
   216  		HTMLURL:     Ptr("http://htmlurl/"),
   217  		AssetsURL:   Ptr("http://assetsurl/"),
   218  		Assets:      []*ReleaseAsset{{ID: Ptr(int64(5))}},
   219  		UploadURL:   Ptr("http://uploadurl/"),
   220  		ZipballURL:  Ptr("http://zipballurl/"),
   221  		TarballURL:  Ptr("http://tarballurl/"),
   222  		Author:      &User{Name: Ptr("octocat")},
   223  		NodeID:      Ptr("nodeid"),
   224  	}
   225  
   226  	mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) {
   227  		v := new(repositoryReleaseRequest)
   228  		assertNilError(t, json.NewDecoder(r.Body).Decode(v))
   229  
   230  		testMethod(t, r, "POST")
   231  		want := &repositoryReleaseRequest{
   232  			Name:                   Ptr("v1.0"),
   233  			DiscussionCategoryName: Ptr("General"),
   234  			GenerateReleaseNotes:   Ptr(true),
   235  		}
   236  		if !cmp.Equal(v, want) {
   237  			t.Errorf("Request body = %+v, want %+v", v, want)
   238  		}
   239  		fmt.Fprint(w, `{"id":1}`)
   240  	})
   241  
   242  	ctx := context.Background()
   243  	release, _, err := client.Repositories.CreateRelease(ctx, "o", "r", input)
   244  	if err != nil {
   245  		t.Errorf("Repositories.CreateRelease returned error: %v", err)
   246  	}
   247  
   248  	want := &RepositoryRelease{ID: Ptr(int64(1))}
   249  	if !cmp.Equal(release, want) {
   250  		t.Errorf("Repositories.CreateRelease returned %+v, want %+v", release, want)
   251  	}
   252  
   253  	const methodName = "CreateRelease"
   254  	testBadOptions(t, methodName, func() (err error) {
   255  		_, _, err = client.Repositories.CreateRelease(ctx, "\n", "\n", input)
   256  		return err
   257  	})
   258  
   259  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   260  		got, resp, err := client.Repositories.CreateRelease(ctx, "o", "r", input)
   261  		if got != nil {
   262  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   263  		}
   264  		return resp, err
   265  	})
   266  }
   267  
   268  func TestRepositoriesService_EditRelease(t *testing.T) {
   269  	t.Parallel()
   270  	client, mux, _ := setup(t)
   271  
   272  	input := &RepositoryRelease{
   273  		Name:                   Ptr("n"),
   274  		DiscussionCategoryName: Ptr("General"),
   275  		// Fields to be removed:
   276  		GenerateReleaseNotes: Ptr(true),
   277  		ID:                   Ptr(int64(2)),
   278  		CreatedAt:            &Timestamp{referenceTime},
   279  		PublishedAt:          &Timestamp{referenceTime},
   280  		URL:                  Ptr("http://url/"),
   281  		HTMLURL:              Ptr("http://htmlurl/"),
   282  		AssetsURL:            Ptr("http://assetsurl/"),
   283  		Assets:               []*ReleaseAsset{{ID: Ptr(int64(5))}},
   284  		UploadURL:            Ptr("http://uploadurl/"),
   285  		ZipballURL:           Ptr("http://zipballurl/"),
   286  		TarballURL:           Ptr("http://tarballurl/"),
   287  		Author:               &User{Name: Ptr("octocat")},
   288  		NodeID:               Ptr("nodeid"),
   289  	}
   290  
   291  	mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) {
   292  		v := new(repositoryReleaseRequest)
   293  		assertNilError(t, json.NewDecoder(r.Body).Decode(v))
   294  
   295  		testMethod(t, r, "PATCH")
   296  		want := &repositoryReleaseRequest{
   297  			Name:                   Ptr("n"),
   298  			DiscussionCategoryName: Ptr("General"),
   299  		}
   300  		if !cmp.Equal(v, want) {
   301  			t.Errorf("Request body = %+v, want %+v", v, want)
   302  		}
   303  		fmt.Fprint(w, `{"id":1}`)
   304  	})
   305  
   306  	ctx := context.Background()
   307  	release, _, err := client.Repositories.EditRelease(ctx, "o", "r", 1, input)
   308  	if err != nil {
   309  		t.Errorf("Repositories.EditRelease returned error: %v", err)
   310  	}
   311  	want := &RepositoryRelease{ID: Ptr(int64(1))}
   312  	if !cmp.Equal(release, want) {
   313  		t.Errorf("Repositories.EditRelease returned = %+v, want %+v", release, want)
   314  	}
   315  
   316  	const methodName = "EditRelease"
   317  	testBadOptions(t, methodName, func() (err error) {
   318  		_, _, err = client.Repositories.EditRelease(ctx, "\n", "\n", 1, input)
   319  		return err
   320  	})
   321  
   322  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   323  		got, resp, err := client.Repositories.EditRelease(ctx, "o", "r", 1, input)
   324  		if got != nil {
   325  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   326  		}
   327  		return resp, err
   328  	})
   329  }
   330  
   331  func TestRepositoriesService_DeleteRelease(t *testing.T) {
   332  	t.Parallel()
   333  	client, mux, _ := setup(t)
   334  
   335  	mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) {
   336  		testMethod(t, r, "DELETE")
   337  	})
   338  
   339  	ctx := context.Background()
   340  	_, err := client.Repositories.DeleteRelease(ctx, "o", "r", 1)
   341  	if err != nil {
   342  		t.Errorf("Repositories.DeleteRelease returned error: %v", err)
   343  	}
   344  
   345  	const methodName = "DeleteRelease"
   346  	testBadOptions(t, methodName, func() (err error) {
   347  		_, err = client.Repositories.DeleteRelease(ctx, "\n", "\n", 1)
   348  		return err
   349  	})
   350  
   351  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   352  		return client.Repositories.DeleteRelease(ctx, "o", "r", 1)
   353  	})
   354  }
   355  
   356  func TestRepositoriesService_ListReleaseAssets(t *testing.T) {
   357  	t.Parallel()
   358  	client, mux, _ := setup(t)
   359  
   360  	mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) {
   361  		testMethod(t, r, "GET")
   362  		testFormValues(t, r, values{"page": "2"})
   363  		fmt.Fprint(w, `[{"id":1}]`)
   364  	})
   365  
   366  	opt := &ListOptions{Page: 2}
   367  	ctx := context.Background()
   368  	assets, _, err := client.Repositories.ListReleaseAssets(ctx, "o", "r", 1, opt)
   369  	if err != nil {
   370  		t.Errorf("Repositories.ListReleaseAssets returned error: %v", err)
   371  	}
   372  	want := []*ReleaseAsset{{ID: Ptr(int64(1))}}
   373  	if !cmp.Equal(assets, want) {
   374  		t.Errorf("Repositories.ListReleaseAssets returned %+v, want %+v", assets, want)
   375  	}
   376  
   377  	const methodName = "ListReleaseAssets"
   378  	testBadOptions(t, methodName, func() (err error) {
   379  		_, _, err = client.Repositories.ListReleaseAssets(ctx, "\n", "\n", 1, opt)
   380  		return err
   381  	})
   382  
   383  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   384  		got, resp, err := client.Repositories.ListReleaseAssets(ctx, "o", "r", 1, opt)
   385  		if got != nil {
   386  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   387  		}
   388  		return resp, err
   389  	})
   390  }
   391  
   392  func TestRepositoriesService_GetReleaseAsset(t *testing.T) {
   393  	t.Parallel()
   394  	client, mux, _ := setup(t)
   395  
   396  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   397  		testMethod(t, r, "GET")
   398  		fmt.Fprint(w, `{"id":1}`)
   399  	})
   400  
   401  	ctx := context.Background()
   402  	asset, _, err := client.Repositories.GetReleaseAsset(ctx, "o", "r", 1)
   403  	if err != nil {
   404  		t.Errorf("Repositories.GetReleaseAsset returned error: %v", err)
   405  	}
   406  	want := &ReleaseAsset{ID: Ptr(int64(1))}
   407  	if !cmp.Equal(asset, want) {
   408  		t.Errorf("Repositories.GetReleaseAsset returned %+v, want %+v", asset, want)
   409  	}
   410  
   411  	const methodName = "GetReleaseAsset"
   412  	testBadOptions(t, methodName, func() (err error) {
   413  		_, _, err = client.Repositories.GetReleaseAsset(ctx, "\n", "\n", 1)
   414  		return err
   415  	})
   416  
   417  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   418  		got, resp, err := client.Repositories.GetReleaseAsset(ctx, "o", "r", 1)
   419  		if got != nil {
   420  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   421  		}
   422  		return resp, err
   423  	})
   424  }
   425  
   426  func TestRepositoriesService_DownloadReleaseAsset_Stream(t *testing.T) {
   427  	t.Parallel()
   428  	client, mux, _ := setup(t)
   429  
   430  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   431  		testMethod(t, r, "GET")
   432  		testHeader(t, r, "Accept", defaultMediaType)
   433  		w.Header().Set("Content-Type", "application/octet-stream")
   434  		w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
   435  		fmt.Fprint(w, "Hello World")
   436  	})
   437  
   438  	ctx := context.Background()
   439  	reader, _, err := client.Repositories.DownloadReleaseAsset(ctx, "o", "r", 1, nil)
   440  	if err != nil {
   441  		t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err)
   442  	}
   443  	want := []byte("Hello World")
   444  	content, err := io.ReadAll(reader)
   445  	if err != nil {
   446  		t.Errorf("Repositories.DownloadReleaseAsset returned bad reader: %v", err)
   447  	}
   448  	if !bytes.Equal(want, content) {
   449  		t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want)
   450  	}
   451  
   452  	const methodName = "DownloadReleaseAsset"
   453  	testBadOptions(t, methodName, func() (err error) {
   454  		_, _, err = client.Repositories.DownloadReleaseAsset(ctx, "\n", "\n", -1, nil)
   455  		return err
   456  	})
   457  }
   458  
   459  func TestRepositoriesService_DownloadReleaseAsset_Redirect(t *testing.T) {
   460  	t.Parallel()
   461  	client, mux, _ := setup(t)
   462  
   463  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   464  		testMethod(t, r, "GET")
   465  		testHeader(t, r, "Accept", defaultMediaType)
   466  		http.Redirect(w, r, "/yo", http.StatusFound)
   467  	})
   468  
   469  	ctx := context.Background()
   470  	_, got, err := client.Repositories.DownloadReleaseAsset(ctx, "o", "r", 1, nil)
   471  	if err != nil {
   472  		t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err)
   473  	}
   474  	want := "/yo"
   475  	if !strings.HasSuffix(got, want) {
   476  		t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", got, want)
   477  	}
   478  }
   479  
   480  func TestRepositoriesService_DownloadReleaseAsset_FollowRedirect(t *testing.T) {
   481  	t.Parallel()
   482  	client, mux, _ := setup(t)
   483  
   484  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   485  		testMethod(t, r, "GET")
   486  		testHeader(t, r, "Accept", defaultMediaType)
   487  		// /yo, below will be served as baseURLPath/yo
   488  		http.Redirect(w, r, baseURLPath+"/yo", http.StatusFound)
   489  	})
   490  	mux.HandleFunc("/yo", func(w http.ResponseWriter, r *http.Request) {
   491  		testMethod(t, r, "GET")
   492  		testHeader(t, r, "Accept", defaultMediaType)
   493  		w.Header().Set("Content-Type", "application/octet-stream")
   494  		w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
   495  		fmt.Fprint(w, "Hello World")
   496  	})
   497  
   498  	ctx := context.Background()
   499  	reader, _, err := client.Repositories.DownloadReleaseAsset(ctx, "o", "r", 1, http.DefaultClient)
   500  	if err != nil {
   501  		t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err)
   502  	}
   503  	content, err := io.ReadAll(reader)
   504  	if err != nil {
   505  		t.Errorf("Reading Repositories.DownloadReleaseAsset returned error: %v", err)
   506  	}
   507  	reader.Close()
   508  	want := []byte("Hello World")
   509  	if !bytes.Equal(want, content) {
   510  		t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want)
   511  	}
   512  }
   513  
   514  func TestRepositoriesService_DownloadReleaseAsset_FollowMultipleRedirects(t *testing.T) {
   515  	t.Parallel()
   516  	client, mux, _ := setup(t)
   517  
   518  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   519  		w.Header().Set("Content-Type", "application/json; charset=utf-8")
   520  		testMethod(t, r, "GET")
   521  		testHeader(t, r, "Accept", defaultMediaType)
   522  		// /yo, below will be served as baseURLPath/yo
   523  		http.Redirect(w, r, baseURLPath+"/yo", http.StatusMovedPermanently)
   524  	})
   525  	mux.HandleFunc("/yo", func(w http.ResponseWriter, r *http.Request) {
   526  		w.Header().Set("Content-Type", "text/html;charset=utf-8")
   527  		testMethod(t, r, "GET")
   528  		testHeader(t, r, "Accept", defaultMediaType)
   529  		// /yo2, below will be served as baseURLPath/yo2
   530  		http.Redirect(w, r, baseURLPath+"/yo2", http.StatusFound)
   531  	})
   532  	mux.HandleFunc("/yo2", func(w http.ResponseWriter, r *http.Request) {
   533  		w.Header().Set("Content-Type", "application/octet-stream")
   534  		w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
   535  		testMethod(t, r, "GET")
   536  		testHeader(t, r, "Accept", defaultMediaType)
   537  		fmt.Fprint(w, "Hello World")
   538  	})
   539  
   540  	ctx := context.Background()
   541  	reader, _, err := client.Repositories.DownloadReleaseAsset(ctx, "o", "r", 1, http.DefaultClient)
   542  	if err != nil {
   543  		t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err)
   544  	}
   545  	content, err := io.ReadAll(reader)
   546  	if err != nil {
   547  		t.Errorf("Reading Repositories.DownloadReleaseAsset returned error: %v", err)
   548  	}
   549  	reader.Close()
   550  	want := []byte("Hello World")
   551  	if !bytes.Equal(want, content) {
   552  		t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want)
   553  	}
   554  }
   555  
   556  func TestRepositoriesService_DownloadReleaseAsset_FollowRedirectToError(t *testing.T) {
   557  	t.Parallel()
   558  	client, mux, _ := setup(t)
   559  
   560  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   561  		testMethod(t, r, "GET")
   562  		testHeader(t, r, "Accept", defaultMediaType)
   563  		// /yo, below will be served as baseURLPath/yo
   564  		http.Redirect(w, r, baseURLPath+"/yo", http.StatusFound)
   565  	})
   566  	mux.HandleFunc("/yo", func(w http.ResponseWriter, r *http.Request) {
   567  		testMethod(t, r, "GET")
   568  		testHeader(t, r, "Accept", defaultMediaType)
   569  		w.WriteHeader(http.StatusNotFound)
   570  	})
   571  
   572  	ctx := context.Background()
   573  	resp, loc, err := client.Repositories.DownloadReleaseAsset(ctx, "o", "r", 1, http.DefaultClient)
   574  	if err == nil {
   575  		t.Error("Repositories.DownloadReleaseAsset did not return an error")
   576  	}
   577  	if resp != nil {
   578  		resp.Close()
   579  		t.Error("Repositories.DownloadReleaseAsset returned stream, want nil")
   580  	}
   581  	if loc != "" {
   582  		t.Errorf(`Repositories.DownloadReleaseAsset returned "%s", want empty ""`, loc)
   583  	}
   584  }
   585  
   586  func TestRepositoriesService_DownloadReleaseAsset_APIError(t *testing.T) {
   587  	t.Parallel()
   588  	client, mux, _ := setup(t)
   589  
   590  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   591  		testMethod(t, r, "GET")
   592  		testHeader(t, r, "Accept", defaultMediaType)
   593  		w.WriteHeader(http.StatusNotFound)
   594  		fmt.Fprint(w, `{"message":"Not Found","documentation_url":"https://developer.github.com/v3"}`)
   595  	})
   596  
   597  	ctx := context.Background()
   598  	resp, loc, err := client.Repositories.DownloadReleaseAsset(ctx, "o", "r", 1, nil)
   599  	if err == nil {
   600  		t.Error("Repositories.DownloadReleaseAsset did not return an error")
   601  	}
   602  
   603  	if resp != nil {
   604  		resp.Close()
   605  		t.Error("Repositories.DownloadReleaseAsset returned stream, want nil")
   606  	}
   607  
   608  	if loc != "" {
   609  		t.Errorf(`Repositories.DownloadReleaseAsset returned "%s", want empty ""`, loc)
   610  	}
   611  }
   612  
   613  func TestRepositoriesService_EditReleaseAsset(t *testing.T) {
   614  	t.Parallel()
   615  	client, mux, _ := setup(t)
   616  
   617  	input := &ReleaseAsset{Name: Ptr("n")}
   618  
   619  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   620  		v := new(ReleaseAsset)
   621  		assertNilError(t, json.NewDecoder(r.Body).Decode(v))
   622  
   623  		testMethod(t, r, "PATCH")
   624  		if !cmp.Equal(v, input) {
   625  			t.Errorf("Request body = %+v, want %+v", v, input)
   626  		}
   627  		fmt.Fprint(w, `{"id":1}`)
   628  	})
   629  
   630  	ctx := context.Background()
   631  	asset, _, err := client.Repositories.EditReleaseAsset(ctx, "o", "r", 1, input)
   632  	if err != nil {
   633  		t.Errorf("Repositories.EditReleaseAsset returned error: %v", err)
   634  	}
   635  	want := &ReleaseAsset{ID: Ptr(int64(1))}
   636  	if !cmp.Equal(asset, want) {
   637  		t.Errorf("Repositories.EditReleaseAsset returned = %+v, want %+v", asset, want)
   638  	}
   639  
   640  	const methodName = "EditReleaseAsset"
   641  	testBadOptions(t, methodName, func() (err error) {
   642  		_, _, err = client.Repositories.EditReleaseAsset(ctx, "\n", "\n", 1, input)
   643  		return err
   644  	})
   645  
   646  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   647  		got, resp, err := client.Repositories.EditReleaseAsset(ctx, "o", "r", 1, input)
   648  		if got != nil {
   649  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   650  		}
   651  		return resp, err
   652  	})
   653  }
   654  
   655  func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) {
   656  	t.Parallel()
   657  	client, mux, _ := setup(t)
   658  
   659  	mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
   660  		testMethod(t, r, "DELETE")
   661  	})
   662  
   663  	ctx := context.Background()
   664  	_, err := client.Repositories.DeleteReleaseAsset(ctx, "o", "r", 1)
   665  	if err != nil {
   666  		t.Errorf("Repositories.DeleteReleaseAsset returned error: %v", err)
   667  	}
   668  
   669  	const methodName = "DeleteReleaseAsset"
   670  	testBadOptions(t, methodName, func() (err error) {
   671  		_, err = client.Repositories.DeleteReleaseAsset(ctx, "\n", "\n", 1)
   672  		return err
   673  	})
   674  
   675  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   676  		return client.Repositories.DeleteReleaseAsset(ctx, "o", "r", 1)
   677  	})
   678  }
   679  
   680  func TestRepositoriesService_UploadReleaseAsset(t *testing.T) {
   681  	t.Parallel()
   682  	var (
   683  		defaultUploadOptions     = &UploadOptions{Name: "n"}
   684  		defaultExpectedFormValue = values{"name": "n"}
   685  		mediaTypeTextPlain       = "text/plain; charset=utf-8"
   686  	)
   687  	uploadTests := []struct {
   688  		uploadOpts         *UploadOptions
   689  		fileName           string
   690  		expectedFormValues values
   691  		expectedMediaType  string
   692  	}{
   693  		// No file extension and no explicit media type.
   694  		{
   695  			defaultUploadOptions,
   696  			"upload",
   697  			defaultExpectedFormValue,
   698  			defaultMediaType,
   699  		},
   700  		// File extension and no explicit media type.
   701  		{
   702  			defaultUploadOptions,
   703  			"upload.txt",
   704  			defaultExpectedFormValue,
   705  			mediaTypeTextPlain,
   706  		},
   707  		// No file extension and explicit media type.
   708  		{
   709  			&UploadOptions{Name: "n", MediaType: "image/png"},
   710  			"upload",
   711  			defaultExpectedFormValue,
   712  			"image/png",
   713  		},
   714  		// File extension and explicit media type.
   715  		{
   716  			&UploadOptions{Name: "n", MediaType: "image/png"},
   717  			"upload.png",
   718  			defaultExpectedFormValue,
   719  			"image/png",
   720  		},
   721  		// Label provided.
   722  		{
   723  			&UploadOptions{Name: "n", Label: "l"},
   724  			"upload.txt",
   725  			values{"name": "n", "label": "l"},
   726  			mediaTypeTextPlain,
   727  		},
   728  		// No label provided.
   729  		{
   730  			defaultUploadOptions,
   731  			"upload.txt",
   732  			defaultExpectedFormValue,
   733  			mediaTypeTextPlain,
   734  		},
   735  	}
   736  
   737  	client, mux, _ := setup(t)
   738  
   739  	for key, test := range uploadTests {
   740  		releaseEndpoint := fmt.Sprintf("/repos/o/r/releases/%d/assets", key)
   741  		mux.HandleFunc(releaseEndpoint, func(w http.ResponseWriter, r *http.Request) {
   742  			testMethod(t, r, "POST")
   743  			testHeader(t, r, "Content-Type", test.expectedMediaType)
   744  			testHeader(t, r, "Content-Length", "12")
   745  			testFormValues(t, r, test.expectedFormValues)
   746  			testBody(t, r, "Upload me !\n")
   747  
   748  			fmt.Fprintf(w, `{"id":1}`)
   749  		})
   750  
   751  		file := openTestFile(t, test.fileName, "Upload me !\n")
   752  
   753  		ctx := context.Background()
   754  		asset, _, err := client.Repositories.UploadReleaseAsset(ctx, "o", "r", int64(key), test.uploadOpts, file)
   755  		if err != nil {
   756  			t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err)
   757  		}
   758  		want := &ReleaseAsset{ID: Ptr(int64(1))}
   759  		if !cmp.Equal(asset, want) {
   760  			t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want)
   761  		}
   762  
   763  		const methodName = "UploadReleaseAsset"
   764  		testBadOptions(t, methodName, func() (err error) {
   765  			_, _, err = client.Repositories.UploadReleaseAsset(ctx, "\n", "\n", int64(key), test.uploadOpts, file)
   766  			return err
   767  		})
   768  	}
   769  }
   770  
   771  func TestRepositoryReleaseRequest_Marshal(t *testing.T) {
   772  	t.Parallel()
   773  	testJSONMarshal(t, &repositoryReleaseRequest{}, "{}")
   774  
   775  	u := &repositoryReleaseRequest{
   776  		TagName:                Ptr("tn"),
   777  		TargetCommitish:        Ptr("tc"),
   778  		Name:                   Ptr("name"),
   779  		Body:                   Ptr("body"),
   780  		Draft:                  Ptr(false),
   781  		Prerelease:             Ptr(false),
   782  		MakeLatest:             Ptr("legacy"),
   783  		DiscussionCategoryName: Ptr("dcn"),
   784  	}
   785  
   786  	want := `{
   787  		"tag_name": "tn",
   788  		"target_commitish": "tc",
   789  		"name": "name",
   790  		"body": "body",
   791  		"draft": false,
   792  		"prerelease": false,
   793  		"make_latest": "legacy",
   794  		"discussion_category_name": "dcn"
   795  	}`
   796  
   797  	testJSONMarshal(t, u, want)
   798  }
   799  
   800  func TestReleaseAsset_Marshal(t *testing.T) {
   801  	t.Parallel()
   802  	testJSONMarshal(t, &ReleaseAsset{}, "{}")
   803  
   804  	u := &ReleaseAsset{
   805  		ID:                 Ptr(int64(1)),
   806  		URL:                Ptr("url"),
   807  		Name:               Ptr("name"),
   808  		Label:              Ptr("label"),
   809  		State:              Ptr("state"),
   810  		ContentType:        Ptr("ct"),
   811  		Size:               Ptr(1),
   812  		DownloadCount:      Ptr(1),
   813  		CreatedAt:          &Timestamp{referenceTime},
   814  		UpdatedAt:          &Timestamp{referenceTime},
   815  		BrowserDownloadURL: Ptr("bdu"),
   816  		Uploader:           &User{ID: Ptr(int64(1))},
   817  		NodeID:             Ptr("nid"),
   818  	}
   819  
   820  	want := `{
   821  		"id": 1,
   822  		"url": "url",
   823  		"name": "name",
   824  		"label": "label",
   825  		"state": "state",
   826  		"content_type": "ct",
   827  		"size": 1,
   828  		"download_count": 1,
   829  		"created_at": ` + referenceTimeStr + `,
   830  		"updated_at": ` + referenceTimeStr + `,
   831  		"browser_download_url": "bdu",
   832  		"uploader": {
   833  			"id": 1
   834  		},
   835  		"node_id": "nid"
   836  	}`
   837  
   838  	testJSONMarshal(t, u, want)
   839  }
   840  
   841  func TestRepositoryRelease_Marshal(t *testing.T) {
   842  	t.Parallel()
   843  	testJSONMarshal(t, &RepositoryRelease{}, "{}")
   844  
   845  	u := &RepositoryRelease{
   846  		TagName:                Ptr("tn"),
   847  		TargetCommitish:        Ptr("tc"),
   848  		Name:                   Ptr("name"),
   849  		Body:                   Ptr("body"),
   850  		Draft:                  Ptr(false),
   851  		Prerelease:             Ptr(false),
   852  		MakeLatest:             Ptr("legacy"),
   853  		DiscussionCategoryName: Ptr("dcn"),
   854  		ID:                     Ptr(int64(1)),
   855  		CreatedAt:              &Timestamp{referenceTime},
   856  		PublishedAt:            &Timestamp{referenceTime},
   857  		URL:                    Ptr("url"),
   858  		HTMLURL:                Ptr("hurl"),
   859  		AssetsURL:              Ptr("aurl"),
   860  		Assets:                 []*ReleaseAsset{{ID: Ptr(int64(1))}},
   861  		UploadURL:              Ptr("uurl"),
   862  		ZipballURL:             Ptr("zurl"),
   863  		TarballURL:             Ptr("turl"),
   864  		Author:                 &User{ID: Ptr(int64(1))},
   865  		NodeID:                 Ptr("nid"),
   866  	}
   867  
   868  	want := `{
   869  		"tag_name": "tn",
   870  		"target_commitish": "tc",
   871  		"name": "name",
   872  		"body": "body",
   873  		"draft": false,
   874  		"prerelease": false,
   875  		"make_latest": "legacy",
   876  		"discussion_category_name": "dcn",
   877  		"id": 1,
   878  		"created_at": ` + referenceTimeStr + `,
   879  		"published_at": ` + referenceTimeStr + `,
   880  		"url": "url",
   881  		"html_url": "hurl",
   882  		"assets_url": "aurl",
   883  		"assets": [
   884  			{
   885  				"id": 1
   886  			}
   887  		],
   888  		"upload_url": "uurl",
   889  		"zipball_url": "zurl",
   890  		"tarball_url": "turl",
   891  		"author": {
   892  			"id": 1
   893  		},
   894  		"node_id": "nid"
   895  	}`
   896  
   897  	testJSONMarshal(t, u, want)
   898  }
   899  
   900  func TestGenerateNotesOptions_Marshal(t *testing.T) {
   901  	t.Parallel()
   902  	testJSONMarshal(t, &GenerateNotesOptions{}, "{}")
   903  
   904  	u := &GenerateNotesOptions{
   905  		TagName:         "tag_name",
   906  		PreviousTagName: Ptr("previous_tag_name"),
   907  		TargetCommitish: Ptr("target_commitish"),
   908  	}
   909  
   910  	want := `{
   911  		"tag_name":          "tag_name",
   912  		"previous_tag_name": "previous_tag_name",
   913  		"target_commitish":  "target_commitish"
   914  	}`
   915  
   916  	testJSONMarshal(t, u, want)
   917  }