github.com/jaylevin/jenkins-library@v1.230.4/pkg/http/http_test.go (about)

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/base64"
     8  	"encoding/xml"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"mime/multipart"
    14  	"net/http"
    15  	"net/http/httptest"
    16  	"os"
    17  	"path/filepath"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/jarcoal/httpmock"
    22  	"github.com/sirupsen/logrus"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/mock"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"github.com/SAP/jenkins-library/pkg/log"
    28  )
    29  
    30  func TestSend(t *testing.T) {
    31  	testURL := "https://example.org"
    32  
    33  	request, err := http.NewRequest(http.MethodGet, testURL, nil)
    34  	require.NoError(t, err)
    35  
    36  	t.Run("success", func(t *testing.T) {
    37  		// given
    38  		httpmock.Activate()
    39  		defer httpmock.DeactivateAndReset()
    40  		httpmock.RegisterResponder(http.MethodGet, testURL, httpmock.NewStringResponder(200, `OK`))
    41  		client := Client{}
    42  		client.SetOptions(ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
    43  		// when
    44  		response, err := client.Send(request)
    45  		// then
    46  		assert.NoError(t, err)
    47  		assert.NotNil(t, response)
    48  	})
    49  	t.Run("failure", func(t *testing.T) {
    50  		// given
    51  		httpmock.Activate()
    52  		defer httpmock.DeactivateAndReset()
    53  		httpmock.RegisterResponder(http.MethodGet, testURL, httpmock.NewErrorResponder(errors.New("failure")))
    54  		client := Client{}
    55  		client.SetOptions(ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
    56  		// when
    57  		response, err := client.Send(request)
    58  		// then
    59  		assert.Error(t, err)
    60  		assert.Nil(t, response)
    61  	})
    62  }
    63  
    64  func TestDefaultTransport(t *testing.T) {
    65  	const testURL string = "https://localhost/api"
    66  
    67  	t.Run("with default transport", func(t *testing.T) {
    68  		httpmock.Activate()
    69  		defer httpmock.DeactivateAndReset()
    70  		httpmock.RegisterResponder(http.MethodGet, testURL, httpmock.NewStringResponder(200, `OK`))
    71  
    72  		client := Client{}
    73  		client.SetOptions(ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
    74  		// test
    75  		response, err := client.SendRequest("GET", testURL, nil, nil, nil)
    76  		// assert
    77  		assert.NoError(t, err)
    78  		// assert.NotEmpty(t, count)
    79  		assert.Equal(t, 1, httpmock.GetTotalCallCount(), "unexpected number of requests")
    80  		content, err := ioutil.ReadAll(response.Body)
    81  		defer response.Body.Close()
    82  		require.NoError(t, err, "unexpected error while reading response body")
    83  		assert.Equal(t, "OK", string(content), "unexpected response content")
    84  	})
    85  	t.Run("with custom transport", func(t *testing.T) {
    86  		httpmock.Activate()
    87  		defer httpmock.DeactivateAndReset()
    88  		httpmock.RegisterResponder(http.MethodGet, testURL, httpmock.NewStringResponder(200, `OK`))
    89  
    90  		client := Client{}
    91  		// test
    92  		_, err := client.SendRequest("GET", testURL, nil, nil, nil)
    93  		// assert
    94  		assert.Error(t, err)
    95  		assert.Contains(t, err.Error(), "connection")
    96  		assert.Contains(t, err.Error(), "refused")
    97  		assert.Equal(t, 0, httpmock.GetTotalCallCount(), "unexpected number of requests")
    98  	})
    99  }
   100  
   101  func TestSendRequest(t *testing.T) {
   102  	var passedHeaders = map[string][]string{}
   103  	passedCookies := []*http.Cookie{}
   104  	var passedUsername string
   105  	var passedPassword string
   106  	// Start a local HTTP server
   107  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   108  		passedHeaders = map[string][]string{}
   109  		if req.Header != nil {
   110  			for name, headers := range req.Header {
   111  				passedHeaders[name] = headers
   112  			}
   113  		}
   114  		passedCookies = req.Cookies()
   115  		passedUsername, passedPassword, _ = req.BasicAuth()
   116  
   117  		rw.Write([]byte("OK"))
   118  	}))
   119  	// Close the server when test finishes
   120  	defer server.Close()
   121  
   122  	oldLogLevel := logrus.GetLevel()
   123  	defer logrus.SetLevel(oldLogLevel)
   124  	logrus.SetLevel(logrus.DebugLevel)
   125  
   126  	tt := []struct {
   127  		client   Client
   128  		method   string
   129  		body     io.Reader
   130  		header   http.Header
   131  		cookies  []*http.Cookie
   132  		expected string
   133  	}{
   134  		{client: Client{logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}, method: "GET", expected: "OK"},
   135  		{client: Client{logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}, method: "GET", header: map[string][]string{"Testheader": {"Test1", "Test2"}}, expected: "OK"},
   136  		{client: Client{logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}, cookies: []*http.Cookie{{Name: "TestCookie1", Value: "TestValue1"}, {Name: "TestCookie2", Value: "TestValue2"}}, method: "GET", expected: "OK"},
   137  		{client: Client{logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http"), username: "TestUser", password: "TestPwd"}, method: "GET", expected: "OK"},
   138  	}
   139  
   140  	for key, test := range tt {
   141  		t.Run(fmt.Sprintf("Row %v", key+1), func(t *testing.T) {
   142  			oldLogOutput := test.client.logger.Logger.Out
   143  			defer func() { test.client.logger.Logger.Out = oldLogOutput }()
   144  			logBuffer := new(bytes.Buffer)
   145  			test.client.logger.Logger.Out = logBuffer
   146  
   147  			response, err := test.client.SendRequest("GET", server.URL, test.body, test.header, test.cookies)
   148  			assert.NoError(t, err, "Error occurred but none expected")
   149  			content, err := ioutil.ReadAll(response.Body)
   150  			assert.Equal(t, test.expected, string(content), "Returned content incorrect")
   151  			response.Body.Close()
   152  
   153  			for k, h := range test.header {
   154  				assert.Containsf(t, passedHeaders, k, "Header %v not contained", k)
   155  				assert.Equalf(t, h, passedHeaders[k], "Header %v contains different value")
   156  			}
   157  
   158  			if len(test.cookies) > 0 {
   159  				assert.Equal(t, test.cookies, passedCookies, "Passed cookies not correct")
   160  			}
   161  
   162  			if len(test.client.username) > 0 || len(test.client.password) > 0 {
   163  				if len(test.client.username) == 0 || len(test.client.password) == 0 {
   164  					//"User and password must both be provided"
   165  					t.Fail()
   166  				}
   167  				assert.Equal(t, test.client.username, passedUsername)
   168  				assert.Equal(t, test.client.password, passedPassword)
   169  
   170  				log := fmt.Sprintf("%s", logBuffer)
   171  				credentialsEncoded := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", test.client.username, test.client.password)))
   172  				assert.NotContains(t, log, fmt.Sprintf("Authorization:[Basic %s]", credentialsEncoded))
   173  				assert.Contains(t, log, "Authorization:[<set>]")
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func TestSetOptions(t *testing.T) {
   180  	c := Client{}
   181  	opts := ClientOptions{MaxRetries: -1, TransportTimeout: 10, MaxRequestDuration: 5, Username: "TestUser", Password: "TestPassword", Token: "TestToken", Logger: log.Entry().WithField("package", "github.com/SAP/jenkins-library/pkg/http")}
   182  	c.SetOptions(opts)
   183  
   184  	assert.Equal(t, opts.TransportTimeout, c.transportTimeout)
   185  	assert.Equal(t, opts.TransportSkipVerification, c.transportSkipVerification)
   186  	assert.Equal(t, opts.MaxRequestDuration, c.maxRequestDuration)
   187  	assert.Equal(t, opts.Username, c.username)
   188  	assert.Equal(t, opts.Password, c.password)
   189  	assert.Equal(t, opts.Token, c.token)
   190  }
   191  
   192  func TestApplyDefaults(t *testing.T) {
   193  	tt := []struct {
   194  		client   Client
   195  		expected Client
   196  	}{
   197  		{client: Client{}, expected: Client{transportTimeout: 3 * time.Minute, maxRequestDuration: 0, logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}},
   198  		{client: Client{transportTimeout: 10, maxRequestDuration: 5}, expected: Client{transportTimeout: 10, maxRequestDuration: 5, logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}},
   199  	}
   200  
   201  	for k, v := range tt {
   202  		v.client.applyDefaults()
   203  		assert.Equal(t, v.expected, v.client, fmt.Sprintf("Run %v failed", k))
   204  	}
   205  }
   206  
   207  func TestUploadRequest(t *testing.T) {
   208  	var passedHeaders = map[string][]string{}
   209  	passedCookies := []*http.Cookie{}
   210  	var passedUsername string
   211  	var passedPassword string
   212  	var multipartFile multipart.File
   213  	var multipartHeader *multipart.FileHeader
   214  	var passedFileContents []byte
   215  	// Start a local HTTP server
   216  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   217  		passedHeaders = map[string][]string{}
   218  		if req.Header != nil {
   219  			for name, headers := range req.Header {
   220  				passedHeaders[name] = headers
   221  			}
   222  		}
   223  		passedCookies = req.Cookies()
   224  		passedUsername, passedPassword, _ = req.BasicAuth()
   225  		err := req.ParseMultipartForm(4096)
   226  		if err != nil {
   227  			t.FailNow()
   228  		}
   229  		multipartFile, multipartHeader, err = req.FormFile("Field1")
   230  		if err != nil {
   231  			t.FailNow()
   232  		}
   233  		defer req.Body.Close()
   234  		passedFileContents, err = ioutil.ReadAll(multipartFile)
   235  		if err != nil {
   236  			t.FailNow()
   237  		}
   238  
   239  		rw.Write([]byte("OK"))
   240  	}))
   241  	// Close the server when test finishes
   242  	defer server.Close()
   243  
   244  	testFile, err := ioutil.TempFile("", "testFileUpload")
   245  	if err != nil {
   246  		t.FailNow()
   247  	}
   248  	defer os.RemoveAll(testFile.Name()) // clean up
   249  
   250  	fileContents, err := ioutil.ReadFile(testFile.Name())
   251  	if err != nil {
   252  		t.FailNow()
   253  	}
   254  
   255  	tt := []struct {
   256  		clientOptions ClientOptions
   257  		method        string
   258  		body          io.Reader
   259  		header        http.Header
   260  		cookies       []*http.Cookie
   261  		expected      string
   262  	}{
   263  		{clientOptions: ClientOptions{MaxRetries: -1}, method: "PUT", expected: "OK"},
   264  		{clientOptions: ClientOptions{MaxRetries: -1}, method: "POST", expected: "OK"},
   265  		{clientOptions: ClientOptions{MaxRetries: -1}, method: "POST", header: map[string][]string{"Testheader": {"Test1", "Test2"}}, expected: "OK"},
   266  		{clientOptions: ClientOptions{MaxRetries: -1}, cookies: []*http.Cookie{{Name: "TestCookie1", Value: "TestValue1"}, {Name: "TestCookie2", Value: "TestValue2"}}, method: "POST", expected: "OK"},
   267  		{clientOptions: ClientOptions{MaxRetries: -1, Username: "TestUser", Password: "TestPwd"}, method: "POST", expected: "OK"},
   268  		{clientOptions: ClientOptions{MaxRetries: -1, Username: "UserOnly", Password: ""}, method: "POST", expected: "OK"},
   269  	}
   270  
   271  	client := Client{logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}
   272  	for key, test := range tt {
   273  		t.Run(fmt.Sprintf("UploadFile Row %v", key+1), func(t *testing.T) {
   274  			client.SetOptions(test.clientOptions)
   275  			response, err := client.UploadFile(server.URL, testFile.Name(), "Field1", test.header, test.cookies, "form")
   276  			assert.NoError(t, err, "Error occurred but none expected")
   277  			content, err := ioutil.ReadAll(response.Body)
   278  			assert.NoError(t, err, "Error occurred but none expected")
   279  			assert.Equal(t, test.expected, string(content), "Returned content incorrect")
   280  			response.Body.Close()
   281  
   282  			assert.Equal(t, filepath.Base(testFile.Name()), multipartHeader.Filename, "Uploaded file incorrect")
   283  			assert.Equal(t, fileContents, passedFileContents, "Uploaded file incorrect")
   284  
   285  			for k, h := range test.header {
   286  				assert.Containsf(t, passedHeaders, k, "Header %v not contained", k)
   287  				assert.Equalf(t, h, passedHeaders[k], "Header %v contains different value")
   288  			}
   289  
   290  			if len(test.cookies) > 0 {
   291  				assert.Equal(t, test.cookies, passedCookies, "Passed cookies not correct")
   292  			}
   293  
   294  			if len(client.username) > 0 {
   295  				assert.Equal(t, client.username, passedUsername)
   296  			}
   297  
   298  			if len(client.password) > 0 {
   299  				assert.Equal(t, client.password, passedPassword)
   300  			}
   301  		})
   302  		t.Run(fmt.Sprintf("UploadRequest Row %v", key+1), func(t *testing.T) {
   303  			client.SetOptions(test.clientOptions)
   304  			response, err := client.UploadRequest(test.method, server.URL, testFile.Name(), "Field1", test.header, test.cookies, "form")
   305  			assert.NoError(t, err, "Error occurred but none expected")
   306  			content, err := ioutil.ReadAll(response.Body)
   307  			assert.NoError(t, err, "Error occurred but none expected")
   308  			assert.Equal(t, test.expected, string(content), "Returned content incorrect")
   309  			response.Body.Close()
   310  
   311  			assert.Equal(t, filepath.Base(testFile.Name()), multipartHeader.Filename, "Uploaded file incorrect")
   312  			assert.Equal(t, fileContents, passedFileContents, "Uploaded file incorrect")
   313  
   314  			for k, h := range test.header {
   315  				assert.Containsf(t, passedHeaders, k, "Header %v not contained", k)
   316  				assert.Equalf(t, h, passedHeaders[k], "Header %v contains different value")
   317  			}
   318  
   319  			if len(test.cookies) > 0 {
   320  				assert.Equal(t, test.cookies, passedCookies, "Passed cookies not correct")
   321  			}
   322  
   323  			if len(client.username) > 0 {
   324  				assert.Equal(t, client.username, passedUsername)
   325  			}
   326  
   327  			if len(client.password) > 0 {
   328  				assert.Equal(t, client.password, passedPassword)
   329  			}
   330  		})
   331  	}
   332  }
   333  
   334  func TestUploadRequestWrongMethod(t *testing.T) {
   335  	client := Client{logger: log.Entry().WithField("package", "SAP/jenkins-library/pkg/http")}
   336  	_, err := client.UploadRequest("GET", "dummy", "testFile", "Field1", nil, nil, "form")
   337  	assert.Error(t, err, "No error occurred but was expected")
   338  }
   339  
   340  func TestTransportTimout(t *testing.T) {
   341  	t.Run("timeout works on transport level", func(t *testing.T) {
   342  		// init
   343  		svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   344  			// Sleep for longer than the configured timeout
   345  			time.Sleep(2 * time.Second)
   346  		}))
   347  		defer svr.Close()
   348  
   349  		client := Client{transportTimeout: 1 * time.Second}
   350  		buffer := bytes.Buffer{}
   351  
   352  		// test
   353  		_, err := client.SendRequest(http.MethodGet, svr.URL, &buffer, nil, nil)
   354  		// assert
   355  		if assert.Error(t, err, "expected request to fail") {
   356  			assert.Contains(t, err.Error(), "timeout awaiting response headers")
   357  		}
   358  	})
   359  	t.Run("timeout is not hit on transport level", func(t *testing.T) {
   360  		// init
   361  		svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   362  			// Sleep for less than the configured timeout
   363  			time.Sleep(1 * time.Second)
   364  		}))
   365  		defer svr.Close()
   366  
   367  		client := Client{transportTimeout: 2 * time.Second}
   368  		buffer := bytes.Buffer{}
   369  		// test
   370  		_, err := client.SendRequest(http.MethodGet, svr.URL, &buffer, nil, nil)
   371  		// assert
   372  		assert.NoError(t, err)
   373  	})
   374  }
   375  
   376  func TestTransportSkipVerification(t *testing.T) {
   377  	testCases := []struct {
   378  		client        Client
   379  		expectedError string
   380  	}{
   381  		{client: Client{}, expectedError: "certificate signed by unknown authority"},
   382  		{client: Client{transportSkipVerification: false}, expectedError: "certificate signed by unknown authority"},
   383  		{client: Client{transportSkipVerification: true}},
   384  	}
   385  
   386  	for _, testCase := range testCases {
   387  		// init
   388  		svr := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   389  		defer svr.Close()
   390  		// test
   391  		_, err := testCase.client.SendRequest(http.MethodGet, svr.URL, &bytes.Buffer{}, nil, nil)
   392  		// assert
   393  		if len(testCase.expectedError) > 0 {
   394  			assert.Error(t, err, "certificate signed by unknown authority")
   395  		} else {
   396  			assert.NoError(t, err)
   397  		}
   398  	}
   399  }
   400  
   401  func TestTransportWithCertifacteAdded(t *testing.T) {
   402  	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   403  		io.WriteString(w, "Hello")
   404  	}))
   405  	defer server.Close()
   406  
   407  	certs := x509.NewCertPool()
   408  	for _, c := range server.TLS.Certificates {
   409  		roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
   410  		if err != nil {
   411  			println("error parsing server's root cert: %v", err)
   412  		}
   413  		for _, root := range roots {
   414  			certs.AddCert(root)
   415  		}
   416  	}
   417  	client := http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: certs, InsecureSkipVerify: false}}}
   418  	_, err := client.Get(server.URL)
   419  	// assert
   420  	assert.NoError(t, err)
   421  }
   422  
   423  func TestMaxRetries(t *testing.T) {
   424  	testCases := []struct {
   425  		client       Client
   426  		countedCalls int
   427  		method       string
   428  		responseCode int
   429  		errorText    string
   430  		timeout      bool
   431  	}{
   432  		{client: Client{maxRetries: 1, useDefaultTransport: true, transportSkipVerification: true, transportTimeout: 1 * time.Microsecond}, responseCode: 666, timeout: true, countedCalls: 2, method: http.MethodPost, errorText: "timeout awaiting response headers"},
   433  		{client: Client{maxRetries: 0}, countedCalls: 1, method: http.MethodGet, responseCode: 500, errorText: "Internal Server Error"},
   434  		{client: Client{maxRetries: 2}, countedCalls: 3, method: http.MethodGet, responseCode: 500, errorText: "Internal Server Error"},
   435  		{client: Client{maxRetries: 3}, countedCalls: 4, method: http.MethodPost, responseCode: 503, errorText: "Service Unavailable"},
   436  		{client: Client{maxRetries: 1}, countedCalls: 2, method: http.MethodPut, responseCode: 506, errorText: "Variant Also Negotiates"},
   437  		{client: Client{maxRetries: 1}, countedCalls: 2, method: http.MethodHead, responseCode: 502, errorText: "Bad Gateway"},
   438  		{client: Client{maxRetries: 3}, countedCalls: 1, method: http.MethodHead, responseCode: 404, errorText: "Not Found"},
   439  		{client: Client{maxRetries: 3}, countedCalls: 1, method: http.MethodHead, responseCode: 401, errorText: "Authentication Error"},
   440  		{client: Client{maxRetries: 3}, countedCalls: 1, method: http.MethodHead, responseCode: 403, errorText: "Authorization Error"},
   441  	}
   442  
   443  	for _, testCase := range testCases {
   444  		// init
   445  		count := 0
   446  		svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   447  			count++
   448  			if testCase.timeout && count == 0 {
   449  				time.Sleep(3 * time.Microsecond)
   450  			}
   451  			w.WriteHeader(testCase.responseCode)
   452  		}))
   453  		defer svr.Close()
   454  		// test
   455  		_, err := testCase.client.SendRequest(testCase.method, svr.URL, &bytes.Buffer{}, nil, nil)
   456  		// assert
   457  		assert.Error(t, err, fmt.Sprintf("%v: %v", testCase.errorText, "Expected error but did not detect one"))
   458  		assert.Equal(t, testCase.countedCalls, count, fmt.Sprintf("%v: %v", testCase.errorText, "Number of invocations mismatch"))
   459  	}
   460  }
   461  
   462  func TestParseHTTPResponseBodyJSON(t *testing.T) {
   463  
   464  	type myJSONStruct struct {
   465  		FullName string `json:"full_name"`
   466  		Name     string `json:"name"`
   467  		Owner    struct {
   468  			Login string `json:"login"`
   469  		} `json:"owner"`
   470  	}
   471  
   472  	t.Run("parse JSON successful", func(t *testing.T) {
   473  
   474  		json := `{"name":"Test Name","full_name":"test full name","owner":{"login": "octocat"}}`
   475  		// create a new reader with that JSON
   476  		r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
   477  		httpResponse := http.Response{
   478  			StatusCode: 200,
   479  			Body:       r,
   480  		}
   481  
   482  		var response myJSONStruct
   483  		err := ParseHTTPResponseBodyJSON(&httpResponse, &response)
   484  
   485  		if assert.NoError(t, err) {
   486  
   487  			t.Run("check correct parsing", func(t *testing.T) {
   488  				assert.Equal(t, myJSONStruct(myJSONStruct{FullName: "test full name", Name: "Test Name", Owner: struct {
   489  					Login string "json:\"login\""
   490  				}{Login: "octocat"}}), response)
   491  			})
   492  
   493  		}
   494  	})
   495  
   496  	t.Run("http response is nil", func(t *testing.T) {
   497  
   498  		var response myJSONStruct
   499  		err := ParseHTTPResponseBodyJSON(nil, &response)
   500  
   501  		t.Run("check error", func(t *testing.T) {
   502  			assert.EqualError(t, err, "cannot parse HTTP response with value <nil>")
   503  		})
   504  
   505  	})
   506  
   507  	t.Run("wrong JSON formatting", func(t *testing.T) {
   508  
   509  		json := `{"name":"Test Name","full_name":"test full name";"owner":{"login": "octocat"}}`
   510  		r := ioutil.NopCloser(bytes.NewReader([]byte(json)))
   511  		httpResponse := http.Response{
   512  			StatusCode: 200,
   513  			Body:       r,
   514  		}
   515  
   516  		var response myJSONStruct
   517  		err := ParseHTTPResponseBodyJSON(&httpResponse, &response)
   518  		println(response.FullName)
   519  
   520  		t.Run("check error", func(t *testing.T) {
   521  			assert.EqualError(t, err, "HTTP response body could not be parsed as JSON: {\"name\":\"Test Name\",\"full_name\":\"test full name\";\"owner\":{\"login\": \"octocat\"}}: invalid character ';' after object key:value pair")
   522  		})
   523  
   524  	})
   525  
   526  	t.Run("IO read error", func(t *testing.T) {
   527  
   528  		mockReadCloser := mockReadCloser{}
   529  		// if Read is called, it will return error
   530  		mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, fmt.Errorf("error reading"))
   531  		// if Close is called, it will return error
   532  		mockReadCloser.On("Close").Return(fmt.Errorf("error closing"))
   533  
   534  		httpResponse := http.Response{
   535  			StatusCode: 200,
   536  			Body:       &mockReadCloser,
   537  		}
   538  
   539  		var response myJSONStruct
   540  		err := ParseHTTPResponseBodyJSON(&httpResponse, &response)
   541  
   542  		t.Run("check error", func(t *testing.T) {
   543  			assert.EqualError(t, err, "HTTP response body could not be read: error reading")
   544  		})
   545  
   546  	})
   547  
   548  }
   549  
   550  func TestParseHTTPResponseBodyXML(t *testing.T) {
   551  
   552  	type myXMLStruct struct {
   553  		XMLName xml.Name `xml:"service"`
   554  		Text    string   `xml:",chardata"`
   555  		App     string   `xml:"app,attr"`
   556  		Atom    string   `xml:"atom,attr"`
   557  	}
   558  
   559  	t.Run("parse XML successful", func(t *testing.T) {
   560  
   561  		myXML := `
   562  		<?xml version="1.0" encoding="utf-8"?>
   563  		<app:service xmlns:app="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom"/>
   564  		`
   565  		// create a new reader with that xml
   566  		r := ioutil.NopCloser(bytes.NewReader([]byte(myXML)))
   567  		httpResponse := http.Response{
   568  			StatusCode: 200,
   569  			Body:       r,
   570  		}
   571  
   572  		var response myXMLStruct
   573  		err := ParseHTTPResponseBodyXML(&httpResponse, &response)
   574  
   575  		if assert.NoError(t, err) {
   576  
   577  			t.Run("check correct parsing", func(t *testing.T) {
   578  				// assert.Equal(t, "<?xml version=\"1.0\" encoding=\"utf-8\"?><app:service xmlns:app=\"http://www.w3.org/2007/app\" xmlns:atom=\"http://www.w3.org/2005/Atom\"/>", response)
   579  				assert.Equal(t, myXMLStruct(myXMLStruct{XMLName: xml.Name{Space: "http://www.w3.org/2007/app", Local: "service"}, Text: "", App: "http://www.w3.org/2007/app", Atom: "http://www.w3.org/2005/Atom"}), response)
   580  			})
   581  
   582  		}
   583  	})
   584  
   585  	t.Run("http response is nil", func(t *testing.T) {
   586  
   587  		var response myXMLStruct
   588  		err := ParseHTTPResponseBodyXML(nil, &response)
   589  
   590  		t.Run("check error", func(t *testing.T) {
   591  			assert.EqualError(t, err, "cannot parse HTTP response with value <nil>")
   592  		})
   593  
   594  	})
   595  
   596  	t.Run("wrong XML formatting", func(t *testing.T) {
   597  
   598  		myXML := `
   599  		<?xml version="1.0" encoding="utf-8"?>
   600  		<app:service xmlns:app=http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom"/>
   601  		`
   602  		r := ioutil.NopCloser(bytes.NewReader([]byte(myXML)))
   603  		httpResponse := http.Response{
   604  			StatusCode: 200,
   605  			Body:       r,
   606  		}
   607  
   608  		var response myXMLStruct
   609  		err := ParseHTTPResponseBodyXML(&httpResponse, &response)
   610  
   611  		t.Run("check error", func(t *testing.T) {
   612  			assert.EqualError(t, err, "HTTP response body could not be parsed as XML: \n\t\t<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\t\t<app:service xmlns:app=http://www.w3.org/2007/app\" xmlns:atom=\"http://www.w3.org/2005/Atom\"/>\n\t\t: XML syntax error on line 3: unquoted or missing attribute value in element")
   613  		})
   614  
   615  	})
   616  
   617  	t.Run("IO read error", func(t *testing.T) {
   618  
   619  		mockReadCloser := mockReadCloser{}
   620  		// if Read is called, it will return error
   621  		mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, fmt.Errorf("error reading"))
   622  		// if Close is called, it will return error
   623  		mockReadCloser.On("Close").Return(fmt.Errorf("error closing"))
   624  
   625  		httpResponse := http.Response{
   626  			StatusCode: 200,
   627  			Body:       &mockReadCloser,
   628  		}
   629  
   630  		var response myXMLStruct
   631  		err := ParseHTTPResponseBodyXML(&httpResponse, &response)
   632  
   633  		t.Run("check error", func(t *testing.T) {
   634  			assert.EqualError(t, err, "HTTP response body could not be read: error reading")
   635  		})
   636  
   637  	})
   638  
   639  }
   640  
   641  type mockReadCloser struct {
   642  	mock.Mock
   643  }
   644  
   645  func (m *mockReadCloser) Read(p []byte) (n int, err error) {
   646  	args := m.Called(p)
   647  	return args.Int(0), args.Error(1)
   648  }
   649  
   650  func (m *mockReadCloser) Close() error {
   651  	args := m.Called()
   652  	return args.Error(0)
   653  }