bitbucket.org/ai69/amoy@v0.2.3/http_test.go (about)

     1  package amoy
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  var (
    15  	testHttpbinURL     = "https://httpbin.org"
    16  	testHTTPRetryTimes = uint(5)
    17  )
    18  
    19  func init() {
    20  	testHttpbinURL = GetEnvVar("TEST_HTTPBIN_URL", "https://httpbin.org")
    21  	fmt.Println("HTTPBin URL:", testHttpbinURL)
    22  }
    23  
    24  func TestHTTPClient_Get(t *testing.T) {
    25  	defaultUA := defaultHTTPClientOpts.UserAgent
    26  	tests := []struct {
    27  		name      string
    28  		opts      *HTTPClientOptions
    29  		urlSuffix string
    30  		queryArgs map[string]string
    31  		headers   map[string]string
    32  		wantStr   []string
    33  		wantErr   bool
    34  	}{
    35  		{"Nil Opts", nil, "/get", nil, nil, []string{defaultUA}, false},
    36  		{"Empty Opts", &HTTPClientOptions{}, "/get", nil, nil, []string{defaultUA}, false},
    37  		{"Custom UserAgent", &HTTPClientOptions{UserAgent: "amoy-test"}, "/get", nil, nil, []string{"amoy-test"}, false},
    38  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/get", nil, nil, []string{defaultUA, "/get"}, false},
    39  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, []string{defaultUA, "/delay"}, true},
    40  		{"Set Query Args", nil, "/get", map[string]string{"a": "1", "b": "2"}, nil, []string{defaultUA, "/get", "a=1", "b=2"}, false},
    41  		{"Set Headers", nil, "/get", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, []string{defaultUA, "/get", "Apple", "Bravo"}, false},
    42  		{"Invalid JSON", nil, "/base64/YWxvaGEK", nil, nil, []string{"aloha"}, false},
    43  		{"Status Code OK", nil, "/status/200", nil, nil, nil, false},
    44  		{"Status Code Error", nil, "/status/300", nil, nil, nil, true},
    45  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, true},
    46  	}
    47  	for _, tt := range tests {
    48  		t.Run(tt.name, func(t *testing.T) {
    49  			c := NewHTTPClient(tt.opts)
    50  			url := testHttpbinURL + tt.urlSuffix
    51  			var (
    52  				got []byte
    53  				err error
    54  			)
    55  			_ = BackOffRetryIf(func() error {
    56  				got, err = c.Get(url, tt.queryArgs, tt.headers)
    57  				return err
    58  			}, func(err error) bool {
    59  				return !strings.Contains(err.Error(), `status code: 502`)
    60  			}, testHTTPRetryTimes, Milliseconds(100))
    61  			// check resp
    62  			if (err != nil) != tt.wantErr {
    63  				t.Errorf("Get(%s) error = %v, wantErr %v", url, err, tt.wantErr)
    64  				return
    65  			}
    66  			if !tt.wantErr && len(tt.wantStr) > 0 {
    67  				gs := string(got)
    68  				for _, s := range tt.wantStr {
    69  					if !strings.Contains(gs, s) {
    70  						t.Errorf("Get(%s) = %s, should contain %q", url, got, s)
    71  					}
    72  				}
    73  			}
    74  		})
    75  	}
    76  }
    77  
    78  func TestHTTPClient_GetJSON(t *testing.T) {
    79  	defaultUA := defaultHTTPClientOpts.UserAgent
    80  	type respSchema struct {
    81  		Args    map[string]string `json:"args"`
    82  		Headers struct {
    83  			AcceptEncoding string `json:"Accept-Encoding"`
    84  			ContentType    string `json:"Content-Type"`
    85  			Host           string `json:"Host"`
    86  			UserAgent      string `json:"User-Agent"`
    87  		} `json:"headers"`
    88  		Origin string `json:"origin"`
    89  		URL    string `json:"url"`
    90  	}
    91  	tests := []struct {
    92  		name      string
    93  		opts      *HTTPClientOptions
    94  		urlSuffix string
    95  		queryArgs map[string]string
    96  		headers   map[string]string
    97  		wantStr   []string
    98  		wantErr   bool
    99  	}{
   100  		{"Nil Opts", nil, "/get", nil, nil, []string{defaultUA}, false},
   101  		{"Empty Opts", &HTTPClientOptions{}, "/get", nil, nil, []string{defaultUA}, false},
   102  		{"Custom UserAgent", &HTTPClientOptions{UserAgent: "amoy-test"}, "/get", nil, nil, []string{"amoy-test"}, false},
   103  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/get", nil, nil, []string{defaultUA, "/get"}, false},
   104  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, []string{defaultUA, "/delay"}, true},
   105  		{"Set Query Args", nil, "/get", map[string]string{"a": "1", "b": "2"}, nil, []string{defaultUA, "/get", "a=1", "b=2"}, false},
   106  		{"Set Headers", nil, "/get", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, []string{defaultUA, "/get", "Apple", "Bravo"}, false},
   107  		{"Set Query Headers", nil, "/get", map[string]string{"a": "1", "b": "2"}, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, []string{defaultUA, "/get", "a=1", "b=2", "Apple", "Bravo"}, false},
   108  		{"Invalid JSON", nil, "/base64/YWxvaGEK", nil, nil, nil, true},
   109  		{"Status Code OK", nil, "/get", nil, nil, nil, false},
   110  		{"Status Code Error", nil, "/status/300", nil, nil, nil, true},
   111  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, true},
   112  	}
   113  	for _, tt := range tests {
   114  		t.Run(tt.name, func(t *testing.T) {
   115  			c := NewHTTPClient(tt.opts)
   116  			url := testHttpbinURL + tt.urlSuffix
   117  			var (
   118  				got  []byte
   119  				err  error
   120  				resp respSchema
   121  			)
   122  			_ = BackOffRetryIf(func() error {
   123  				got, err = c.GetJSON(url, tt.queryArgs, tt.headers, &resp)
   124  				return err
   125  			}, func(err error) bool {
   126  				return !strings.Contains(err.Error(), `status code: 502`)
   127  			}, testHTTPRetryTimes, Milliseconds(100))
   128  			// check resp
   129  			if (err != nil) != tt.wantErr {
   130  				t.Errorf("GetJSON(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   131  				return
   132  			}
   133  			if !tt.wantErr && len(tt.wantStr) > 0 {
   134  				gs := string(got)
   135  				gotUA := tt.wantStr[0]
   136  				if resp.Headers.UserAgent != gotUA {
   137  					t.Errorf("GetJSON(%s)'s UA = %s, want %q", url, got, gotUA)
   138  				}
   139  				for _, s := range tt.wantStr {
   140  					if !strings.Contains(gs, s) {
   141  						t.Errorf("GetJSON(%s) = %s, should contain %q", url, got, s)
   142  					}
   143  				}
   144  			}
   145  		})
   146  	}
   147  }
   148  
   149  func TestHTTPClient_Post(t *testing.T) {
   150  	defaultUA := defaultHTTPClientOpts.UserAgent
   151  	tests := []struct {
   152  		name      string
   153  		opts      *HTTPClientOptions
   154  		urlSuffix string
   155  		queryArgs map[string]string
   156  		headers   map[string]string
   157  		body      []byte
   158  		wantStr   []string
   159  		wantErr   bool
   160  	}{
   161  		{"Nil Opts", nil, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   162  		{"Empty Opts", &HTTPClientOptions{}, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   163  		{"Custom UserAgent", &HTTPClientOptions{UserAgent: "amoy-test"}, "/post", nil, nil, nil, []string{"amoy-test", "/post"}, false},
   164  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   165  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, nil, []string{defaultUA, "/delay"}, true},
   166  		{"Set Query Args", nil, "/post", map[string]string{"a": "1", "b": "2"}, nil, nil, []string{defaultUA, "/post", "a=1", "b=2"}, false},
   167  		{"Set Headers", nil, "/post", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/post", "Apple", "Bravo"}, false},
   168  		{"Set Query Headers", nil, "/post", map[string]string{"a": "1", "b": "2"}, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/post", "a=1", "b=2", "Apple", "Bravo"}, false},
   169  		{"No Data", nil, "/post", nil, nil, nil, []string{defaultUA, "/post", `""`}, false},
   170  		{"Empty Data", nil, "/post", nil, nil, []byte{}, []string{defaultUA, "/post", `""`}, false},
   171  		{"Data Body", nil, "/post", nil, nil, []byte("hello"), []string{defaultUA, "/post", "hello"}, false},
   172  		{"Status Code OK", nil, "/post", nil, nil, nil, nil, false},
   173  		{"Status Code Error", nil, "/status/300", nil, nil, nil, nil, true},
   174  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, nil, true},
   175  	}
   176  	for _, tt := range tests {
   177  		t.Run(tt.name, func(t *testing.T) {
   178  			c := NewHTTPClient(tt.opts)
   179  			url := testHttpbinURL + tt.urlSuffix
   180  			var (
   181  				got []byte
   182  				err error
   183  			)
   184  			_ = BackOffRetryIf(func() error {
   185  				var rd io.Reader
   186  				if tt.body != nil {
   187  					rd = bytes.NewReader(tt.body)
   188  				}
   189  				got, err = c.Post(url, tt.queryArgs, tt.headers, rd)
   190  				return err
   191  			}, func(err error) bool {
   192  				return !strings.Contains(err.Error(), `status code: 502`)
   193  			}, testHTTPRetryTimes, Milliseconds(100))
   194  			// check resp
   195  			if (err != nil) != tt.wantErr {
   196  				t.Errorf("PostData(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   197  				return
   198  			}
   199  			if !tt.wantErr && len(tt.wantStr) > 0 {
   200  				gs := string(got)
   201  				ds := string(tt.body)
   202  				if !strings.Contains(gs, ds) {
   203  					t.Errorf("PostData(%s)'s Payload = %s, should contain %q", url, got, ds)
   204  				}
   205  				for _, s := range tt.wantStr {
   206  					if !strings.Contains(gs, s) {
   207  						t.Errorf("PostData(%s) = %s, should contain %q", url, got, s)
   208  					}
   209  				}
   210  			}
   211  		})
   212  	}
   213  }
   214  
   215  func TestHTTPClient_PostData(t *testing.T) {
   216  	defaultUA := defaultHTTPClientOpts.UserAgent
   217  	tests := []struct {
   218  		name      string
   219  		opts      *HTTPClientOptions
   220  		urlSuffix string
   221  		queryArgs map[string]string
   222  		headers   map[string]string
   223  		body      []byte
   224  		wantStr   []string
   225  		wantErr   bool
   226  	}{
   227  		{"Nil Opts", nil, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   228  		{"Empty Opts", &HTTPClientOptions{}, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   229  		{"Custom UserAgent", &HTTPClientOptions{UserAgent: "amoy-test"}, "/post", nil, nil, nil, []string{"amoy-test", "/post"}, false},
   230  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   231  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, nil, []string{defaultUA, "/delay"}, true},
   232  		{"Set Query Args", nil, "/post", map[string]string{"a": "1", "b": "2"}, nil, nil, []string{defaultUA, "/post", "a=1", "b=2"}, false},
   233  		{"Set Headers", nil, "/post", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/post", "Apple", "Bravo"}, false},
   234  		{"Set Query Headers", nil, "/post", map[string]string{"a": "1", "b": "2"}, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/post", "a=1", "b=2", "Apple", "Bravo"}, false},
   235  		{"No Data", nil, "/post", nil, nil, nil, []string{defaultUA, "/post", `""`}, false},
   236  		{"Data Body", nil, "/post", nil, nil, []byte("hello"), []string{defaultUA, "/post", "hello"}, false},
   237  		{"Status Code OK", nil, "/post", nil, nil, nil, nil, false},
   238  		{"Status Code Error", nil, "/status/300", nil, nil, nil, nil, true},
   239  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, nil, true},
   240  	}
   241  	for _, tt := range tests {
   242  		t.Run(tt.name, func(t *testing.T) {
   243  			c := NewHTTPClient(tt.opts)
   244  			url := testHttpbinURL + tt.urlSuffix
   245  			var (
   246  				got []byte
   247  				err error
   248  			)
   249  			_ = BackOffRetryIf(func() error {
   250  				got, err = c.PostData(url, tt.queryArgs, tt.headers, tt.body)
   251  				return err
   252  			}, func(err error) bool {
   253  				return !strings.Contains(err.Error(), `status code: 502`)
   254  			}, testHTTPRetryTimes, Milliseconds(100))
   255  			// check resp
   256  			if (err != nil) != tt.wantErr {
   257  				t.Errorf("PostData(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   258  				return
   259  			}
   260  			if !tt.wantErr && len(tt.wantStr) > 0 {
   261  				gs := string(got)
   262  				ds := string(tt.body)
   263  				if !strings.Contains(gs, ds) {
   264  					t.Errorf("PostData(%s)'s Payload = %s, should contain %q", url, got, ds)
   265  				}
   266  				for _, s := range tt.wantStr {
   267  					if !strings.Contains(gs, s) {
   268  						t.Errorf("PostData(%s) = %s, should contain %q", url, got, s)
   269  					}
   270  				}
   271  			}
   272  		})
   273  	}
   274  }
   275  
   276  func TestHTTPClient_PostJSON(t *testing.T) {
   277  	var nilMap map[string]interface{}
   278  	defaultUA := defaultHTTPClientOpts.UserAgent
   279  	type respSchema struct {
   280  		Data    string                 `json:"data"`
   281  		Files   map[string]interface{} `json:"files"`
   282  		Form    map[string]interface{} `json:"form"`
   283  		JSON    map[string]interface{} `json:"json"`
   284  		Args    map[string]string      `json:"args"`
   285  		Headers struct {
   286  			AcceptEncoding string `json:"Accept-Encoding"`
   287  			ContentType    string `json:"Content-Type"`
   288  			Host           string `json:"Host"`
   289  			UserAgent      string `json:"User-Agent"`
   290  		} `json:"headers"`
   291  		Origin string `json:"origin"`
   292  		URL    string `json:"url"`
   293  	}
   294  	tests := []struct {
   295  		name      string
   296  		opts      *HTTPClientOptions
   297  		urlSuffix string
   298  		queryArgs map[string]string
   299  		headers   map[string]string
   300  		body      map[string]interface{}
   301  		wantStr   []string
   302  		wantErr   bool
   303  	}{
   304  		{"Nil Opts", nil, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   305  		{"Empty Opts", &HTTPClientOptions{}, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   306  		{"Custom UserAgent", &HTTPClientOptions{UserAgent: "amoy-test"}, "/post", nil, nil, nil, []string{"amoy-test", "/post"}, false},
   307  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/post", nil, nil, nil, []string{defaultUA, "/post"}, false},
   308  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, nil, []string{defaultUA, "/delay"}, true},
   309  		{"Set Query Args", nil, "/post", map[string]string{"a": "1", "b": "2"}, nil, nil, []string{defaultUA, "/post", "a=1", "b=2"}, false},
   310  		{"Set Headers", nil, "/post", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/post", "Apple", "Bravo"}, false},
   311  		{"Set Query Headers", nil, "/post", map[string]string{"a": "1", "b": "2"}, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/post", "a=1", "b=2", "Apple", "Bravo"}, false},
   312  		{"Nil Data", nil, "/post", nil, nil, nil, []string{defaultUA, "/post", `""`, `{}`}, false},
   313  		{"Nil Data 2", nil, "/post", nil, nil, nilMap, []string{defaultUA, "/post", `""`, `{}`}, false},
   314  		{"Empty JSON", nil, "/post", nil, nil, map[string]interface{}{}, []string{defaultUA, "/post", `{}`}, false},
   315  		{"JSON Data", nil, "/post", nil, nil, map[string]interface{}{"task": "hello", "job": "world", "success": true}, []string{defaultUA, "/post", "hello", "world"}, false},
   316  		{"JSON Marshal Error", nil, "/post", nil, nil, map[string]interface{}{"task": "error", "func": strings.Repeat}, nil, true},
   317  		{"Status Code OK", nil, "/post", nil, nil, nil, nil, false},
   318  		{"Status Code Error", nil, "/status/300", nil, nil, nil, nil, true},
   319  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, nil, true},
   320  	}
   321  	for _, tt := range tests {
   322  		t.Run(tt.name, func(t *testing.T) {
   323  			c := NewHTTPClient(tt.opts)
   324  			url := testHttpbinURL + tt.urlSuffix
   325  			var (
   326  				got  []byte
   327  				err  error
   328  				resp respSchema
   329  			)
   330  			_ = BackOffRetryIf(func() error {
   331  				got, err = c.PostJSON(url, tt.queryArgs, tt.headers, tt.body, &resp)
   332  				return err
   333  			}, func(err error) bool {
   334  				return !strings.Contains(err.Error(), `status code: 502`)
   335  			}, testHTTPRetryTimes, Milliseconds(100))
   336  			// check resp
   337  			if (err != nil) != tt.wantErr {
   338  				t.Errorf("PostJSON(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   339  				return
   340  			}
   341  			if !tt.wantErr && tt.body != nil {
   342  				if !reflect.DeepEqual(resp.JSON, tt.body) {
   343  					t.Errorf("PostJSON(%s)'s JSON = %v, want %v", url, resp.JSON, tt.body)
   344  				}
   345  			}
   346  			if !tt.wantErr && len(tt.wantStr) > 0 {
   347  				gs := string(got)
   348  				gotUA := tt.wantStr[0]
   349  				if resp.Headers.UserAgent != gotUA {
   350  					t.Errorf("PostJSON(%s)'s UA = %s, want %q", url, got, gotUA)
   351  				}
   352  				for _, s := range tt.wantStr {
   353  					if !strings.Contains(gs, s) {
   354  						t.Errorf("PostJSON(%s) = %s, should contain %q", url, got, s)
   355  					}
   356  				}
   357  			}
   358  		})
   359  	}
   360  }
   361  
   362  func TestHTTPClient_Custom_Head(t *testing.T) {
   363  	tests := []struct {
   364  		name      string
   365  		opts      *HTTPClientOptions
   366  		urlSuffix string
   367  		queryArgs map[string]string
   368  		headers   map[string]string
   369  		body      []byte
   370  		wantErr   bool
   371  	}{
   372  		{"Nil Opts", nil, "/get", nil, nil, nil, false},
   373  		{"Empty Opts", &HTTPClientOptions{}, "/get", nil, nil, nil, false},
   374  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/get", nil, nil, nil, false},
   375  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, nil, true},
   376  		{"Set Query Args", nil, "/get", map[string]string{"a": "1", "b": "2"}, nil, nil, false},
   377  		{"Set Headers", nil, "/get", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, false},
   378  		{"Body Data", nil, "/base64/YWxvaGEK", nil, nil, []byte("aloha"), false},
   379  		{"Status Code OK", nil, "/status/200", nil, nil, nil, false},
   380  		{"Status Code Error", nil, "/status/300", nil, nil, nil, true},
   381  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, true},
   382  	}
   383  	for _, tt := range tests {
   384  		t.Run(tt.name, func(t *testing.T) {
   385  			c := NewHTTPClient(tt.opts)
   386  			url := testHttpbinURL + tt.urlSuffix
   387  			var (
   388  				got []byte
   389  				err error
   390  			)
   391  			_ = BackOffRetryIf(func() error {
   392  				var rd io.Reader
   393  				if tt.body != nil {
   394  					rd = bytes.NewReader(tt.body)
   395  				}
   396  				got, err = c.Custom(http.MethodHead, url, tt.queryArgs, tt.headers, rd)
   397  				return err
   398  			}, func(err error) bool {
   399  				return !strings.Contains(err.Error(), `status code: 502`)
   400  			}, testHTTPRetryTimes, Milliseconds(100))
   401  			// check resp
   402  			if (err != nil) != tt.wantErr {
   403  				t.Errorf("Custom(Head, %s) error = %v, wantErr %v", url, err, tt.wantErr)
   404  				return
   405  			}
   406  			if !tt.wantErr && len(got) > 0 {
   407  				t.Errorf("Custom(Head, %s) = %s, want empty", url, got)
   408  			}
   409  		})
   410  	}
   411  }
   412  
   413  func TestHTTPClient_Custom_Patch(t *testing.T) {
   414  	defaultUA := defaultHTTPClientOpts.UserAgent
   415  	tests := []struct {
   416  		name      string
   417  		opts      *HTTPClientOptions
   418  		urlSuffix string
   419  		queryArgs map[string]string
   420  		headers   map[string]string
   421  		body      []byte
   422  		wantStr   []string
   423  		wantErr   bool
   424  	}{
   425  		{"Nil Opts", nil, "/patch", nil, nil, nil, []string{defaultUA}, false},
   426  		{"Empty Opts", &HTTPClientOptions{}, "/patch", nil, nil, nil, []string{defaultUA}, false},
   427  		{"Custom UserAgent", &HTTPClientOptions{UserAgent: "amoy-test"}, "/patch", nil, nil, nil, []string{"amoy-test"}, false},
   428  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/patch", nil, nil, nil, []string{defaultUA, "/patch"}, false},
   429  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, nil, []string{defaultUA, "/delay"}, true},
   430  		{"Set Query Args", nil, "/patch", map[string]string{"a": "1", "b": "2"}, nil, nil, []string{defaultUA, "/patch", "a=1", "b=2"}, false},
   431  		{"Set Headers", nil, "/patch", nil, map[string]string{"Type-A": "Apple", "Type-B": "Bravo"}, nil, []string{defaultUA, "/patch", "Apple", "Bravo"}, false},
   432  		{"Body Data", nil, "/patch", nil, nil, []byte("aloha"), []string{"aloha"}, false},
   433  		{"Status Code OK", nil, "/status/200", nil, nil, nil, nil, false},
   434  		{"Status Code Error", nil, "/status/300", nil, nil, nil, nil, true},
   435  		{"Status Code Error 2", nil, "/status/404", nil, nil, nil, nil, true},
   436  	}
   437  	for _, tt := range tests {
   438  		t.Run(tt.name, func(t *testing.T) {
   439  			c := NewHTTPClient(tt.opts)
   440  			url := testHttpbinURL + tt.urlSuffix
   441  			var (
   442  				got []byte
   443  				err error
   444  			)
   445  			_ = BackOffRetryIf(func() error {
   446  				var rd io.Reader
   447  				if tt.body != nil {
   448  					rd = bytes.NewReader(tt.body)
   449  				}
   450  				got, err = c.Custom(http.MethodPatch, url, tt.queryArgs, tt.headers, rd)
   451  				return err
   452  			}, func(err error) bool {
   453  				return !strings.Contains(err.Error(), `status code: 502`)
   454  			}, testHTTPRetryTimes, Milliseconds(100))
   455  			// check resp
   456  			if (err != nil) != tt.wantErr {
   457  				t.Errorf("Custom(Patch, %s) error = %v, wantErr %v", url, err, tt.wantErr)
   458  				return
   459  			}
   460  			if !tt.wantErr && len(tt.wantStr) > 0 {
   461  				gs := string(got)
   462  				for _, s := range tt.wantStr {
   463  					if !strings.Contains(gs, s) {
   464  						t.Errorf("Custom(Patch, %s) = %s, should contain %q", url, got, s)
   465  					}
   466  				}
   467  			}
   468  		})
   469  	}
   470  }
   471  
   472  func TestHTTPClient_EmbeddingClient(t *testing.T) {
   473  	defaultUA := "Go-http-client/"
   474  	tests := []struct {
   475  		name      string
   476  		opts      *HTTPClientOptions
   477  		urlSuffix string
   478  		queryArgs map[string]string
   479  		headers   map[string]string
   480  		wantStr   []string
   481  		wantErr   bool
   482  	}{
   483  		{"Custom Timeout OK", &HTTPClientOptions{Timeout: Seconds(2)}, "/get", nil, nil, []string{defaultUA, "/get"}, false},
   484  		{"Custom Timeout Error", &HTTPClientOptions{Timeout: Seconds(2)}, "/delay/5", nil, nil, nil, true},
   485  		{"Missed Auth", &HTTPClientOptions{Username: "joe", Password: "secret"}, "/basic-auth/joe/secret", nil, nil, nil, true},
   486  	}
   487  	for _, tt := range tests {
   488  		t.Run(tt.name, func(t *testing.T) {
   489  			c := NewHTTPClient(tt.opts)
   490  			url := testHttpbinURL + tt.urlSuffix
   491  			var (
   492  				got []byte
   493  				err error
   494  			)
   495  			err = BackOffRetryIf(func() error {
   496  				resp, e := c.Client.Get(url)
   497  				if e != nil {
   498  					return e
   499  				}
   500  				if resp.StatusCode != http.StatusOK {
   501  					return fmt.Errorf("status code: %d", resp.StatusCode)
   502  				}
   503  				defer resp.Body.Close()
   504  				got, err = ioutil.ReadAll(resp.Body)
   505  				return err
   506  			}, func(err error) bool {
   507  				return !strings.Contains(err.Error(), `status code: 502`)
   508  			}, testHTTPRetryTimes, Milliseconds(100))
   509  			// check resp
   510  			if (err != nil) != tt.wantErr {
   511  				t.Errorf("EmbeddingClient Get(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   512  				return
   513  			}
   514  			if !tt.wantErr && len(tt.wantStr) > 0 {
   515  				gs := string(got)
   516  				for _, s := range tt.wantStr {
   517  					if !strings.Contains(gs, s) {
   518  						t.Errorf("EmbeddingClient Get(%s) = %s, should contain %q", url, got, s)
   519  					}
   520  				}
   521  			}
   522  		})
   523  	}
   524  }
   525  
   526  func TestHTTPClient_Auth_Basic(t *testing.T) {
   527  	tests := []struct {
   528  		name      string
   529  		opts      *HTTPClientOptions
   530  		urlSuffix string
   531  		queryArgs map[string]string
   532  		headers   map[string]string
   533  		wantStr   []string
   534  		wantErr   bool
   535  	}{
   536  		{"Nil Opts", nil, "/basic-auth/joe/secret", nil, nil, nil, true},
   537  		{"Empty Opts", &HTTPClientOptions{}, "/basic-auth/joe/secret", nil, nil, nil, true},
   538  		{"Incorrect User", &HTTPClientOptions{Username: "john", Password: "secret"}, "/basic-auth/joe/secret", nil, nil, nil, true},
   539  		{"Incorrect Password", &HTTPClientOptions{Username: "joe", Password: "nopass"}, "/basic-auth/joe/secret", nil, nil, nil, true},
   540  		{"Token Override", &HTTPClientOptions{Username: "joe", Password: "secret", BearerToken: "abc12345"}, "/basic-auth/joe/secret", nil, nil, nil, true},
   541  		{"Correct", &HTTPClientOptions{Username: "joe", Password: "secret"}, "/basic-auth/joe/secret", nil, nil, []string{"true", "joe"}, false},
   542  		{"Correct 2", &HTTPClientOptions{Username: "john", Password: "imagine"}, "/basic-auth/john/imagine", nil, nil, []string{"true", "john"}, false},
   543  	}
   544  	for _, tt := range tests {
   545  		t.Run(tt.name, func(t *testing.T) {
   546  			c := NewHTTPClient(tt.opts)
   547  			url := testHttpbinURL + tt.urlSuffix
   548  			var (
   549  				got []byte
   550  				err error
   551  			)
   552  			_ = BackOffRetryIf(func() error {
   553  				got, err = c.Get(url, tt.queryArgs, tt.headers)
   554  				return err
   555  			}, func(err error) bool {
   556  				return !strings.Contains(err.Error(), `status code: 502`)
   557  			}, testHTTPRetryTimes, Milliseconds(100))
   558  			// check resp
   559  			if (err != nil) != tt.wantErr {
   560  				t.Errorf("Auth Basic(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   561  				return
   562  			}
   563  			if !tt.wantErr && len(tt.wantStr) > 0 {
   564  				gs := string(got)
   565  				for _, s := range tt.wantStr {
   566  					if !strings.Contains(gs, s) {
   567  						t.Errorf("Auth Basic(%s) = %s, should contain %q", url, got, s)
   568  					}
   569  				}
   570  			}
   571  		})
   572  	}
   573  }
   574  
   575  func TestHTTPClient_Auth_Bearer(t *testing.T) {
   576  	tests := []struct {
   577  		name      string
   578  		opts      *HTTPClientOptions
   579  		urlSuffix string
   580  		queryArgs map[string]string
   581  		headers   map[string]string
   582  		wantStr   []string
   583  		wantErr   bool
   584  	}{
   585  		{"Nil Opts", nil, "/bearer", nil, nil, nil, true},
   586  		{"Empty Opts", &HTTPClientOptions{}, "/bearer", nil, nil, nil, true},
   587  		{"No Token", &HTTPClientOptions{BearerToken: ""}, "/bearer", nil, nil, nil, true},
   588  		{"Correct Token", &HTTPClientOptions{BearerToken: "abc12345"}, "/bearer", nil, nil, []string{"abc12345"}, false},
   589  		{"Duplicate User", &HTTPClientOptions{Username: "joe", Password: "secret", BearerToken: "abc12345"}, "/bearer", nil, nil, []string{"abc12345"}, false},
   590  	}
   591  	for _, tt := range tests {
   592  		t.Run(tt.name, func(t *testing.T) {
   593  			c := NewHTTPClient(tt.opts)
   594  			url := testHttpbinURL + tt.urlSuffix
   595  			var (
   596  				got []byte
   597  				err error
   598  			)
   599  			_ = BackOffRetryIf(func() error {
   600  				got, err = c.Get(url, tt.queryArgs, tt.headers)
   601  				return err
   602  			}, func(err error) bool {
   603  				return !strings.Contains(err.Error(), `status code: 502`)
   604  			}, testHTTPRetryTimes, Milliseconds(100))
   605  			// check resp
   606  			if (err != nil) != tt.wantErr {
   607  				t.Errorf("Auth Bearer(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   608  				return
   609  			}
   610  			if !tt.wantErr && len(tt.wantStr) > 0 {
   611  				gs := string(got)
   612  				for _, s := range tt.wantStr {
   613  					if !strings.Contains(gs, s) {
   614  						t.Errorf("Auth Bearer(%s) = %s, should contain %q", url, got, s)
   615  					}
   616  				}
   617  			}
   618  		})
   619  	}
   620  }
   621  
   622  func TestHTTPClient_Redirect(t *testing.T) {
   623  	defaultUA := defaultHTTPClientOpts.UserAgent
   624  	tests := []struct {
   625  		name      string
   626  		opts      *HTTPClientOptions
   627  		urlSuffix string
   628  		queryArgs map[string]string
   629  		headers   map[string]string
   630  		wantStr   []string
   631  		wantErr   bool
   632  	}{
   633  		{"Nil Opts", nil, "/redirect/1", nil, nil, []string{defaultUA, "/get"}, false},
   634  		{"Empty Opts", &HTTPClientOptions{}, "/redirect/1", nil, nil, []string{defaultUA, "/get"}, false},
   635  		{"Enable Redirect", &HTTPClientOptions{DisableRedirect: false}, "/redirect/1", nil, nil, []string{defaultUA, "/get"}, false},
   636  		{"Disable Redirect", &HTTPClientOptions{DisableRedirect: true}, "/redirect/1", nil, nil, nil, true},
   637  		{"Redirect Too Many", &HTTPClientOptions{DisableRedirect: false}, "/redirect/10", nil, nil, nil, true},
   638  	}
   639  	for _, tt := range tests {
   640  		t.Run(tt.name, func(t *testing.T) {
   641  			c := NewHTTPClient(tt.opts)
   642  			url := testHttpbinURL + tt.urlSuffix
   643  			var (
   644  				got []byte
   645  				err error
   646  			)
   647  			_ = BackOffRetryIf(func() error {
   648  				got, err = c.Get(url, tt.queryArgs, tt.headers)
   649  				return err
   650  			}, func(err error) bool {
   651  				return !strings.Contains(err.Error(), `status code: 502`)
   652  			}, testHTTPRetryTimes, Milliseconds(100))
   653  			// check resp
   654  			if (err != nil) != tt.wantErr {
   655  				t.Errorf("Redirect(%s) error = %v, wantErr %v", url, err, tt.wantErr)
   656  				return
   657  			}
   658  			if !tt.wantErr && len(tt.wantStr) > 0 {
   659  				gs := string(got)
   660  				for _, s := range tt.wantStr {
   661  					if !strings.Contains(gs, s) {
   662  						t.Errorf("Redirect(%s) = %s, should contain %q", url, got, s)
   663  					}
   664  				}
   665  			}
   666  		})
   667  	}
   668  }
   669  
   670  func TestHTTPClient_Error(t *testing.T) {
   671  	tests := []struct {
   672  		name      string
   673  		opts      *HTTPClientOptions
   674  		url       string
   675  		queryArgs map[string]string
   676  		headers   map[string]string
   677  		wantStr   []string
   678  		wantErr   bool
   679  	}{
   680  		{"No Host", nil, "https://example.invalid", nil, nil, nil, true},
   681  		{"Expired Cert", nil, "https://expired.badssl.com", nil, nil, nil, true},
   682  		{"Expired Cert Ignored", &HTTPClientOptions{Insecure: true}, "https://expired.badssl.com", nil, nil, []string{"expired"}, false},
   683  		{"Revoked Cert", nil, "https://revoked.badssl.com", nil, nil, nil, true},
   684  		{"Revoked Cert Ignored", &HTTPClientOptions{Insecure: true}, "https://revoked.badssl.com", nil, nil, []string{"revoked"}, false},
   685  		{"Wrong Host Cert", nil, "https://wrong.host.badssl.com", nil, nil, nil, true},
   686  		{"Wrong Host Cert Ignored", &HTTPClientOptions{Insecure: true}, "https://wrong.host.badssl.com", nil, nil, []string{"wrong"}, false},
   687  		{"Self Signed Cert", nil, "https://self-signed.badssl.com", nil, nil, nil, true},
   688  		{"Self Signed Cert Ignored", &HTTPClientOptions{Insecure: true}, "https://self-signed.badssl.com", nil, nil, []string{"self-signed"}, false},
   689  	}
   690  	for _, tt := range tests {
   691  		t.Run(tt.name, func(t *testing.T) {
   692  			c := NewHTTPClient(tt.opts)
   693  			var (
   694  				got []byte
   695  				err error
   696  			)
   697  			_ = BackOffRetryIf(func() error {
   698  				got, err = c.Get(tt.url, tt.queryArgs, tt.headers)
   699  				return err
   700  			}, func(err error) bool {
   701  				return !strings.Contains(err.Error(), `status code: 502`)
   702  			}, testHTTPRetryTimes, Milliseconds(100))
   703  			// check resp
   704  			if (err != nil) != tt.wantErr {
   705  				t.Errorf("Error(%s) error = %v, data = %d, wantErr %v", tt.url, err, len(got), tt.wantErr)
   706  				return
   707  			}
   708  			if !tt.wantErr && len(tt.wantStr) > 0 {
   709  				gs := string(got)
   710  				for _, s := range tt.wantStr {
   711  					if !strings.Contains(gs, s) {
   712  						t.Errorf("Error(%s) = %s, should contain %q", tt.url, got, s)
   713  					}
   714  				}
   715  			}
   716  		})
   717  	}
   718  }