github.com/google/go-github/v69@v69.2.0/github/repos_contents_test.go (about)

     1  // Copyright 2014 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  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/url"
    15  	"testing"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  )
    19  
    20  func TestRepositoryContent_GetContent(t *testing.T) {
    21  	t.Parallel()
    22  	tests := []struct {
    23  		encoding, content *string // input encoding and content
    24  		want              string  // desired output
    25  		wantErr           bool    // whether an error is expected
    26  	}{
    27  		{
    28  			encoding: Ptr(""),
    29  			content:  Ptr("hello"),
    30  			want:     "hello",
    31  			wantErr:  false,
    32  		},
    33  		{
    34  			encoding: nil,
    35  			content:  Ptr("hello"),
    36  			want:     "hello",
    37  			wantErr:  false,
    38  		},
    39  		{
    40  			encoding: nil,
    41  			content:  nil,
    42  			want:     "",
    43  			wantErr:  false,
    44  		},
    45  		{
    46  			encoding: Ptr("base64"),
    47  			content:  Ptr("aGVsbG8="),
    48  			want:     "hello",
    49  			wantErr:  false,
    50  		},
    51  		{
    52  			encoding: Ptr("bad"),
    53  			content:  Ptr("aGVsbG8="),
    54  			want:     "",
    55  			wantErr:  true,
    56  		},
    57  		{
    58  			encoding: Ptr("none"),
    59  			content:  nil,
    60  			want:     "",
    61  			wantErr:  true,
    62  		},
    63  	}
    64  
    65  	for _, tt := range tests {
    66  		r := RepositoryContent{Encoding: tt.encoding, Content: tt.content}
    67  		got, err := r.GetContent()
    68  		if err != nil && !tt.wantErr {
    69  			t.Errorf("RepositoryContent(%s, %s) returned unexpected error: %v",
    70  				stringOrNil(tt.encoding), stringOrNil(tt.content), err)
    71  		}
    72  		if err == nil && tt.wantErr {
    73  			t.Errorf("RepositoryContent(%s, %s) did not return unexpected error",
    74  				stringOrNil(tt.encoding), stringOrNil(tt.content))
    75  		}
    76  		if want := tt.want; got != want {
    77  			t.Errorf("RepositoryContent.GetContent returned %+v, want %+v", got, want)
    78  		}
    79  	}
    80  }
    81  
    82  // stringOrNil converts a potentially null string pointer to string.
    83  // For non-nil input pointer, the returned string is enclosed in double-quotes.
    84  func stringOrNil(s *string) string {
    85  	if s == nil {
    86  		return "<nil>"
    87  	}
    88  	return fmt.Sprintf("%q", *s)
    89  }
    90  
    91  func TestRepositoriesService_GetReadme(t *testing.T) {
    92  	t.Parallel()
    93  	client, mux, _ := setup(t)
    94  
    95  	mux.HandleFunc("/repos/o/r/readme", func(w http.ResponseWriter, r *http.Request) {
    96  		testMethod(t, r, "GET")
    97  		fmt.Fprint(w, `{
    98  		  "type": "file",
    99  		  "encoding": "base64",
   100  		  "size": 5362,
   101  		  "name": "README.md",
   102  		  "path": "README.md"
   103  		}`)
   104  	})
   105  	ctx := context.Background()
   106  	readme, _, err := client.Repositories.GetReadme(ctx, "o", "r", &RepositoryContentGetOptions{})
   107  	if err != nil {
   108  		t.Errorf("Repositories.GetReadme returned error: %v", err)
   109  	}
   110  	want := &RepositoryContent{Type: Ptr("file"), Name: Ptr("README.md"), Size: Ptr(5362), Encoding: Ptr("base64"), Path: Ptr("README.md")}
   111  	if !cmp.Equal(readme, want) {
   112  		t.Errorf("Repositories.GetReadme returned %+v, want %+v", readme, want)
   113  	}
   114  
   115  	const methodName = "GetReadme"
   116  	testBadOptions(t, methodName, func() (err error) {
   117  		_, _, err = client.Repositories.GetReadme(ctx, "\n", "\n", &RepositoryContentGetOptions{})
   118  		return err
   119  	})
   120  
   121  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   122  		got, resp, err := client.Repositories.GetReadme(ctx, "o", "r", &RepositoryContentGetOptions{})
   123  		if got != nil {
   124  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   125  		}
   126  		return resp, err
   127  	})
   128  }
   129  
   130  func TestRepositoriesService_DownloadContents_Success(t *testing.T) {
   131  	t.Parallel()
   132  	client, mux, serverURL := setup(t)
   133  
   134  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   135  		testMethod(t, r, "GET")
   136  		fmt.Fprint(w, `[{
   137  		  "type": "file",
   138  		  "name": "f",
   139  		  "download_url": "`+serverURL+baseURLPath+`/download/f"
   140  		}]`)
   141  	})
   142  	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
   143  		testMethod(t, r, "GET")
   144  		fmt.Fprint(w, "foo")
   145  	})
   146  
   147  	ctx := context.Background()
   148  	r, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil)
   149  	if err != nil {
   150  		t.Errorf("Repositories.DownloadContents returned error: %v", err)
   151  	}
   152  
   153  	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
   154  		t.Errorf("Repositories.DownloadContents returned status code %v, want %v", got, want)
   155  	}
   156  
   157  	bytes, err := io.ReadAll(r)
   158  	if err != nil {
   159  		t.Errorf("Error reading response body: %v", err)
   160  	}
   161  	r.Close()
   162  
   163  	if got, want := string(bytes), "foo"; got != want {
   164  		t.Errorf("Repositories.DownloadContents returned %v, want %v", got, want)
   165  	}
   166  
   167  	const methodName = "DownloadContents"
   168  	testBadOptions(t, methodName, func() (err error) {
   169  		_, _, err = client.Repositories.DownloadContents(ctx, "\n", "\n", "\n", nil)
   170  		return err
   171  	})
   172  
   173  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   174  		got, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil)
   175  		if got != nil {
   176  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   177  		}
   178  		return resp, err
   179  	})
   180  }
   181  
   182  func TestRepositoriesService_DownloadContents_FailedResponse(t *testing.T) {
   183  	t.Parallel()
   184  	client, mux, serverURL := setup(t)
   185  
   186  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   187  		testMethod(t, r, "GET")
   188  		fmt.Fprint(w, `[{
   189  			"type": "file",
   190  			"name": "f",
   191  			"download_url": "`+serverURL+baseURLPath+`/download/f"
   192  		  }]`)
   193  	})
   194  	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
   195  		testMethod(t, r, "GET")
   196  		w.WriteHeader(http.StatusInternalServerError)
   197  		fmt.Fprint(w, "foo error")
   198  	})
   199  
   200  	ctx := context.Background()
   201  	r, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil)
   202  	if err != nil {
   203  		t.Errorf("Repositories.DownloadContents returned error: %v", err)
   204  	}
   205  
   206  	if got, want := resp.Response.StatusCode, http.StatusInternalServerError; got != want {
   207  		t.Errorf("Repositories.DownloadContents returned status code %v, want %v", got, want)
   208  	}
   209  
   210  	bytes, err := io.ReadAll(r)
   211  	if err != nil {
   212  		t.Errorf("Error reading response body: %v", err)
   213  	}
   214  	r.Close()
   215  
   216  	if got, want := string(bytes), "foo error"; got != want {
   217  		t.Errorf("Repositories.DownloadContents returned %v, want %v", got, want)
   218  	}
   219  }
   220  
   221  func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) {
   222  	t.Parallel()
   223  	client, mux, _ := setup(t)
   224  
   225  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   226  		testMethod(t, r, "GET")
   227  		fmt.Fprint(w, `[{
   228  		  "type": "file",
   229  		  "name": "f",
   230  		}]`)
   231  	})
   232  
   233  	ctx := context.Background()
   234  	_, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil)
   235  	if err == nil {
   236  		t.Errorf("Repositories.DownloadContents did not return expected error")
   237  	}
   238  
   239  	if resp == nil {
   240  		t.Errorf("Repositories.DownloadContents did not return expected response")
   241  	}
   242  }
   243  
   244  func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) {
   245  	t.Parallel()
   246  	client, mux, _ := setup(t)
   247  
   248  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   249  		testMethod(t, r, "GET")
   250  		fmt.Fprint(w, `[]`)
   251  	})
   252  
   253  	ctx := context.Background()
   254  	_, resp, err := client.Repositories.DownloadContents(ctx, "o", "r", "d/f", nil)
   255  	if err == nil {
   256  		t.Errorf("Repositories.DownloadContents did not return expected error")
   257  	}
   258  
   259  	if resp == nil {
   260  		t.Errorf("Repositories.DownloadContents did not return expected response")
   261  	}
   262  }
   263  
   264  func TestRepositoriesService_DownloadContentsWithMeta_Success(t *testing.T) {
   265  	t.Parallel()
   266  	client, mux, serverURL := setup(t)
   267  
   268  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   269  		testMethod(t, r, "GET")
   270  		fmt.Fprint(w, `[{
   271  		  "type": "file",
   272  		  "name": "f",
   273  		  "download_url": "`+serverURL+baseURLPath+`/download/f"
   274  		}]`)
   275  	})
   276  	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
   277  		testMethod(t, r, "GET")
   278  		fmt.Fprint(w, "foo")
   279  	})
   280  
   281  	ctx := context.Background()
   282  	r, c, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
   283  	if err != nil {
   284  		t.Errorf("Repositories.DownloadContentsWithMeta returned error: %v", err)
   285  	}
   286  
   287  	if got, want := resp.Response.StatusCode, http.StatusOK; got != want {
   288  		t.Errorf("Repositories.DownloadContentsWithMeta returned status code %v, want %v", got, want)
   289  	}
   290  
   291  	bytes, err := io.ReadAll(r)
   292  	if err != nil {
   293  		t.Errorf("Error reading response body: %v", err)
   294  	}
   295  	r.Close()
   296  
   297  	if got, want := string(bytes), "foo"; got != want {
   298  		t.Errorf("Repositories.DownloadContentsWithMeta returned %v, want %v", got, want)
   299  	}
   300  
   301  	if c != nil && c.Name != nil {
   302  		if got, want := *c.Name, "f"; got != want {
   303  			t.Errorf("Repositories.DownloadContentsWithMeta returned content name %v, want %v", got, want)
   304  		}
   305  	} else {
   306  		t.Errorf("Returned RepositoryContent is null")
   307  	}
   308  
   309  	const methodName = "DownloadContentsWithMeta"
   310  	testBadOptions(t, methodName, func() (err error) {
   311  		_, _, _, err = client.Repositories.DownloadContentsWithMeta(ctx, "\n", "\n", "\n", nil)
   312  		return err
   313  	})
   314  
   315  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   316  		got, cot, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
   317  		if got != nil {
   318  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   319  		}
   320  		if cot != nil {
   321  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, cot)
   322  		}
   323  		return resp, err
   324  	})
   325  }
   326  
   327  func TestRepositoriesService_DownloadContentsWithMeta_FailedResponse(t *testing.T) {
   328  	t.Parallel()
   329  	client, mux, serverURL := setup(t)
   330  
   331  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   332  		testMethod(t, r, "GET")
   333  		fmt.Fprint(w, `[{
   334  			"type": "file",
   335  			"name": "f",
   336  			"download_url": "`+serverURL+baseURLPath+`/download/f"
   337  		  }]`)
   338  	})
   339  	mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
   340  		testMethod(t, r, "GET")
   341  		w.WriteHeader(http.StatusInternalServerError)
   342  		fmt.Fprint(w, "foo error")
   343  	})
   344  
   345  	ctx := context.Background()
   346  	r, c, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
   347  	if err != nil {
   348  		t.Errorf("Repositories.DownloadContentsWithMeta returned error: %v", err)
   349  	}
   350  
   351  	if got, want := resp.Response.StatusCode, http.StatusInternalServerError; got != want {
   352  		t.Errorf("Repositories.DownloadContentsWithMeta returned status code %v, want %v", got, want)
   353  	}
   354  
   355  	bytes, err := io.ReadAll(r)
   356  	if err != nil {
   357  		t.Errorf("Error reading response body: %v", err)
   358  	}
   359  	r.Close()
   360  
   361  	if got, want := string(bytes), "foo error"; got != want {
   362  		t.Errorf("Repositories.DownloadContentsWithMeta returned %v, want %v", got, want)
   363  	}
   364  
   365  	if c != nil && c.Name != nil {
   366  		if got, want := *c.Name, "f"; got != want {
   367  			t.Errorf("Repositories.DownloadContentsWithMeta returned content name %v, want %v", got, want)
   368  		}
   369  	} else {
   370  		t.Errorf("Returned RepositoryContent is null")
   371  	}
   372  }
   373  
   374  func TestRepositoriesService_DownloadContentsWithMeta_NoDownloadURL(t *testing.T) {
   375  	t.Parallel()
   376  	client, mux, _ := setup(t)
   377  
   378  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   379  		testMethod(t, r, "GET")
   380  		fmt.Fprint(w, `[{
   381  		  "type": "file",
   382  		  "name": "f",
   383  		}]`)
   384  	})
   385  
   386  	ctx := context.Background()
   387  	_, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
   388  	if err == nil {
   389  		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected error")
   390  	}
   391  
   392  	if resp == nil {
   393  		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected response")
   394  	}
   395  }
   396  
   397  func TestRepositoriesService_DownloadContentsWithMeta_NoFile(t *testing.T) {
   398  	t.Parallel()
   399  	client, mux, _ := setup(t)
   400  
   401  	mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
   402  		testMethod(t, r, "GET")
   403  		fmt.Fprint(w, `[]`)
   404  	})
   405  
   406  	ctx := context.Background()
   407  	_, _, resp, err := client.Repositories.DownloadContentsWithMeta(ctx, "o", "r", "d/f", nil)
   408  	if err == nil {
   409  		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected error")
   410  	}
   411  
   412  	if resp == nil {
   413  		t.Errorf("Repositories.DownloadContentsWithMeta did not return expected response")
   414  	}
   415  }
   416  
   417  func TestRepositoriesService_GetContents_File(t *testing.T) {
   418  	t.Parallel()
   419  	client, mux, _ := setup(t)
   420  
   421  	mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
   422  		testMethod(t, r, "GET")
   423  		fmt.Fprint(w, `{
   424  		  "type": "file",
   425  		  "encoding": "base64",
   426  		  "size": 20678,
   427  		  "name": "LICENSE",
   428  		  "path": "LICENSE"
   429  		}`)
   430  	})
   431  	ctx := context.Background()
   432  	fileContents, _, _, err := client.Repositories.GetContents(ctx, "o", "r", "p", &RepositoryContentGetOptions{})
   433  	if err != nil {
   434  		t.Errorf("Repositories.GetContents returned error: %v", err)
   435  	}
   436  	want := &RepositoryContent{Type: Ptr("file"), Name: Ptr("LICENSE"), Size: Ptr(20678), Encoding: Ptr("base64"), Path: Ptr("LICENSE")}
   437  	if !cmp.Equal(fileContents, want) {
   438  		t.Errorf("Repositories.GetContents returned %+v, want %+v", fileContents, want)
   439  	}
   440  
   441  	const methodName = "GetContents"
   442  	testBadOptions(t, methodName, func() (err error) {
   443  		_, _, _, err = client.Repositories.GetContents(ctx, "\n", "\n", "\n", &RepositoryContentGetOptions{})
   444  		return err
   445  	})
   446  
   447  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   448  		got, _, resp, err := client.Repositories.GetContents(ctx, "o", "r", "p", &RepositoryContentGetOptions{})
   449  		if got != nil {
   450  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   451  		}
   452  		return resp, err
   453  	})
   454  }
   455  
   456  func TestRepositoriesService_GetContents_FilenameNeedsEscape(t *testing.T) {
   457  	t.Parallel()
   458  	client, mux, _ := setup(t)
   459  
   460  	mux.HandleFunc("/repos/o/r/contents/p#?%/中.go", func(w http.ResponseWriter, r *http.Request) {
   461  		testMethod(t, r, "GET")
   462  		fmt.Fprint(w, `{}`)
   463  	})
   464  	ctx := context.Background()
   465  	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", "p#?%/中.go", &RepositoryContentGetOptions{})
   466  	if err != nil {
   467  		t.Fatalf("Repositories.GetContents returned error: %v", err)
   468  	}
   469  }
   470  
   471  func TestRepositoriesService_GetContents_DirectoryWithSpaces(t *testing.T) {
   472  	t.Parallel()
   473  	client, mux, _ := setup(t)
   474  
   475  	mux.HandleFunc("/repos/o/r/contents/some%20directory/file.go", func(w http.ResponseWriter, r *http.Request) {
   476  		testMethod(t, r, "GET")
   477  		fmt.Fprint(w, `{}`)
   478  	})
   479  	ctx := context.Background()
   480  	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", "some directory/file.go", &RepositoryContentGetOptions{})
   481  	if err != nil {
   482  		t.Fatalf("Repositories.GetContents returned error: %v", err)
   483  	}
   484  }
   485  
   486  func TestRepositoriesService_GetContents_PathWithParent(t *testing.T) {
   487  	t.Parallel()
   488  	client, mux, _ := setup(t)
   489  
   490  	mux.HandleFunc("/repos/o/r/contents/some/../directory/file.go", func(w http.ResponseWriter, r *http.Request) {
   491  		testMethod(t, r, "GET")
   492  		fmt.Fprint(w, `{}`)
   493  	})
   494  	ctx := context.Background()
   495  	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", "some/../directory/file.go", &RepositoryContentGetOptions{})
   496  	if err == nil {
   497  		t.Fatal("Repositories.GetContents expected error but got none")
   498  	}
   499  }
   500  
   501  func TestRepositoriesService_GetContents_DirectoryWithPlusChars(t *testing.T) {
   502  	t.Parallel()
   503  	client, mux, _ := setup(t)
   504  
   505  	mux.HandleFunc("/repos/o/r/contents/some%20directory%2Bname/file.go", func(w http.ResponseWriter, r *http.Request) {
   506  		testMethod(t, r, "GET")
   507  		fmt.Fprint(w, `{}`)
   508  	})
   509  	ctx := context.Background()
   510  	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", "some directory+name/file.go", &RepositoryContentGetOptions{})
   511  	if err != nil {
   512  		t.Fatalf("Repositories.GetContents returned error: %v", err)
   513  	}
   514  }
   515  
   516  func TestRepositoriesService_GetContents_Directory(t *testing.T) {
   517  	t.Parallel()
   518  	client, mux, _ := setup(t)
   519  
   520  	mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
   521  		testMethod(t, r, "GET")
   522  		fmt.Fprint(w, `[{
   523  		  "type": "dir",
   524  		  "name": "lib",
   525  		  "path": "lib"
   526  		},
   527  		{
   528  		  "type": "file",
   529  		  "size": 20678,
   530  		  "name": "LICENSE",
   531  		  "path": "LICENSE"
   532  		}]`)
   533  	})
   534  	ctx := context.Background()
   535  	_, directoryContents, _, err := client.Repositories.GetContents(ctx, "o", "r", "p", &RepositoryContentGetOptions{})
   536  	if err != nil {
   537  		t.Errorf("Repositories.GetContents returned error: %v", err)
   538  	}
   539  	want := []*RepositoryContent{{Type: Ptr("dir"), Name: Ptr("lib"), Path: Ptr("lib")},
   540  		{Type: Ptr("file"), Name: Ptr("LICENSE"), Size: Ptr(20678), Path: Ptr("LICENSE")}}
   541  	if !cmp.Equal(directoryContents, want) {
   542  		t.Errorf("Repositories.GetContents_Directory returned %+v, want %+v", directoryContents, want)
   543  	}
   544  }
   545  
   546  func TestRepositoriesService_CreateFile(t *testing.T) {
   547  	t.Parallel()
   548  	client, mux, _ := setup(t)
   549  
   550  	mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
   551  		testMethod(t, r, "PUT")
   552  		fmt.Fprint(w, `{
   553  			"content":{
   554  				"name":"p"
   555  			},
   556  			"commit":{
   557  				"message":"m",
   558  				"sha":"f5f369044773ff9c6383c087466d12adb6fa0828"
   559  			}
   560  		}`)
   561  	})
   562  	message := "m"
   563  	content := []byte("c")
   564  	repositoryContentsOptions := &RepositoryContentFileOptions{
   565  		Message:   &message,
   566  		Content:   content,
   567  		Committer: &CommitAuthor{Name: Ptr("n"), Email: Ptr("e")},
   568  	}
   569  	ctx := context.Background()
   570  	createResponse, _, err := client.Repositories.CreateFile(ctx, "o", "r", "p", repositoryContentsOptions)
   571  	if err != nil {
   572  		t.Errorf("Repositories.CreateFile returned error: %v", err)
   573  	}
   574  	want := &RepositoryContentResponse{
   575  		Content: &RepositoryContent{Name: Ptr("p")},
   576  		Commit: Commit{
   577  			Message: Ptr("m"),
   578  			SHA:     Ptr("f5f369044773ff9c6383c087466d12adb6fa0828"),
   579  		},
   580  	}
   581  	if !cmp.Equal(createResponse, want) {
   582  		t.Errorf("Repositories.CreateFile returned %+v, want %+v", createResponse, want)
   583  	}
   584  
   585  	const methodName = "CreateFile"
   586  	testBadOptions(t, methodName, func() (err error) {
   587  		_, _, err = client.Repositories.CreateFile(ctx, "\n", "\n", "\n", repositoryContentsOptions)
   588  		return err
   589  	})
   590  
   591  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   592  		got, resp, err := client.Repositories.CreateFile(ctx, "o", "r", "p", repositoryContentsOptions)
   593  		if got != nil {
   594  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   595  		}
   596  		return resp, err
   597  	})
   598  }
   599  
   600  func TestRepositoriesService_UpdateFile(t *testing.T) {
   601  	t.Parallel()
   602  	client, mux, _ := setup(t)
   603  
   604  	mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
   605  		testMethod(t, r, "PUT")
   606  		fmt.Fprint(w, `{
   607  			"content":{
   608  				"name":"p"
   609  			},
   610  			"commit":{
   611  				"message":"m",
   612  				"sha":"f5f369044773ff9c6383c087466d12adb6fa0828"
   613  			}
   614  		}`)
   615  	})
   616  	message := "m"
   617  	content := []byte("c")
   618  	sha := "f5f369044773ff9c6383c087466d12adb6fa0828"
   619  	repositoryContentsOptions := &RepositoryContentFileOptions{
   620  		Message:   &message,
   621  		Content:   content,
   622  		SHA:       &sha,
   623  		Committer: &CommitAuthor{Name: Ptr("n"), Email: Ptr("e")},
   624  	}
   625  	ctx := context.Background()
   626  	updateResponse, _, err := client.Repositories.UpdateFile(ctx, "o", "r", "p", repositoryContentsOptions)
   627  	if err != nil {
   628  		t.Errorf("Repositories.UpdateFile returned error: %v", err)
   629  	}
   630  	want := &RepositoryContentResponse{
   631  		Content: &RepositoryContent{Name: Ptr("p")},
   632  		Commit: Commit{
   633  			Message: Ptr("m"),
   634  			SHA:     Ptr("f5f369044773ff9c6383c087466d12adb6fa0828"),
   635  		},
   636  	}
   637  	if !cmp.Equal(updateResponse, want) {
   638  		t.Errorf("Repositories.UpdateFile returned %+v, want %+v", updateResponse, want)
   639  	}
   640  
   641  	const methodName = "UpdateFile"
   642  	testBadOptions(t, methodName, func() (err error) {
   643  		_, _, err = client.Repositories.UpdateFile(ctx, "\n", "\n", "\n", repositoryContentsOptions)
   644  		return err
   645  	})
   646  
   647  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   648  		got, resp, err := client.Repositories.UpdateFile(ctx, "o", "r", "p", repositoryContentsOptions)
   649  		if got != nil {
   650  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   651  		}
   652  		return resp, err
   653  	})
   654  }
   655  
   656  func TestRepositoriesService_DeleteFile(t *testing.T) {
   657  	t.Parallel()
   658  	client, mux, _ := setup(t)
   659  
   660  	mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
   661  		testMethod(t, r, "DELETE")
   662  		fmt.Fprint(w, `{
   663  			"content": null,
   664  			"commit":{
   665  				"message":"m",
   666  				"sha":"f5f369044773ff9c6383c087466d12adb6fa0828"
   667  			}
   668  		}`)
   669  	})
   670  	message := "m"
   671  	sha := "f5f369044773ff9c6383c087466d12adb6fa0828"
   672  	repositoryContentsOptions := &RepositoryContentFileOptions{
   673  		Message:   &message,
   674  		SHA:       &sha,
   675  		Committer: &CommitAuthor{Name: Ptr("n"), Email: Ptr("e")},
   676  	}
   677  	ctx := context.Background()
   678  	deleteResponse, _, err := client.Repositories.DeleteFile(ctx, "o", "r", "p", repositoryContentsOptions)
   679  	if err != nil {
   680  		t.Errorf("Repositories.DeleteFile returned error: %v", err)
   681  	}
   682  	want := &RepositoryContentResponse{
   683  		Content: nil,
   684  		Commit: Commit{
   685  			Message: Ptr("m"),
   686  			SHA:     Ptr("f5f369044773ff9c6383c087466d12adb6fa0828"),
   687  		},
   688  	}
   689  	if !cmp.Equal(deleteResponse, want) {
   690  		t.Errorf("Repositories.DeleteFile returned %+v, want %+v", deleteResponse, want)
   691  	}
   692  
   693  	const methodName = "DeleteFile"
   694  	testBadOptions(t, methodName, func() (err error) {
   695  		_, _, err = client.Repositories.DeleteFile(ctx, "\n", "\n", "\n", repositoryContentsOptions)
   696  		return err
   697  	})
   698  
   699  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   700  		got, resp, err := client.Repositories.DeleteFile(ctx, "o", "r", "p", repositoryContentsOptions)
   701  		if got != nil {
   702  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   703  		}
   704  		return resp, err
   705  	})
   706  }
   707  
   708  func TestRepositoriesService_GetArchiveLink(t *testing.T) {
   709  	t.Parallel()
   710  	tcs := []struct {
   711  		name              string
   712  		respectRateLimits bool
   713  	}{
   714  		{
   715  			name:              "withoutRateLimits",
   716  			respectRateLimits: false,
   717  		},
   718  		{
   719  			name:              "withRateLimits",
   720  			respectRateLimits: true,
   721  		},
   722  	}
   723  
   724  	for _, tc := range tcs {
   725  		tc := tc
   726  		t.Run(tc.name, func(t *testing.T) {
   727  			t.Parallel()
   728  			client, mux, _ := setup(t)
   729  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   730  
   731  			mux.HandleFunc("/repos/o/r/tarball/yo", func(w http.ResponseWriter, r *http.Request) {
   732  				testMethod(t, r, "GET")
   733  				http.Redirect(w, r, "http://github.com/a", http.StatusFound)
   734  			})
   735  			ctx := context.Background()
   736  			url, resp, err := client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{Ref: "yo"}, 1)
   737  			if err != nil {
   738  				t.Errorf("Repositories.GetArchiveLink returned error: %v", err)
   739  			}
   740  			if resp.StatusCode != http.StatusFound {
   741  				t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound)
   742  			}
   743  			want := "http://github.com/a"
   744  			if url.String() != want {
   745  				t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want)
   746  			}
   747  
   748  			const methodName = "GetArchiveLink"
   749  			testBadOptions(t, methodName, func() (err error) {
   750  				_, _, err = client.Repositories.GetArchiveLink(ctx, "\n", "\n", Tarball, &RepositoryContentGetOptions{}, 1)
   751  				return err
   752  			})
   753  
   754  			// Add custom round tripper
   755  			client.client.Transport = roundTripperFunc(func(r *http.Request) (*http.Response, error) {
   756  				return nil, errors.New("failed to get archive link")
   757  			})
   758  			testBadOptions(t, methodName, func() (err error) {
   759  				_, _, err = client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{}, 1)
   760  				return err
   761  			})
   762  		})
   763  	}
   764  }
   765  
   766  func TestRepositoriesService_GetArchiveLink_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
   767  	t.Parallel()
   768  	tcs := []struct {
   769  		name              string
   770  		respectRateLimits bool
   771  	}{
   772  		{
   773  			name:              "withoutRateLimits",
   774  			respectRateLimits: false,
   775  		},
   776  		{
   777  			name:              "withRateLimits",
   778  			respectRateLimits: true,
   779  		},
   780  	}
   781  
   782  	for _, tc := range tcs {
   783  		tc := tc
   784  		t.Run(tc.name, func(t *testing.T) {
   785  			t.Parallel()
   786  			client, mux, _ := setup(t)
   787  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   788  
   789  			mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) {
   790  				testMethod(t, r, "GET")
   791  				http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
   792  			})
   793  			ctx := context.Background()
   794  			_, resp, _ := client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{}, 0)
   795  			if resp.StatusCode != http.StatusMovedPermanently {
   796  				t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
   797  			}
   798  		})
   799  	}
   800  }
   801  
   802  func TestRepositoriesService_GetArchiveLink_StatusMovedPermanently_followRedirects(t *testing.T) {
   803  	t.Parallel()
   804  	tcs := []struct {
   805  		name              string
   806  		respectRateLimits bool
   807  	}{
   808  		{
   809  			name:              "withoutRateLimits",
   810  			respectRateLimits: false,
   811  		},
   812  		{
   813  			name:              "withRateLimits",
   814  			respectRateLimits: true,
   815  		},
   816  	}
   817  
   818  	for _, tc := range tcs {
   819  		tc := tc
   820  		t.Run(tc.name, func(t *testing.T) {
   821  			t.Parallel()
   822  			client, mux, serverURL := setup(t)
   823  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   824  
   825  			// Mock a redirect link, which leads to an archive link
   826  			mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) {
   827  				testMethod(t, r, "GET")
   828  				redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
   829  				http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
   830  			})
   831  			mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
   832  				testMethod(t, r, "GET")
   833  				http.Redirect(w, r, "http://github.com/a", http.StatusFound)
   834  			})
   835  			ctx := context.Background()
   836  			url, resp, err := client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{}, 1)
   837  			if err != nil {
   838  				t.Errorf("Repositories.GetArchiveLink returned error: %v", err)
   839  			}
   840  			if resp.StatusCode != http.StatusFound {
   841  				t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound)
   842  			}
   843  			want := "http://github.com/a"
   844  			if url.String() != want {
   845  				t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want)
   846  			}
   847  		})
   848  	}
   849  }
   850  
   851  func TestRepositoriesService_GetContents_NoTrailingSlashInDirectoryApiPath(t *testing.T) {
   852  	t.Parallel()
   853  	client, mux, _ := setup(t)
   854  
   855  	mux.HandleFunc("/repos/o/r/contents/.github", func(w http.ResponseWriter, r *http.Request) {
   856  		testMethod(t, r, "GET")
   857  		query := r.URL.Query()
   858  		if query.Get("ref") != "mybranch" {
   859  			t.Errorf("Repositories.GetContents returned %+v, want %+v", query.Get("ref"), "mybranch")
   860  		}
   861  		fmt.Fprint(w, `{}`)
   862  	})
   863  	ctx := context.Background()
   864  	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", ".github/", &RepositoryContentGetOptions{
   865  		Ref: "mybranch",
   866  	})
   867  	if err != nil {
   868  		t.Fatalf("Repositories.GetContents returned error: %v", err)
   869  	}
   870  }
   871  
   872  func TestRepositoryContent_Marshal(t *testing.T) {
   873  	t.Parallel()
   874  	testJSONMarshal(t, &RepositoryContent{}, "{}")
   875  
   876  	r := &RepositoryContent{
   877  		Type:            Ptr("type"),
   878  		Target:          Ptr("target"),
   879  		Encoding:        Ptr("encoding"),
   880  		Size:            Ptr(1),
   881  		Name:            Ptr("name"),
   882  		Path:            Ptr("path"),
   883  		Content:         Ptr("content"),
   884  		SHA:             Ptr("sha"),
   885  		URL:             Ptr("url"),
   886  		GitURL:          Ptr("gurl"),
   887  		HTMLURL:         Ptr("hurl"),
   888  		DownloadURL:     Ptr("durl"),
   889  		SubmoduleGitURL: Ptr("smgurl"),
   890  	}
   891  
   892  	want := `{
   893  		"type": "type",
   894  		"target": "target",
   895  		"encoding": "encoding",
   896  		"size": 1,
   897  		"name": "name",
   898  		"path": "path",
   899  		"content": "content",
   900  		"sha": "sha",
   901  		"url": "url",
   902  		"git_url": "gurl",
   903  		"html_url": "hurl",
   904  		"download_url": "durl",
   905  		"submodule_git_url": "smgurl"
   906  	}`
   907  
   908  	testJSONMarshal(t, r, want)
   909  }
   910  
   911  func TestRepositoryContentResponse_Marshal(t *testing.T) {
   912  	t.Parallel()
   913  	testJSONMarshal(t, &RepositoryContentResponse{}, "{}")
   914  
   915  	r := &RepositoryContentResponse{
   916  		Content: &RepositoryContent{
   917  			Type:            Ptr("type"),
   918  			Target:          Ptr("target"),
   919  			Encoding:        Ptr("encoding"),
   920  			Size:            Ptr(1),
   921  			Name:            Ptr("name"),
   922  			Path:            Ptr("path"),
   923  			Content:         Ptr("content"),
   924  			SHA:             Ptr("sha"),
   925  			URL:             Ptr("url"),
   926  			GitURL:          Ptr("gurl"),
   927  			HTMLURL:         Ptr("hurl"),
   928  			DownloadURL:     Ptr("durl"),
   929  			SubmoduleGitURL: Ptr("smgurl"),
   930  		},
   931  		Commit: Commit{
   932  			SHA: Ptr("s"),
   933  			Author: &CommitAuthor{
   934  				Date:  &Timestamp{referenceTime},
   935  				Name:  Ptr("n"),
   936  				Email: Ptr("e"),
   937  				Login: Ptr("u"),
   938  			},
   939  			Committer: &CommitAuthor{
   940  				Date:  &Timestamp{referenceTime},
   941  				Name:  Ptr("n"),
   942  				Email: Ptr("e"),
   943  				Login: Ptr("u"),
   944  			},
   945  			Message: Ptr("m"),
   946  			Tree: &Tree{
   947  				SHA: Ptr("s"),
   948  				Entries: []*TreeEntry{{
   949  					SHA:     Ptr("s"),
   950  					Path:    Ptr("p"),
   951  					Mode:    Ptr("m"),
   952  					Type:    Ptr("t"),
   953  					Size:    Ptr(1),
   954  					Content: Ptr("c"),
   955  					URL:     Ptr("u"),
   956  				}},
   957  				Truncated: Ptr(false),
   958  			},
   959  			Parents: nil,
   960  			HTMLURL: Ptr("h"),
   961  			URL:     Ptr("u"),
   962  			Verification: &SignatureVerification{
   963  				Verified:  Ptr(false),
   964  				Reason:    Ptr("r"),
   965  				Signature: Ptr("s"),
   966  				Payload:   Ptr("p"),
   967  			},
   968  			NodeID:       Ptr("n"),
   969  			CommentCount: Ptr(1),
   970  		},
   971  	}
   972  
   973  	want := `{
   974  		"content": {
   975  			"type": "type",
   976  			"target": "target",
   977  			"encoding": "encoding",
   978  			"size": 1,
   979  			"name": "name",
   980  			"path": "path",
   981  			"content": "content",
   982  			"sha": "sha",
   983  			"url": "url",
   984  			"git_url": "gurl",
   985  			"html_url": "hurl",
   986  			"download_url": "durl",
   987  			"submodule_git_url": "smgurl"
   988  		},
   989  		"commit": {
   990  			"sha": "s",
   991  			"author": {
   992  				"date": ` + referenceTimeStr + `,
   993  				"name": "n",
   994  				"email": "e",
   995  				"username": "u"
   996  			},
   997  			"committer": {
   998  				"date": ` + referenceTimeStr + `,
   999  				"name": "n",
  1000  				"email": "e",
  1001  				"username": "u"
  1002  			},
  1003  			"message": "m",
  1004  			"tree": {
  1005  				"sha": "s",
  1006  				"tree": [
  1007  					{
  1008  						"sha": "s",
  1009  						"path": "p",
  1010  						"mode": "m",
  1011  						"type": "t",
  1012  						"size": 1,
  1013  						"content": "c",
  1014  						"url": "u"
  1015  					}
  1016  				],
  1017  				"truncated": false
  1018  			},
  1019  			"html_url": "h",
  1020  			"url": "u",
  1021  			"verification": {
  1022  				"verified": false,
  1023  				"reason": "r",
  1024  				"signature": "s",
  1025  				"payload": "p"
  1026  			},
  1027  			"node_id": "n",
  1028  			"comment_count": 1
  1029  		}
  1030  	}`
  1031  
  1032  	testJSONMarshal(t, r, want)
  1033  }
  1034  
  1035  func TestRepositoryContentFileOptions_Marshal(t *testing.T) {
  1036  	t.Parallel()
  1037  	testJSONMarshal(t, &RepositoryContentFileOptions{}, "{}")
  1038  
  1039  	r := &RepositoryContentFileOptions{
  1040  		Message: Ptr("type"),
  1041  		Content: []byte{1},
  1042  		SHA:     Ptr("type"),
  1043  		Branch:  Ptr("type"),
  1044  		Author: &CommitAuthor{
  1045  			Date:  &Timestamp{referenceTime},
  1046  			Name:  Ptr("name"),
  1047  			Email: Ptr("email"),
  1048  			Login: Ptr("login"),
  1049  		},
  1050  		Committer: &CommitAuthor{
  1051  			Date:  &Timestamp{referenceTime},
  1052  			Name:  Ptr("name"),
  1053  			Email: Ptr("email"),
  1054  			Login: Ptr("login"),
  1055  		},
  1056  	}
  1057  
  1058  	want := `{
  1059  		"message": "type",
  1060  		"content": "AQ==",
  1061  		"sha": "type",
  1062  		"branch": "type",
  1063  		"author": {
  1064  			"date": ` + referenceTimeStr + `,
  1065  			"name": "name",
  1066  			"email": "email",
  1067  			"username": "login"
  1068  		},
  1069  		"committer": {
  1070  			"date": ` + referenceTimeStr + `,
  1071  			"name": "name",
  1072  			"email": "email",
  1073  			"username": "login"
  1074  		}
  1075  	}`
  1076  
  1077  	testJSONMarshal(t, r, want)
  1078  }