github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/client_test.go (about)

     1  // Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
     2  // resty source code and usage is governed by a MIT style
     3  // license that can be found in the LICENSE file.
     4  
     5  package resty
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/tls"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"net/http"
    15  	"net/url"
    16  	"os"
    17  	"path/filepath"
    18  	"reflect"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  )
    26  
    27  func TestClientBasicAuth(t *testing.T) {
    28  	ts := createAuthServer(t)
    29  	defer ts.Close()
    30  
    31  	c := dc()
    32  	c.SetBasicAuth("myuser", "basicauth").
    33  		SetBaseURL(ts.URL).
    34  		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
    35  
    36  	resp, err := c.R().
    37  		SetResult(&AuthSuccess{}).
    38  		Post("/login")
    39  
    40  	assert.Nil(t, err)
    41  	assert.Equal(t, http.StatusOK, resp.StatusCode())
    42  
    43  	t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
    44  	logResponse(t, resp)
    45  }
    46  
    47  func TestClientAuthToken(t *testing.T) {
    48  	ts := createAuthServer(t)
    49  	defer ts.Close()
    50  
    51  	c := dc()
    52  	c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
    53  		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
    54  		SetBaseURL(ts.URL + "/")
    55  
    56  	resp, err := c.R().Get("/profile")
    57  
    58  	assert.Nil(t, err)
    59  	assert.Equal(t, http.StatusOK, resp.StatusCode())
    60  }
    61  
    62  func TestClientAuthScheme(t *testing.T) {
    63  	ts := createAuthServer(t)
    64  	defer ts.Close()
    65  
    66  	c := dc()
    67  	// Ensure default Bearer
    68  	c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
    69  		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
    70  		SetBaseURL(ts.URL + "/")
    71  
    72  	resp, err := c.R().Get("/profile")
    73  
    74  	assert.Nil(t, err)
    75  	assert.Equal(t, http.StatusOK, resp.StatusCode())
    76  
    77  	// Ensure setting the scheme works as well
    78  	c.SetAuthScheme("Bearer")
    79  
    80  	resp2, err2 := c.R().Get("/profile")
    81  	assert.Nil(t, err2)
    82  	assert.Equal(t, http.StatusOK, resp2.StatusCode())
    83  }
    84  
    85  func TestOnAfterMiddleware(t *testing.T) {
    86  	ts := createGenServer(t)
    87  	defer ts.Close()
    88  
    89  	c := dc()
    90  	c.OnAfterResponse(func(c *Client, res *Response) error {
    91  		t.Logf("Request sent at: %v", res.Request.Time)
    92  		t.Logf("Response Received at: %v", res.ReceivedAt())
    93  
    94  		return nil
    95  	})
    96  
    97  	resp, err := c.R().
    98  		SetBody("OnAfterResponse: This is plain text body to server").
    99  		Put(ts.URL + "/plaintext")
   100  
   101  	assert.Nil(t, err)
   102  	assert.Equal(t, http.StatusOK, resp.StatusCode())
   103  	assert.Equal(t, "TestPut: plain text response", resp.String())
   104  }
   105  
   106  func TestClientRedirectPolicy(t *testing.T) {
   107  	ts := createRedirectServer(t)
   108  	defer ts.Close()
   109  
   110  	c := dc().SetRedirectPolicy(FlexibleRedirectPolicy(20))
   111  	_, err := c.R().Get(ts.URL + "/redirect-1")
   112  
   113  	assert.Equal(t, true, "Get /redirect-21: stopped after 20 redirects" == err.Error() ||
   114  		"Get \"/redirect-21\": stopped after 20 redirects" == err.Error())
   115  
   116  	c.SetRedirectPolicy(NoRedirectPolicy())
   117  	_, err = c.R().Get(ts.URL + "/redirect-1")
   118  	assert.Equal(t, true, "Get /redirect-2: auto redirect is disabled" == err.Error() ||
   119  		"Get \"/redirect-2\": auto redirect is disabled" == err.Error())
   120  }
   121  
   122  func TestClientTimeout(t *testing.T) {
   123  	ts := createGetServer(t)
   124  	defer ts.Close()
   125  
   126  	c := dc().SetTimeout(time.Second * 3)
   127  	_, err := c.R().Get(ts.URL + "/set-timeout-test")
   128  
   129  	assert.Equal(t, true, strings.Contains(strings.ToLower(err.Error()), "timeout"))
   130  }
   131  
   132  func TestClientTimeoutWithinThreshold(t *testing.T) {
   133  	ts := createGetServer(t)
   134  	defer ts.Close()
   135  
   136  	c := dc().SetTimeout(time.Second * 3)
   137  	resp, err := c.R().Get(ts.URL + "/set-timeout-test-with-sequence")
   138  
   139  	assert.Nil(t, err)
   140  
   141  	seq1, _ := strconv.ParseInt(resp.String(), 10, 32)
   142  
   143  	resp, err = c.R().Get(ts.URL + "/set-timeout-test-with-sequence")
   144  	assert.Nil(t, err)
   145  
   146  	seq2, _ := strconv.ParseInt(resp.String(), 10, 32)
   147  
   148  	assert.Equal(t, seq1+1, seq2)
   149  }
   150  
   151  func TestClientTimeoutInternalError(t *testing.T) {
   152  	c := dc().SetTimeout(time.Second * 1)
   153  	_, _ = c.R().Get("http://localhost:9000/set-timeout-test")
   154  }
   155  
   156  func TestClientProxy(t *testing.T) {
   157  	ts := createGetServer(t)
   158  	defer ts.Close()
   159  
   160  	c := dc()
   161  	c.SetTimeout(1 * time.Second)
   162  	c.SetProxy("http://sampleproxy:8888")
   163  
   164  	resp, err := c.R().Get(ts.URL)
   165  	assert.NotNil(t, resp)
   166  	assert.NotNil(t, err)
   167  
   168  	// Error
   169  	c.SetProxy("//not.a.user@%66%6f%6f.com:8888")
   170  
   171  	resp, err = c.R().
   172  		Get(ts.URL)
   173  	assert.NotNil(t, err)
   174  	assert.NotNil(t, resp)
   175  }
   176  
   177  func TestClientSetCertificates(t *testing.T) {
   178  	client := dc()
   179  	client.SetCertificates(tls.Certificate{})
   180  
   181  	transport, err := client.transport()
   182  
   183  	assert.Nil(t, err)
   184  	assert.Equal(t, 1, len(transport.TLSClientConfig.Certificates))
   185  }
   186  
   187  func TestClientSetRootCertificate(t *testing.T) {
   188  	client := dc()
   189  	client.SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
   190  
   191  	transport, err := client.transport()
   192  
   193  	assert.Nil(t, err)
   194  	assert.NotNil(t, transport.TLSClientConfig.RootCAs)
   195  }
   196  
   197  func TestClientSetRootCertificateNotExists(t *testing.T) {
   198  	client := dc()
   199  	client.SetRootCertificate(filepath.Join(getTestDataPath(), "not-exists-sample-root.pem"))
   200  
   201  	transport, err := client.transport()
   202  
   203  	assert.Nil(t, err)
   204  	assert.Nil(t, transport.TLSClientConfig)
   205  }
   206  
   207  func TestClientSetRootCertificateFromString(t *testing.T) {
   208  	client := dc()
   209  	rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
   210  	assert.Nil(t, err)
   211  
   212  	client.SetRootCertificateFromString(string(rootPemData))
   213  
   214  	transport, err := client.transport()
   215  
   216  	assert.Nil(t, err)
   217  	assert.NotNil(t, transport.TLSClientConfig.RootCAs)
   218  }
   219  
   220  func TestClientSetRootCertificateFromStringErrorTls(t *testing.T) {
   221  	client := NewWithClient(&http.Client{})
   222  	client.outputLogTo(io.Discard)
   223  
   224  	rootPemData, err := os.ReadFile(filepath.Join(getTestDataPath(), "sample-root.pem"))
   225  	assert.Nil(t, err)
   226  	rt := &CustomRoundTripper{}
   227  	client.SetTransport(rt)
   228  	transport, err := client.transport()
   229  
   230  	client.SetRootCertificateFromString(string(rootPemData))
   231  
   232  	assert.NotNil(t, rt)
   233  	assert.NotNil(t, err)
   234  	assert.Nil(t, transport)
   235  }
   236  
   237  func TestClientOnBeforeRequestModification(t *testing.T) {
   238  	tc := dc()
   239  	tc.OnBeforeRequest(func(c *Client, r *Request) error {
   240  		r.SetAuthToken("This is test auth token")
   241  		return nil
   242  	})
   243  
   244  	ts := createGetServer(t)
   245  	defer ts.Close()
   246  
   247  	resp, err := tc.R().Get(ts.URL + "/")
   248  
   249  	assert.Nil(t, err)
   250  	assert.Equal(t, http.StatusOK, resp.StatusCode())
   251  	assert.Equal(t, "200 OK", resp.Status())
   252  	assert.NotNil(t, resp.Body())
   253  	assert.Equal(t, "TestGet: text response", resp.String())
   254  
   255  	logResponse(t, resp)
   256  }
   257  
   258  func TestClientSetHeaderVerbatim(t *testing.T) {
   259  	ts := createPostServer(t)
   260  	defer ts.Close()
   261  
   262  	c := dc().
   263  		SetHeaderVerbatim("header-lowercase", "value_lowercase").
   264  		SetHeader("header-lowercase", "value_standard")
   265  
   266  	assert.Equal(t, "value_lowercase", strings.Join(c.Header["header-lowercase"], "")) //nolint
   267  	assert.Equal(t, "value_standard", c.Header.Get("Header-Lowercase"))
   268  }
   269  
   270  func TestClientSetTransport(t *testing.T) {
   271  	ts := createGetServer(t)
   272  	defer ts.Close()
   273  	client := dc()
   274  
   275  	transport := &http.Transport{
   276  		// something like Proxying to httptest.Server, etc...
   277  		Proxy: func(req *http.Request) (*url.URL, error) {
   278  			return url.Parse(ts.URL)
   279  		},
   280  	}
   281  	client.SetTransport(transport)
   282  	transportInUse, err := client.transport()
   283  
   284  	assert.Nil(t, err)
   285  	assert.Equal(t, true, transport == transportInUse)
   286  }
   287  
   288  func TestClientSetScheme(t *testing.T) {
   289  	client := dc()
   290  
   291  	client.SetScheme("http")
   292  
   293  	assert.Equal(t, true, client.scheme == "http")
   294  }
   295  
   296  func TestClientSetCookieJar(t *testing.T) {
   297  	client := dc()
   298  	backupJar := client.httpClient.Jar
   299  
   300  	client.SetCookieJar(nil)
   301  	assert.Nil(t, client.httpClient.Jar)
   302  
   303  	client.SetCookieJar(backupJar)
   304  	assert.Equal(t, true, client.httpClient.Jar == backupJar)
   305  }
   306  
   307  func TestClientOptions(t *testing.T) {
   308  	client := dc()
   309  	client.SetContentLength(true)
   310  	assert.Equal(t, client.setContentLength, true)
   311  
   312  	client.SetBaseURL("http://httpbin.org")
   313  	assert.Equal(t, "http://httpbin.org", client.BaseURL)
   314  
   315  	client.SetHeader(hdrContentTypeKey, "application/json; charset=utf-8")
   316  	client.SetHeaders(map[string]string{
   317  		hdrUserAgentKey: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) go-resty v0.1",
   318  		"X-Request-Id":  strconv.FormatInt(time.Now().UnixNano(), 10),
   319  	})
   320  	assert.Equal(t, "application/json; charset=utf-8", client.Header.Get(hdrContentTypeKey))
   321  
   322  	client.SetCookies(&http.Cookie{
   323  		Name:  "default-cookie",
   324  		Value: "This is cookie default-cookie value",
   325  	})
   326  	assert.Equal(t, "default-cookie", client.Cookies[0].Name)
   327  
   328  	cookies := []*http.Cookie{
   329  		{
   330  			Name:  "default-cookie-1",
   331  			Value: "This is default-cookie 1 value",
   332  		}, {
   333  			Name:  "default-cookie-2",
   334  			Value: "This is default-cookie 2 value",
   335  		},
   336  	}
   337  	client.SetCookies(cookies...)
   338  	assert.Equal(t, "default-cookie-1", client.Cookies[1].Name)
   339  	assert.Equal(t, "default-cookie-2", client.Cookies[2].Name)
   340  
   341  	client.SetQueryParam("test_param_1", "Param_1")
   342  	client.SetQueryParams(map[string]string{"test_param_2": "Param_2", "test_param_3": "Param_3"})
   343  	assert.Equal(t, "Param_3", client.QueryParam.Get("test_param_3"))
   344  
   345  	rTime := strconv.FormatInt(time.Now().UnixNano(), 10)
   346  	client.SetFormData(map[string]string{"r_time": rTime})
   347  	assert.Equal(t, rTime, client.FormData.Get("r_time"))
   348  
   349  	client.SetBasicAuth("myuser", "mypass")
   350  	assert.Equal(t, "myuser", client.UserInfo.Username)
   351  
   352  	client.SetAuthToken("AC75BD37F019E08FBC594900518B4F7E")
   353  	assert.Equal(t, "AC75BD37F019E08FBC594900518B4F7E", client.Token)
   354  
   355  	client.SetDisableWarn(true)
   356  	assert.Equal(t, client.DisableWarn, true)
   357  
   358  	client.SetRetryCount(3)
   359  	assert.Equal(t, 3, client.RetryCount)
   360  
   361  	rwt := time.Duration(1000) * time.Millisecond
   362  	client.SetRetryWaitTime(rwt)
   363  	assert.Equal(t, rwt, client.RetryWaitTime)
   364  
   365  	mrwt := time.Duration(2) * time.Second
   366  	client.SetRetryMaxWaitTime(mrwt)
   367  	assert.Equal(t, mrwt, client.RetryMaxWaitTime)
   368  
   369  	client.AddRetryAfterErrorCondition()
   370  	// assert.True(t, reflect.DeepEqual(client.RetryConditions[0],
   371  	//	func(response *Response, err error) bool { return response.IsError() }))
   372  
   373  	err := &AuthError{}
   374  	client.SetError(err)
   375  	if reflect.TypeOf(err) == client.Error {
   376  		t.Error("SetError failed")
   377  	}
   378  
   379  	client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
   380  	transport, transportErr := client.transport()
   381  
   382  	assert.Nil(t, transportErr)
   383  	assert.Equal(t, true, transport.TLSClientConfig.InsecureSkipVerify)
   384  
   385  	client.OnBeforeRequest(func(c *Client, r *Request) error {
   386  		c.log.Debugf("I'm in Request middleware")
   387  		return nil // if it success
   388  	})
   389  	client.OnAfterResponse(func(c *Client, r *Response) error {
   390  		c.log.Debugf("I'm in Response middleware")
   391  		return nil // if it success
   392  	})
   393  
   394  	client.SetTimeout(5 * time.Second)
   395  	client.SetRedirectPolicy(FlexibleRedirectPolicy(10), func(req *http.Request, via []*http.Request) error {
   396  		return errors.New("sample test redirect")
   397  	})
   398  	client.SetContentLength(true)
   399  
   400  	client.SetDebug(true)
   401  	assert.Equal(t, client.Debug, true)
   402  
   403  	var sl int64 = 1000000
   404  	client.SetDebugBodyLimit(sl)
   405  	assert.Equal(t, client.debugBodySizeLimit, sl)
   406  
   407  	client.SetAllowGetMethodPayload(true)
   408  	assert.Equal(t, client.AllowGetMethodPayload, true)
   409  
   410  	client.SetScheme("http")
   411  	assert.Equal(t, client.scheme, "http")
   412  
   413  	client.SetCloseConnection(true)
   414  	assert.Equal(t, client.closeConnection, true)
   415  }
   416  
   417  func TestClientPreRequestHook(t *testing.T) {
   418  	client := dc()
   419  	client.SetPreRequestHook(func(c *Client, r *http.Request) error {
   420  		c.log.Debugf("I'm in Pre-Request Hook")
   421  		return nil
   422  	})
   423  
   424  	client.SetPreRequestHook(func(c *Client, r *http.Request) error {
   425  		c.log.Debugf("I'm Overwriting existing Pre-Request Hook")
   426  
   427  		// Reading Request `N` no of times
   428  		for i := 0; i < 5; i++ {
   429  			b, _ := r.GetBody()
   430  			rb, _ := io.ReadAll(b)
   431  			c.log.Debugf("%s %v", string(rb), len(rb))
   432  			assert.Equal(t, true, len(rb) >= 45)
   433  		}
   434  		return nil
   435  	})
   436  
   437  	ts := createPostServer(t)
   438  	defer ts.Close()
   439  
   440  	// Regular bodybuf use case
   441  	resp, _ := client.R().
   442  		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
   443  		Post(ts.URL + "/login")
   444  	assert.Equal(t, http.StatusOK, resp.StatusCode())
   445  	assert.Equal(t, `{ "id": "success", "message": "login successful" }`, resp.String())
   446  
   447  	// io.Reader body use case
   448  	resp, _ = client.R().
   449  		SetHeader(hdrContentTypeKey, jsonContentType).
   450  		SetBody(bytes.NewReader([]byte(`{"username":"testuser", "password":"testpass"}`))).
   451  		Post(ts.URL + "/login")
   452  	assert.Equal(t, http.StatusOK, resp.StatusCode())
   453  	assert.Equal(t, `{ "id": "success", "message": "login successful" }`, resp.String())
   454  }
   455  
   456  func TestClientAllowsGetMethodPayload(t *testing.T) {
   457  	ts := createGetServer(t)
   458  	defer ts.Close()
   459  
   460  	c := dc()
   461  	c.SetAllowGetMethodPayload(true)
   462  	c.SetPreRequestHook(func(*Client, *http.Request) error { return nil }) // for coverage
   463  
   464  	payload := "test-payload"
   465  	resp, err := c.R().SetBody(payload).Get(ts.URL + "/get-method-payload-test")
   466  
   467  	assert.Nil(t, err)
   468  	assert.Equal(t, http.StatusOK, resp.StatusCode())
   469  	assert.Equal(t, payload, resp.String())
   470  }
   471  
   472  func TestClientRoundTripper(t *testing.T) {
   473  	c := NewWithClient(&http.Client{})
   474  	c.outputLogTo(io.Discard)
   475  
   476  	rt := &CustomRoundTripper{}
   477  	c.SetTransport(rt)
   478  
   479  	ct, err := c.transport()
   480  	assert.NotNil(t, err)
   481  	assert.Nil(t, ct)
   482  	assert.Equal(t, "current transport is not an *http.Transport instance", err.Error())
   483  
   484  	c.SetTLSClientConfig(&tls.Config{})
   485  	c.SetProxy("http://localhost:9090")
   486  	c.RemoveProxy()
   487  	c.SetCertificates(tls.Certificate{})
   488  	c.SetRootCertificate(filepath.Join(getTestDataPath(), "sample-root.pem"))
   489  }
   490  
   491  func TestClientNewRequest(t *testing.T) {
   492  	c := New()
   493  	request := c.NewRequest()
   494  	assert.NotNil(t, request)
   495  }
   496  
   497  func TestDebugBodySizeLimit(t *testing.T) {
   498  	ts := createGetServer(t)
   499  	defer ts.Close()
   500  
   501  	var lgr bytes.Buffer
   502  	c := dc()
   503  	c.SetDebug(true)
   504  	c.SetDebugBodyLimit(30)
   505  	c.outputLogTo(&lgr)
   506  
   507  	testcases := []struct{ url, want string }{
   508  		// Text, does not exceed limit.
   509  		{ts.URL, "TestGet: text response"},
   510  		// Empty response.
   511  		{ts.URL + "/no-content", "***** NO CONTENT *****"},
   512  		// JSON, does not exceed limit.
   513  		{ts.URL + "/json", "{\n   \"TestGet\": \"JSON response\"\n}"},
   514  		// Invalid JSON, does not exceed limit.
   515  		{ts.URL + "/json-invalid", "TestGet: Invalid JSON"},
   516  		// Text, exceeds limit.
   517  		{ts.URL + "/long-text", "RESPONSE TOO LARGE"},
   518  		// JSON, exceeds limit.
   519  		{ts.URL + "/long-json", "RESPONSE TOO LARGE"},
   520  	}
   521  
   522  	for _, tc := range testcases {
   523  		_, err := c.R().Get(tc.url)
   524  		assert.Nil(t, err)
   525  		debugLog := lgr.String()
   526  		if !strings.Contains(debugLog, tc.want) {
   527  			t.Errorf("Expected logs to contain [%v], got [\n%v]", tc.want, debugLog)
   528  		}
   529  		lgr.Reset()
   530  	}
   531  }
   532  
   533  // CustomRoundTripper just for test
   534  type CustomRoundTripper struct{}
   535  
   536  // RoundTrip just for test
   537  func (rt *CustomRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
   538  	return &http.Response{}, nil
   539  }
   540  
   541  func TestAutoGzip(t *testing.T) {
   542  	ts := createGenServer(t)
   543  	defer ts.Close()
   544  
   545  	c := New()
   546  	testcases := []struct{ url, want string }{
   547  		{ts.URL + "/gzip-test", "This is Gzip response testing"},
   548  		{ts.URL + "/gzip-test-gziped-empty-body", ""},
   549  		{ts.URL + "/gzip-test-no-gziped-body", ""},
   550  	}
   551  	for _, tc := range testcases {
   552  		resp, err := c.R().
   553  			SetHeader("Accept-Encoding", "gzip").
   554  			Get(tc.url)
   555  
   556  		assert.Nil(t, err)
   557  		assert.Equal(t, http.StatusOK, resp.StatusCode())
   558  		assert.Equal(t, "200 OK", resp.Status())
   559  		assert.NotNil(t, resp.Body())
   560  		assert.Equal(t, tc.want, resp.String())
   561  
   562  		logResponse(t, resp)
   563  	}
   564  }
   565  
   566  func TestLogCallbacks(t *testing.T) {
   567  	ts := createAuthServer(t)
   568  	defer ts.Close()
   569  
   570  	c := New().SetDebug(true)
   571  
   572  	var lgr bytes.Buffer
   573  	c.outputLogTo(&lgr)
   574  
   575  	c.OnRequestLog(func(r *RequestLog) error {
   576  		// masking authorzation header
   577  		r.Header.Set("Authorization", "Bearer *******************************")
   578  		return nil
   579  	})
   580  	c.OnResponseLog(func(r *ResponseLog) error {
   581  		r.Header.Add("X-Debug-Resposne-Log", "Modified :)")
   582  		r.Body += "\nModified the response body content"
   583  		return nil
   584  	})
   585  
   586  	c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
   587  		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF")
   588  
   589  	resp, err := c.R().
   590  		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF-Request").
   591  		Get(ts.URL + "/profile")
   592  
   593  	assert.Nil(t, err)
   594  	assert.Equal(t, http.StatusOK, resp.StatusCode())
   595  
   596  	// Validating debug log updates
   597  	logInfo := lgr.String()
   598  	assert.Equal(t, true, strings.Contains(logInfo, "Bearer *******************************"))
   599  	assert.Equal(t, true, strings.Contains(logInfo, "X-Debug-Resposne-Log"))
   600  	assert.Equal(t, true, strings.Contains(logInfo, "Modified the response body content"))
   601  
   602  	// Error scenario
   603  	c.OnRequestLog(func(r *RequestLog) error { return errors.New("request test error") })
   604  	resp, err = c.R().
   605  		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF-Request").
   606  		Get(ts.URL + "/profile")
   607  	assert.Equal(t, errors.New("request test error"), err)
   608  	assert.Nil(t, resp)
   609  	assert.NotNil(t, err)
   610  
   611  	c.OnRequestLog(nil)
   612  	c.OnResponseLog(func(r *ResponseLog) error { return errors.New("response test error") })
   613  	resp, err = c.R().
   614  		SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF-Request").
   615  		Get(ts.URL + "/profile")
   616  	assert.Equal(t, errors.New("response test error"), err)
   617  	assert.NotNil(t, resp)
   618  }
   619  
   620  func TestNewWithLocalAddr(t *testing.T) {
   621  	ts := createGetServer(t)
   622  	defer ts.Close()
   623  
   624  	localAddress, _ := net.ResolveTCPAddr("tcp", "127.0.0.1")
   625  	client := NewWithLocalAddr(localAddress)
   626  	client.SetBaseURL(ts.URL)
   627  
   628  	resp, err := client.R().Get("/")
   629  	assert.Nil(t, err)
   630  	assert.Equal(t, resp.String(), "TestGet: text response")
   631  }
   632  
   633  func TestClientOnResponseError(t *testing.T) {
   634  	ts := createAuthServer(t)
   635  	defer ts.Close()
   636  
   637  	tests := []struct {
   638  		name        string
   639  		setup       func(*Client)
   640  		isError     bool
   641  		hasResponse bool
   642  	}{
   643  		{
   644  			name: "successful_request",
   645  		},
   646  		{
   647  			name: "http_status_error",
   648  			setup: func(client *Client) {
   649  				client.SetAuthToken("BAD")
   650  			},
   651  		},
   652  		{
   653  			name: "before_request_error",
   654  			setup: func(client *Client) {
   655  				client.OnBeforeRequest(func(client *Client, request *Request) error {
   656  					return fmt.Errorf("before request")
   657  				})
   658  			},
   659  			isError: true,
   660  		},
   661  		{
   662  			name: "before_request_error_retry",
   663  			setup: func(client *Client) {
   664  				client.SetRetryCount(3).OnBeforeRequest(func(client *Client, request *Request) error {
   665  					return fmt.Errorf("before request")
   666  				})
   667  			},
   668  			isError: true,
   669  		},
   670  		{
   671  			name: "after_response_error",
   672  			setup: func(client *Client) {
   673  				client.OnAfterResponse(func(client *Client, response *Response) error {
   674  					return fmt.Errorf("after response")
   675  				})
   676  			},
   677  			isError:     true,
   678  			hasResponse: true,
   679  		},
   680  		{
   681  			name: "after_response_error_retry",
   682  			setup: func(client *Client) {
   683  				client.SetRetryCount(3).OnAfterResponse(func(client *Client, response *Response) error {
   684  					return fmt.Errorf("after response")
   685  				})
   686  			},
   687  			isError:     true,
   688  			hasResponse: true,
   689  		},
   690  	}
   691  
   692  	for _, test := range tests {
   693  		t.Run(test.name, func(t *testing.T) {
   694  			assertErrorHook := func(r *Request, err error) {
   695  				assert.NotNil(t, r)
   696  				v, ok := err.(*ResponseError)
   697  				assert.Equal(t, test.hasResponse, ok)
   698  				if ok {
   699  					assert.NotNil(t, v.Response)
   700  					assert.NotNil(t, v.Err)
   701  				}
   702  			}
   703  			var hook1, hook2 int
   704  			c := New().outputLogTo(io.Discard).
   705  				SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
   706  				SetAuthToken("004DDB79-6801-4587-B976-F093E6AC44FF").
   707  				SetRetryCount(0).
   708  				SetRetryMaxWaitTime(time.Microsecond).
   709  				AddRetryCondition(func(response *Response, err error) bool {
   710  					if err != nil {
   711  						return true
   712  					}
   713  					return response.IsError()
   714  				}).
   715  				OnError(func(r *Request, err error) {
   716  					assertErrorHook(r, err)
   717  					hook1++
   718  				}).
   719  				OnError(func(r *Request, err error) {
   720  					assertErrorHook(r, err)
   721  					hook2++
   722  				})
   723  			if test.setup != nil {
   724  				test.setup(c)
   725  			}
   726  			_, err := c.R().Get(ts.URL + "/profile")
   727  			if test.isError {
   728  				assert.NotNil(t, err)
   729  				assert.Equal(t, 1, hook1)
   730  				assert.Equal(t, 1, hook2)
   731  			} else {
   732  				assert.Nil(t, err)
   733  			}
   734  		})
   735  	}
   736  }
   737  
   738  func TestResponseError(t *testing.T) {
   739  	err := errors.New("error message")
   740  	re := &ResponseError{
   741  		Response: &Response{},
   742  		Err:      err,
   743  	}
   744  	assert.NotNil(t, re.Unwrap())
   745  	assert.Equal(t, err.Error(), re.Error())
   746  }
   747  
   748  func TestHostURLForGH318AndGH407(t *testing.T) {
   749  	ts := createPostServer(t)
   750  	defer ts.Close()
   751  
   752  	targetURL, _ := url.Parse(ts.URL)
   753  	t.Log("ts.URL:", ts.URL)
   754  	t.Log("targetURL.Host:", targetURL.Host)
   755  	// Sample output
   756  	// ts.URL: http://127.0.0.1:55967
   757  	// targetURL.Host: 127.0.0.1:55967
   758  
   759  	// Unable use the local http test server for this
   760  	// use case testing
   761  	//
   762  	// using `targetURL.Host` value or test case yield to ERROR
   763  	// "parse "127.0.0.1:55967": first path segment in URL cannot contain colon"
   764  
   765  	// test the functionality with httpbin.org locally
   766  	// will figure out later
   767  
   768  	c := dc()
   769  	// c.SetScheme("http")
   770  	// c.SetHostURL(targetURL.Host + "/")
   771  
   772  	// t.Log("with leading `/`")
   773  	// resp, err := c.R().Post("/login")
   774  	// assert.Nil(t, err)
   775  	// assert.NotNil(t, resp)
   776  
   777  	// t.Log("\nwithout leading `/`")
   778  	// resp, err = c.R().Post("login")
   779  	// assert.Nil(t, err)
   780  	// assert.NotNil(t, resp)
   781  
   782  	t.Log("with leading `/` on request & with trailing `/` on host url")
   783  	c.SetBaseURL(ts.URL + "/")
   784  	resp, err := c.R().
   785  		SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
   786  		Post("/login")
   787  	assert.Nil(t, err)
   788  	assert.NotNil(t, resp)
   789  }