github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/api/http_client_test.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"os"
     8  	"regexp"
     9  	"testing"
    10  
    11  	"github.com/MakeNowJust/heredoc"
    12  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestNewHTTPClient(t *testing.T) {
    18  	type args struct {
    19  		config     tokenGetter
    20  		appVersion string
    21  		setAccept  bool
    22  	}
    23  	tests := []struct {
    24  		name       string
    25  		args       args
    26  		envDebug   string
    27  		setGhDebug bool
    28  		envGhDebug string
    29  		host       string
    30  		wantHeader map[string]string
    31  		wantStderr string
    32  	}{
    33  		{
    34  			name: "github.com with Accept header",
    35  			args: args{
    36  				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
    37  				appVersion: "v1.2.3",
    38  				setAccept:  true,
    39  			},
    40  			host: "github.com",
    41  			wantHeader: map[string]string{
    42  				"authorization": "token MYTOKEN",
    43  				"user-agent":    "GitHub CLI v1.2.3",
    44  				"accept":        "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
    45  			},
    46  			wantStderr: "",
    47  		},
    48  		{
    49  			name: "github.com no Accept header",
    50  			args: args{
    51  				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
    52  				appVersion: "v1.2.3",
    53  				setAccept:  false,
    54  			},
    55  			host: "github.com",
    56  			wantHeader: map[string]string{
    57  				"authorization": "token MYTOKEN",
    58  				"user-agent":    "GitHub CLI v1.2.3",
    59  				"accept":        "",
    60  			},
    61  			wantStderr: "",
    62  		},
    63  		{
    64  			name: "github.com no authentication token",
    65  			args: args{
    66  				config:     tinyConfig{"example.com:oauth_token": "MYTOKEN"},
    67  				appVersion: "v1.2.3",
    68  				setAccept:  true,
    69  			},
    70  			host: "github.com",
    71  			wantHeader: map[string]string{
    72  				"authorization": "",
    73  				"user-agent":    "GitHub CLI v1.2.3",
    74  				"accept":        "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
    75  			},
    76  			wantStderr: "",
    77  		},
    78  		{
    79  			name: "github.com in verbose mode",
    80  			args: args{
    81  				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
    82  				appVersion: "v1.2.3",
    83  				setAccept:  true,
    84  			},
    85  			host:       "github.com",
    86  			envDebug:   "api",
    87  			setGhDebug: false,
    88  			wantHeader: map[string]string{
    89  				"authorization": "token MYTOKEN",
    90  				"user-agent":    "GitHub CLI v1.2.3",
    91  				"accept":        "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
    92  			},
    93  			wantStderr: heredoc.Doc(`
    94  				* Request at <time>
    95  				* Request to http://<host>:<port>
    96  				> GET / HTTP/1.1
    97  				> Host: github.com
    98  				> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
    99  				> Authorization: token ████████████████████
   100  				> Content-Type: application/json; charset=utf-8
   101  				> Time-Zone: <timezone>
   102  				> User-Agent: GitHub CLI v1.2.3
   103  
   104  				< HTTP/1.1 204 No Content
   105  				< Date: <time>
   106  
   107  				* Request took <duration>
   108  			`),
   109  		},
   110  		{
   111  			name: "github.com in verbose mode",
   112  			args: args{
   113  				config:     tinyConfig{"github.com:oauth_token": "MYTOKEN"},
   114  				appVersion: "v1.2.3",
   115  				setAccept:  true,
   116  			},
   117  			host:       "github.com",
   118  			envGhDebug: "api",
   119  			setGhDebug: true,
   120  			wantHeader: map[string]string{
   121  				"authorization": "token MYTOKEN",
   122  				"user-agent":    "GitHub CLI v1.2.3",
   123  				"accept":        "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
   124  			},
   125  			wantStderr: heredoc.Doc(`
   126  				* Request at <time>
   127  				* Request to http://<host>:<port>
   128  				> GET / HTTP/1.1
   129  				> Host: github.com
   130  				> Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
   131  				> Authorization: token ████████████████████
   132  				> Content-Type: application/json; charset=utf-8
   133  				> Time-Zone: <timezone>
   134  				> User-Agent: GitHub CLI v1.2.3
   135  
   136  				< HTTP/1.1 204 No Content
   137  				< Date: <time>
   138  
   139  				* Request took <duration>
   140  			`),
   141  		},
   142  		{
   143  			name: "GHES Accept header",
   144  			args: args{
   145  				config:     tinyConfig{"example.com:oauth_token": "GHETOKEN"},
   146  				appVersion: "v1.2.3",
   147  				setAccept:  true,
   148  			},
   149  			host: "example.com",
   150  			wantHeader: map[string]string{
   151  				"authorization": "token GHETOKEN",
   152  				"user-agent":    "GitHub CLI v1.2.3",
   153  				"accept":        "application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview",
   154  			},
   155  			wantStderr: "",
   156  		},
   157  	}
   158  
   159  	var gotReq *http.Request
   160  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   161  		gotReq = r
   162  		w.WriteHeader(http.StatusNoContent)
   163  	}))
   164  	defer ts.Close()
   165  
   166  	for _, tt := range tests {
   167  		t.Run(tt.name, func(t *testing.T) {
   168  			t.Setenv("DEBUG", tt.envDebug)
   169  			if tt.setGhDebug {
   170  				t.Setenv("GH_DEBUG", tt.envGhDebug)
   171  			} else {
   172  				os.Unsetenv("GH_DEBUG")
   173  			}
   174  
   175  			ios, _, _, stderr := iostreams.Test()
   176  			client, err := NewHTTPClient(HTTPClientOptions{
   177  				AppVersion:        tt.args.appVersion,
   178  				Config:            tt.args.config,
   179  				Log:               ios.ErrOut,
   180  				SkipAcceptHeaders: !tt.args.setAccept,
   181  			})
   182  			require.NoError(t, err)
   183  
   184  			req, err := http.NewRequest("GET", ts.URL, nil)
   185  			req.Header.Set("time-zone", "Europe/Amsterdam")
   186  			req.Host = tt.host
   187  			require.NoError(t, err)
   188  
   189  			res, err := client.Do(req)
   190  
   191  			require.NoError(t, err)
   192  
   193  			for name, value := range tt.wantHeader {
   194  				assert.Equal(t, value, gotReq.Header.Get(name), name)
   195  			}
   196  
   197  			assert.Equal(t, 204, res.StatusCode)
   198  			assert.Equal(t, tt.wantStderr, normalizeVerboseLog(stderr.String()))
   199  		})
   200  	}
   201  }
   202  
   203  type tinyConfig map[string]string
   204  
   205  func (c tinyConfig) AuthToken(host string) (string, string) {
   206  	return c[fmt.Sprintf("%s:%s", host, "oauth_token")], "oauth_token"
   207  }
   208  
   209  var requestAtRE = regexp.MustCompile(`(?m)^\* Request at .+`)
   210  var dateRE = regexp.MustCompile(`(?m)^< Date: .+`)
   211  var hostWithPortRE = regexp.MustCompile(`127\.0\.0\.1:\d+`)
   212  var durationRE = regexp.MustCompile(`(?m)^\* Request took .+`)
   213  var timezoneRE = regexp.MustCompile(`(?m)^> Time-Zone: .+`)
   214  
   215  func normalizeVerboseLog(t string) string {
   216  	t = requestAtRE.ReplaceAllString(t, "* Request at <time>")
   217  	t = hostWithPortRE.ReplaceAllString(t, "<host>:<port>")
   218  	t = dateRE.ReplaceAllString(t, "< Date: <time>")
   219  	t = durationRE.ReplaceAllString(t, "* Request took <duration>")
   220  	t = timezoneRE.ReplaceAllString(t, "> Time-Zone: <timezone>")
   221  	return t
   222  }