github.com/google/go-github/v71@v71.0.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  		t.Run(tc.name, func(t *testing.T) {
   726  			t.Parallel()
   727  			client, mux, _ := setup(t)
   728  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   729  
   730  			mux.HandleFunc("/repos/o/r/tarball/yo", func(w http.ResponseWriter, r *http.Request) {
   731  				testMethod(t, r, "GET")
   732  				http.Redirect(w, r, "http://github.com/a", http.StatusFound)
   733  			})
   734  			ctx := context.Background()
   735  			url, resp, err := client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{Ref: "yo"}, 1)
   736  			if err != nil {
   737  				t.Errorf("Repositories.GetArchiveLink returned error: %v", err)
   738  			}
   739  			if resp.StatusCode != http.StatusFound {
   740  				t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound)
   741  			}
   742  			want := "http://github.com/a"
   743  			if url.String() != want {
   744  				t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want)
   745  			}
   746  
   747  			const methodName = "GetArchiveLink"
   748  			testBadOptions(t, methodName, func() (err error) {
   749  				_, _, err = client.Repositories.GetArchiveLink(ctx, "\n", "\n", Tarball, &RepositoryContentGetOptions{}, 1)
   750  				return err
   751  			})
   752  
   753  			// Add custom round tripper
   754  			client.client.Transport = roundTripperFunc(func(r *http.Request) (*http.Response, error) {
   755  				return nil, errors.New("failed to get archive link")
   756  			})
   757  			testBadOptions(t, methodName, func() (err error) {
   758  				_, _, err = client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{}, 1)
   759  				return err
   760  			})
   761  		})
   762  	}
   763  }
   764  
   765  func TestRepositoriesService_GetArchiveLink_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
   766  	t.Parallel()
   767  	tcs := []struct {
   768  		name              string
   769  		respectRateLimits bool
   770  	}{
   771  		{
   772  			name:              "withoutRateLimits",
   773  			respectRateLimits: false,
   774  		},
   775  		{
   776  			name:              "withRateLimits",
   777  			respectRateLimits: true,
   778  		},
   779  	}
   780  
   781  	for _, tc := range tcs {
   782  		t.Run(tc.name, func(t *testing.T) {
   783  			t.Parallel()
   784  			client, mux, _ := setup(t)
   785  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   786  
   787  			mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) {
   788  				testMethod(t, r, "GET")
   789  				http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
   790  			})
   791  			ctx := context.Background()
   792  			_, resp, _ := client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{}, 0)
   793  			if resp.StatusCode != http.StatusMovedPermanently {
   794  				t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
   795  			}
   796  		})
   797  	}
   798  }
   799  
   800  func TestRepositoriesService_GetArchiveLink_StatusMovedPermanently_followRedirects(t *testing.T) {
   801  	t.Parallel()
   802  	tcs := []struct {
   803  		name              string
   804  		respectRateLimits bool
   805  	}{
   806  		{
   807  			name:              "withoutRateLimits",
   808  			respectRateLimits: false,
   809  		},
   810  		{
   811  			name:              "withRateLimits",
   812  			respectRateLimits: true,
   813  		},
   814  	}
   815  
   816  	for _, tc := range tcs {
   817  		t.Run(tc.name, func(t *testing.T) {
   818  			t.Parallel()
   819  			client, mux, serverURL := setup(t)
   820  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   821  
   822  			// Mock a redirect link, which leads to an archive link
   823  			mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) {
   824  				testMethod(t, r, "GET")
   825  				redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
   826  				http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
   827  			})
   828  			mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
   829  				testMethod(t, r, "GET")
   830  				http.Redirect(w, r, "http://github.com/a", http.StatusFound)
   831  			})
   832  			ctx := context.Background()
   833  			url, resp, err := client.Repositories.GetArchiveLink(ctx, "o", "r", Tarball, &RepositoryContentGetOptions{}, 1)
   834  			if err != nil {
   835  				t.Errorf("Repositories.GetArchiveLink returned error: %v", err)
   836  			}
   837  			if resp.StatusCode != http.StatusFound {
   838  				t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound)
   839  			}
   840  			want := "http://github.com/a"
   841  			if url.String() != want {
   842  				t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want)
   843  			}
   844  		})
   845  	}
   846  }
   847  
   848  func TestRepositoriesService_GetContents_NoTrailingSlashInDirectoryApiPath(t *testing.T) {
   849  	t.Parallel()
   850  	client, mux, _ := setup(t)
   851  
   852  	mux.HandleFunc("/repos/o/r/contents/.github", func(w http.ResponseWriter, r *http.Request) {
   853  		testMethod(t, r, "GET")
   854  		query := r.URL.Query()
   855  		if query.Get("ref") != "mybranch" {
   856  			t.Errorf("Repositories.GetContents returned %+v, want %+v", query.Get("ref"), "mybranch")
   857  		}
   858  		fmt.Fprint(w, `{}`)
   859  	})
   860  	ctx := context.Background()
   861  	_, _, _, err := client.Repositories.GetContents(ctx, "o", "r", ".github/", &RepositoryContentGetOptions{
   862  		Ref: "mybranch",
   863  	})
   864  	if err != nil {
   865  		t.Fatalf("Repositories.GetContents returned error: %v", err)
   866  	}
   867  }
   868  
   869  func TestRepositoryContent_Marshal(t *testing.T) {
   870  	t.Parallel()
   871  	testJSONMarshal(t, &RepositoryContent{}, "{}")
   872  
   873  	r := &RepositoryContent{
   874  		Type:            Ptr("type"),
   875  		Target:          Ptr("target"),
   876  		Encoding:        Ptr("encoding"),
   877  		Size:            Ptr(1),
   878  		Name:            Ptr("name"),
   879  		Path:            Ptr("path"),
   880  		Content:         Ptr("content"),
   881  		SHA:             Ptr("sha"),
   882  		URL:             Ptr("url"),
   883  		GitURL:          Ptr("gurl"),
   884  		HTMLURL:         Ptr("hurl"),
   885  		DownloadURL:     Ptr("durl"),
   886  		SubmoduleGitURL: Ptr("smgurl"),
   887  	}
   888  
   889  	want := `{
   890  		"type": "type",
   891  		"target": "target",
   892  		"encoding": "encoding",
   893  		"size": 1,
   894  		"name": "name",
   895  		"path": "path",
   896  		"content": "content",
   897  		"sha": "sha",
   898  		"url": "url",
   899  		"git_url": "gurl",
   900  		"html_url": "hurl",
   901  		"download_url": "durl",
   902  		"submodule_git_url": "smgurl"
   903  	}`
   904  
   905  	testJSONMarshal(t, r, want)
   906  }
   907  
   908  func TestRepositoryContentResponse_Marshal(t *testing.T) {
   909  	t.Parallel()
   910  	testJSONMarshal(t, &RepositoryContentResponse{}, "{}")
   911  
   912  	r := &RepositoryContentResponse{
   913  		Content: &RepositoryContent{
   914  			Type:            Ptr("type"),
   915  			Target:          Ptr("target"),
   916  			Encoding:        Ptr("encoding"),
   917  			Size:            Ptr(1),
   918  			Name:            Ptr("name"),
   919  			Path:            Ptr("path"),
   920  			Content:         Ptr("content"),
   921  			SHA:             Ptr("sha"),
   922  			URL:             Ptr("url"),
   923  			GitURL:          Ptr("gurl"),
   924  			HTMLURL:         Ptr("hurl"),
   925  			DownloadURL:     Ptr("durl"),
   926  			SubmoduleGitURL: Ptr("smgurl"),
   927  		},
   928  		Commit: Commit{
   929  			SHA: Ptr("s"),
   930  			Author: &CommitAuthor{
   931  				Date:  &Timestamp{referenceTime},
   932  				Name:  Ptr("n"),
   933  				Email: Ptr("e"),
   934  				Login: Ptr("u"),
   935  			},
   936  			Committer: &CommitAuthor{
   937  				Date:  &Timestamp{referenceTime},
   938  				Name:  Ptr("n"),
   939  				Email: Ptr("e"),
   940  				Login: Ptr("u"),
   941  			},
   942  			Message: Ptr("m"),
   943  			Tree: &Tree{
   944  				SHA: Ptr("s"),
   945  				Entries: []*TreeEntry{{
   946  					SHA:     Ptr("s"),
   947  					Path:    Ptr("p"),
   948  					Mode:    Ptr("m"),
   949  					Type:    Ptr("t"),
   950  					Size:    Ptr(1),
   951  					Content: Ptr("c"),
   952  					URL:     Ptr("u"),
   953  				}},
   954  				Truncated: Ptr(false),
   955  			},
   956  			Parents: nil,
   957  			HTMLURL: Ptr("h"),
   958  			URL:     Ptr("u"),
   959  			Verification: &SignatureVerification{
   960  				Verified:  Ptr(false),
   961  				Reason:    Ptr("r"),
   962  				Signature: Ptr("s"),
   963  				Payload:   Ptr("p"),
   964  			},
   965  			NodeID:       Ptr("n"),
   966  			CommentCount: Ptr(1),
   967  		},
   968  	}
   969  
   970  	want := `{
   971  		"content": {
   972  			"type": "type",
   973  			"target": "target",
   974  			"encoding": "encoding",
   975  			"size": 1,
   976  			"name": "name",
   977  			"path": "path",
   978  			"content": "content",
   979  			"sha": "sha",
   980  			"url": "url",
   981  			"git_url": "gurl",
   982  			"html_url": "hurl",
   983  			"download_url": "durl",
   984  			"submodule_git_url": "smgurl"
   985  		},
   986  		"commit": {
   987  			"sha": "s",
   988  			"author": {
   989  				"date": ` + referenceTimeStr + `,
   990  				"name": "n",
   991  				"email": "e",
   992  				"username": "u"
   993  			},
   994  			"committer": {
   995  				"date": ` + referenceTimeStr + `,
   996  				"name": "n",
   997  				"email": "e",
   998  				"username": "u"
   999  			},
  1000  			"message": "m",
  1001  			"tree": {
  1002  				"sha": "s",
  1003  				"tree": [
  1004  					{
  1005  						"sha": "s",
  1006  						"path": "p",
  1007  						"mode": "m",
  1008  						"type": "t",
  1009  						"size": 1,
  1010  						"content": "c",
  1011  						"url": "u"
  1012  					}
  1013  				],
  1014  				"truncated": false
  1015  			},
  1016  			"html_url": "h",
  1017  			"url": "u",
  1018  			"verification": {
  1019  				"verified": false,
  1020  				"reason": "r",
  1021  				"signature": "s",
  1022  				"payload": "p"
  1023  			},
  1024  			"node_id": "n",
  1025  			"comment_count": 1
  1026  		}
  1027  	}`
  1028  
  1029  	testJSONMarshal(t, r, want)
  1030  }
  1031  
  1032  func TestRepositoryContentFileOptions_Marshal(t *testing.T) {
  1033  	t.Parallel()
  1034  	testJSONMarshal(t, &RepositoryContentFileOptions{}, "{}")
  1035  
  1036  	r := &RepositoryContentFileOptions{
  1037  		Message: Ptr("type"),
  1038  		Content: []byte{1},
  1039  		SHA:     Ptr("type"),
  1040  		Branch:  Ptr("type"),
  1041  		Author: &CommitAuthor{
  1042  			Date:  &Timestamp{referenceTime},
  1043  			Name:  Ptr("name"),
  1044  			Email: Ptr("email"),
  1045  			Login: Ptr("login"),
  1046  		},
  1047  		Committer: &CommitAuthor{
  1048  			Date:  &Timestamp{referenceTime},
  1049  			Name:  Ptr("name"),
  1050  			Email: Ptr("email"),
  1051  			Login: Ptr("login"),
  1052  		},
  1053  	}
  1054  
  1055  	want := `{
  1056  		"message": "type",
  1057  		"content": "AQ==",
  1058  		"sha": "type",
  1059  		"branch": "type",
  1060  		"author": {
  1061  			"date": ` + referenceTimeStr + `,
  1062  			"name": "name",
  1063  			"email": "email",
  1064  			"username": "login"
  1065  		},
  1066  		"committer": {
  1067  			"date": ` + referenceTimeStr + `,
  1068  			"name": "name",
  1069  			"email": "email",
  1070  			"username": "login"
  1071  		}
  1072  	}`
  1073  
  1074  	testJSONMarshal(t, r, want)
  1075  }