...

Source file src/github.com/cli/go-gh/v2/pkg/api/rest_client_test.go

Documentation: github.com/cli/go-gh/v2/pkg/api

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io"
     7  	"net/http"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"gopkg.in/h2non/gock.v1"
    13  )
    14  
    15  func TestRESTClient(t *testing.T) {
    16  	stubConfig(t, testConfig())
    17  	t.Cleanup(gock.Off)
    18  
    19  	gock.New("https://api.github.com").
    20  		Get("/some/test/path").
    21  		MatchHeader("Authorization", "token abc123").
    22  		Reply(200).
    23  		JSON(`{"message": "success"}`)
    24  
    25  	client, err := DefaultRESTClient()
    26  	assert.NoError(t, err)
    27  
    28  	res := struct{ Message string }{}
    29  	err = client.Do("GET", "some/test/path", nil, &res)
    30  	assert.NoError(t, err)
    31  	assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
    32  	assert.Equal(t, "success", res.Message)
    33  }
    34  
    35  func TestRESTClientRequest(t *testing.T) {
    36  	tests := []struct {
    37  		name       string
    38  		host       string
    39  		path       string
    40  		httpMocks  func()
    41  		wantErr    bool
    42  		wantErrMsg string
    43  		wantBody   string
    44  	}{
    45  		{
    46  			name: "success request empty response",
    47  			path: "some/test/path",
    48  			httpMocks: func() {
    49  				gock.New("https://api.github.com").
    50  					Get("/some/test/path").
    51  					Reply(204)
    52  			},
    53  			wantBody: ``,
    54  		},
    55  		{
    56  			name: "success request non-empty response",
    57  			path: "some/test/path",
    58  			httpMocks: func() {
    59  				gock.New("https://api.github.com").
    60  					Get("/some/test/path").
    61  					Reply(200).
    62  					JSON(`{"message": "success"}`)
    63  			},
    64  			wantBody: `{"message": "success"}`,
    65  		},
    66  		{
    67  			name: "fail request empty response",
    68  			path: "some/test/path",
    69  			httpMocks: func() {
    70  				gock.New("https://api.github.com").
    71  					Get("/some/test/path").
    72  					Reply(404).
    73  					JSON(`{}`)
    74  			},
    75  			wantErr:    true,
    76  			wantErrMsg: "HTTP 404 (https://api.github.com/some/test/path)",
    77  			wantBody:   `{}`,
    78  		},
    79  		{
    80  			name: "fail request non-empty response",
    81  			path: "some/test/path",
    82  			httpMocks: func() {
    83  				gock.New("https://api.github.com").
    84  					Get("/some/test/path").
    85  					Reply(422).
    86  					JSON(`{"message": "OH NO"}`)
    87  			},
    88  			wantErr:    true,
    89  			wantErrMsg: "HTTP 422: OH NO (https://api.github.com/some/test/path)",
    90  			wantBody:   `{"message": "OH NO"}`,
    91  		},
    92  		{
    93  			name: "support full urls",
    94  			path: "https://example.com/someother/test/path",
    95  			httpMocks: func() {
    96  				gock.New("https://example.com").
    97  					Get("/someother/test/path").
    98  					Reply(200).
    99  					JSON(`{}`)
   100  			},
   101  			wantBody: `{}`,
   102  		},
   103  		{
   104  			name: "support enterprise hosts",
   105  			host: "enterprise.com",
   106  			path: "some/test/path",
   107  			httpMocks: func() {
   108  				gock.New("https://enterprise.com").
   109  					Get("/some/test/path").
   110  					Reply(200).
   111  					JSON(`{}`)
   112  			},
   113  			wantBody: `{}`,
   114  		},
   115  	}
   116  
   117  	for _, tt := range tests {
   118  		t.Run(tt.name, func(t *testing.T) {
   119  			t.Cleanup(gock.Off)
   120  			if tt.host == "" {
   121  				tt.host = "github.com"
   122  			}
   123  			if tt.httpMocks != nil {
   124  				tt.httpMocks()
   125  			}
   126  			client, _ := NewRESTClient(ClientOptions{
   127  				Host:      tt.host,
   128  				AuthToken: "token",
   129  				Transport: http.DefaultTransport,
   130  			})
   131  
   132  			resp, err := client.Request("GET", tt.path, nil)
   133  			if tt.wantErr {
   134  				assert.EqualError(t, err, tt.wantErrMsg)
   135  			} else {
   136  				assert.NoError(t, err)
   137  			}
   138  
   139  			if err == nil {
   140  				defer resp.Body.Close()
   141  				body, err := io.ReadAll(resp.Body)
   142  				assert.NoError(t, err)
   143  				assert.Equal(t, tt.wantBody, string(body))
   144  			}
   145  
   146  			assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   147  		})
   148  	}
   149  }
   150  
   151  func TestRESTClientDo(t *testing.T) {
   152  	tests := []struct {
   153  		name       string
   154  		host       string
   155  		path       string
   156  		httpMocks  func()
   157  		wantErr    bool
   158  		wantErrMsg string
   159  		wantMsg    string
   160  	}{
   161  		{
   162  			name: "success request empty response",
   163  			path: "some/test/path",
   164  			httpMocks: func() {
   165  				gock.New("https://api.github.com").
   166  					Get("/some/test/path").
   167  					Reply(204).
   168  					JSON(`{}`)
   169  			},
   170  		},
   171  		{
   172  			name: "success request non-empty response",
   173  			path: "some/test/path",
   174  			httpMocks: func() {
   175  				gock.New("https://api.github.com").
   176  					Get("/some/test/path").
   177  					Reply(200).
   178  					JSON(`{"message": "success"}`)
   179  			},
   180  			wantMsg: "success",
   181  		},
   182  		{
   183  			name: "fail request empty response",
   184  			path: "some/test/path",
   185  			httpMocks: func() {
   186  				gock.New("https://api.github.com").
   187  					Get("/some/test/path").
   188  					Reply(404).
   189  					JSON(`{}`)
   190  			},
   191  			wantErr:    true,
   192  			wantErrMsg: "HTTP 404 (https://api.github.com/some/test/path)",
   193  		},
   194  		{
   195  			name: "fail request non-empty response",
   196  			path: "some/test/path",
   197  			httpMocks: func() {
   198  				gock.New("https://api.github.com").
   199  					Get("/some/test/path").
   200  					Reply(422).
   201  					JSON(`{"message": "OH NO"}`)
   202  			},
   203  			wantErr:    true,
   204  			wantErrMsg: "HTTP 422: OH NO (https://api.github.com/some/test/path)",
   205  		},
   206  		{
   207  			name: "support full urls",
   208  			path: "https://example.com/someother/test/path",
   209  			httpMocks: func() {
   210  				gock.New("https://example.com").
   211  					Get("/someother/test/path").
   212  					Reply(204).
   213  					JSON(`{}`)
   214  			},
   215  		},
   216  		{
   217  			name: "support enterprise hosts",
   218  			host: "enterprise.com",
   219  			path: "some/test/path",
   220  			httpMocks: func() {
   221  				gock.New("https://enterprise.com").
   222  					Get("/some/test/path").
   223  					Reply(204).
   224  					JSON(`{}`)
   225  			},
   226  		},
   227  	}
   228  
   229  	for _, tt := range tests {
   230  		t.Run(tt.name, func(t *testing.T) {
   231  			t.Cleanup(gock.Off)
   232  			if tt.host == "" {
   233  				tt.host = "github.com"
   234  			}
   235  			if tt.httpMocks != nil {
   236  				tt.httpMocks()
   237  			}
   238  			client, _ := NewRESTClient(ClientOptions{
   239  				Host:      tt.host,
   240  				AuthToken: "token",
   241  				Transport: http.DefaultTransport,
   242  			})
   243  			res := struct{ Message string }{}
   244  			err := client.Do("GET", tt.path, nil, &res)
   245  			if tt.wantErr {
   246  				assert.EqualError(t, err, tt.wantErrMsg)
   247  			} else {
   248  				assert.NoError(t, err)
   249  			}
   250  			assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   251  			assert.Equal(t, tt.wantMsg, res.Message)
   252  		})
   253  	}
   254  }
   255  
   256  func TestRESTClientDelete(t *testing.T) {
   257  	t.Cleanup(gock.Off)
   258  	gock.New("https://api.github.com").
   259  		Delete("/some/path/here").
   260  		Reply(204).
   261  		JSON(`{}`)
   262  	client, _ := NewRESTClient(ClientOptions{
   263  		Host:      "github.com",
   264  		AuthToken: "token",
   265  		Transport: http.DefaultTransport,
   266  	})
   267  	err := client.Delete("some/path/here", nil)
   268  	assert.NoError(t, err)
   269  	assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   270  }
   271  
   272  func TestRESTClientGet(t *testing.T) {
   273  	t.Cleanup(gock.Off)
   274  	gock.New("https://api.github.com").
   275  		Get("/some/path/here").
   276  		Reply(204).
   277  		JSON(`{}`)
   278  	client, _ := NewRESTClient(ClientOptions{
   279  		Host:      "github.com",
   280  		AuthToken: "token",
   281  		Transport: http.DefaultTransport,
   282  	})
   283  	err := client.Get("some/path/here", nil)
   284  	assert.NoError(t, err)
   285  	assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   286  }
   287  
   288  func TestRESTClientPatch(t *testing.T) {
   289  	t.Cleanup(gock.Off)
   290  	gock.New("https://api.github.com").
   291  		Patch("/some/path/here").
   292  		BodyString(`{}`).
   293  		Reply(204).
   294  		JSON(`{}`)
   295  	client, _ := NewRESTClient(ClientOptions{
   296  		Host:      "github.com",
   297  		AuthToken: "token",
   298  		Transport: http.DefaultTransport,
   299  	})
   300  	r := bytes.NewReader([]byte(`{}`))
   301  	err := client.Patch("some/path/here", r, nil)
   302  	assert.NoError(t, err)
   303  	assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   304  }
   305  
   306  func TestRESTClientPost(t *testing.T) {
   307  	t.Cleanup(gock.Off)
   308  	gock.New("https://api.github.com").
   309  		Post("/some/path/here").
   310  		BodyString(`{}`).
   311  		Reply(204).
   312  		JSON(`{}`)
   313  	client, _ := NewRESTClient(ClientOptions{
   314  		Host:      "github.com",
   315  		AuthToken: "token",
   316  		Transport: http.DefaultTransport,
   317  	})
   318  	r := bytes.NewReader([]byte(`{}`))
   319  	err := client.Post("some/path/here", r, nil)
   320  	assert.NoError(t, err)
   321  	assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   322  }
   323  
   324  func TestRESTClientPut(t *testing.T) {
   325  	t.Cleanup(gock.Off)
   326  	gock.New("https://api.github.com").
   327  		Put("/some/path/here").
   328  		BodyString(`{}`).
   329  		Reply(204).
   330  		JSON(`{}`)
   331  	client, _ := NewRESTClient(ClientOptions{
   332  		Host:      "github.com",
   333  		AuthToken: "token",
   334  		Transport: http.DefaultTransport,
   335  	})
   336  	r := bytes.NewReader([]byte(`{}`))
   337  	err := client.Put("some/path/here", r, nil)
   338  	assert.NoError(t, err)
   339  	assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   340  }
   341  
   342  func TestRESTClientDoWithContext(t *testing.T) {
   343  	tests := []struct {
   344  		name       string
   345  		wantErrMsg string
   346  		getCtx     func() context.Context
   347  	}{
   348  		{
   349  			name: "http fail request canceled",
   350  			getCtx: func() context.Context {
   351  				ctx, cancel := context.WithCancel(context.Background())
   352  				// call 'cancel' to ensure that context is already canceled
   353  				cancel()
   354  				return ctx
   355  			},
   356  			wantErrMsg: `Get "https://api.github.com/some/path": context canceled`,
   357  		},
   358  		{
   359  			name: "http fail request timed out",
   360  			getCtx: func() context.Context {
   361  				// pass current time to ensure that deadline has already passed
   362  				ctx, cancel := context.WithDeadline(context.Background(), time.Now())
   363  				cancel()
   364  				return ctx
   365  			},
   366  			wantErrMsg: `Get "https://api.github.com/some/path": context deadline exceeded`,
   367  		},
   368  	}
   369  
   370  	for _, tt := range tests {
   371  		t.Run(tt.name, func(t *testing.T) {
   372  			// given
   373  			t.Cleanup(gock.Off)
   374  			gock.New("https://api.github.com").
   375  				Get("/some/path").
   376  				Reply(204).
   377  				JSON(`{}`)
   378  
   379  			client, _ := NewRESTClient(ClientOptions{
   380  				Host:      "github.com",
   381  				AuthToken: "token",
   382  				Transport: http.DefaultTransport,
   383  			})
   384  			res := struct{ Message string }{}
   385  
   386  			// when
   387  			ctx := tt.getCtx()
   388  			gotErr := client.DoWithContext(ctx, http.MethodGet, "some/path", nil, &res)
   389  
   390  			// then
   391  			assert.EqualError(t, gotErr, tt.wantErrMsg)
   392  			assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   393  		})
   394  	}
   395  }
   396  
   397  func TestRESTClientRequestWithContext(t *testing.T) {
   398  	tests := []struct {
   399  		name       string
   400  		wantErrMsg string
   401  		getCtx     func() context.Context
   402  	}{
   403  		{
   404  			name: "http fail request canceled",
   405  			getCtx: func() context.Context {
   406  				ctx, cancel := context.WithCancel(context.Background())
   407  				// call 'cancel' to ensure that context is already canceled
   408  				cancel()
   409  				return ctx
   410  			},
   411  			wantErrMsg: `Get "https://api.github.com/some/path": context canceled`,
   412  		},
   413  		{
   414  			name: "http fail request timed out",
   415  			getCtx: func() context.Context {
   416  				// pass current time to ensure that deadline has already passed
   417  				ctx, cancel := context.WithDeadline(context.Background(), time.Now())
   418  				cancel()
   419  				return ctx
   420  			},
   421  			wantErrMsg: `Get "https://api.github.com/some/path": context deadline exceeded`,
   422  		},
   423  	}
   424  
   425  	for _, tt := range tests {
   426  		t.Run(tt.name, func(t *testing.T) {
   427  			// given
   428  			t.Cleanup(gock.Off)
   429  			gock.New("https://api.github.com").
   430  				Get("/some/path").
   431  				Reply(204).
   432  				JSON(`{}`)
   433  
   434  			client, _ := NewRESTClient(ClientOptions{
   435  				Host:      "github.com",
   436  				AuthToken: "token",
   437  				Transport: http.DefaultTransport,
   438  			})
   439  
   440  			// when
   441  			ctx := tt.getCtx()
   442  			_, gotErr := client.RequestWithContext(ctx, http.MethodGet, "some/path", nil)
   443  
   444  			// then
   445  			assert.EqualError(t, gotErr, tt.wantErrMsg)
   446  			assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
   447  		})
   448  	}
   449  }
   450  
   451  func TestRestPrefix(t *testing.T) {
   452  	tests := []struct {
   453  		name         string
   454  		host         string
   455  		wantEndpoint string
   456  	}{
   457  		{
   458  			name:         "github",
   459  			host:         "github.com",
   460  			wantEndpoint: "https://api.github.com/",
   461  		},
   462  		{
   463  			name:         "localhost",
   464  			host:         "github.localhost",
   465  			wantEndpoint: "http://api.github.localhost/",
   466  		},
   467  		{
   468  			name:         "garage",
   469  			host:         "garage.github.com",
   470  			wantEndpoint: "https://garage.github.com/api/v3/",
   471  		},
   472  		{
   473  			name:         "enterprise",
   474  			host:         "enterprise.com",
   475  			wantEndpoint: "https://enterprise.com/api/v3/",
   476  		},
   477  		{
   478  			name:         "tenant",
   479  			host:         "tenant.ghe.com",
   480  			wantEndpoint: "https://api.tenant.ghe.com/",
   481  		},
   482  	}
   483  
   484  	for _, tt := range tests {
   485  		t.Run(tt.name, func(t *testing.T) {
   486  			endpoint := restPrefix(tt.host)
   487  			assert.Equal(t, tt.wantEndpoint, endpoint)
   488  		})
   489  	}
   490  }
   491  

View as plain text