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 }