github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/cf/net/gateway_test.go (about)

     1  package net_test
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"net/url"
    12  	"os"
    13  	"reflect"
    14  	"runtime"
    15  	"strings"
    16  	"time"
    17  
    18  	"code.cloudfoundry.org/cli/cf/api/authentication"
    19  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    20  	"code.cloudfoundry.org/cli/cf/errors"
    21  	. "code.cloudfoundry.org/cli/cf/net"
    22  	"code.cloudfoundry.org/cli/cf/net/netfakes"
    23  	"code.cloudfoundry.org/cli/cf/terminal/terminalfakes"
    24  	"code.cloudfoundry.org/cli/cf/trace/tracefakes"
    25  	testconfig "code.cloudfoundry.org/cli/util/testhelpers/configuration"
    26  	testnet "code.cloudfoundry.org/cli/util/testhelpers/net"
    27  	"code.cloudfoundry.org/cli/version"
    28  	. "github.com/onsi/ginkgo"
    29  	. "github.com/onsi/gomega"
    30  	"github.com/onsi/gomega/ghttp"
    31  )
    32  
    33  var _ = Describe("Gateway", func() {
    34  	var (
    35  		ccServer    *ghttp.Server
    36  		ccGateway   Gateway
    37  		uaaGateway  Gateway
    38  		config      coreconfig.ReadWriter
    39  		authRepo    authentication.Repository
    40  		currentTime time.Time
    41  		clock       func() time.Time
    42  
    43  		client *netfakes.FakeHTTPClientInterface
    44  	)
    45  
    46  	BeforeEach(func() {
    47  		currentTime = time.Unix(0, 0)
    48  		clock = func() time.Time { return currentTime }
    49  		config = testconfig.NewRepository()
    50  
    51  		ccGateway = NewCloudControllerGateway(config, clock, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "")
    52  		ccGateway.PollingThrottle = 3 * time.Millisecond
    53  		uaaGateway = NewUAAGateway(config, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "")
    54  	})
    55  
    56  	Describe("async timeout", func() {
    57  		Context("when the config has a positive async timeout", func() {
    58  			It("inherits the async timeout from the config", func() {
    59  				config.SetAsyncTimeout(9001)
    60  				ccGateway = NewCloudControllerGateway(config, time.Now, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "")
    61  				Expect(ccGateway.AsyncTimeout()).To(Equal(9001 * time.Minute))
    62  			})
    63  		})
    64  	})
    65  
    66  	Describe("Connection errors", func() {
    67  		var oldNewHTTPClient func(tr *http.Transport, dumper RequestDumper) HTTPClientInterface
    68  
    69  		BeforeEach(func() {
    70  			client = new(netfakes.FakeHTTPClientInterface)
    71  
    72  			oldNewHTTPClient = NewHTTPClient
    73  			NewHTTPClient = func(tr *http.Transport, dumper RequestDumper) HTTPClientInterface {
    74  				return client
    75  			}
    76  		})
    77  
    78  		AfterEach(func() {
    79  			NewHTTPClient = oldNewHTTPClient
    80  		})
    81  
    82  		It("only retry when response body is nil and error occurred", func() {
    83  			client.DoReturns(&http.Response{Status: "internal error", StatusCode: 500}, errors.New("internal error"))
    84  			request, apiErr := ccGateway.NewRequest("GET", "https://example.com/v2/apps", "BEARER my-access-token", nil)
    85  			Expect(apiErr).ToNot(HaveOccurred())
    86  
    87  			_, apiErr = ccGateway.PerformRequest(request)
    88  			Expect(client.DoCallCount()).To(Equal(1))
    89  			Expect(apiErr).To(HaveOccurred())
    90  		})
    91  
    92  		It("Retries 3 times if we cannot contact the server", func() {
    93  			client.DoReturns(nil, errors.New("Connection refused"))
    94  			request, apiErr := ccGateway.NewRequest("GET", "https://example.com/v2/apps", "BEARER my-access-token", nil)
    95  			Expect(apiErr).ToNot(HaveOccurred())
    96  
    97  			_, apiErr = ccGateway.PerformRequest(request)
    98  			Expect(apiErr).To(HaveOccurred())
    99  			Expect(client.DoCallCount()).To(Equal(3))
   100  		})
   101  	})
   102  
   103  	Describe("NewRequest", func() {
   104  		var (
   105  			request *Request
   106  			apiErr  error
   107  		)
   108  
   109  		Context("when the body is nil", func() {
   110  			BeforeEach(func() {
   111  				request, apiErr = ccGateway.NewRequest("GET", "https://example.com/v2/apps", "BEARER my-access-token", nil)
   112  				Expect(apiErr).NotTo(HaveOccurred())
   113  			})
   114  
   115  			It("does not use a ProgressReader as the SeekableBody", func() {
   116  				Expect(reflect.TypeOf(request.SeekableBody)).To(BeNil())
   117  			})
   118  
   119  			It("sets the Authorization header", func() {
   120  				Expect(request.HTTPReq.Header.Get("Authorization")).To(Equal("BEARER my-access-token"))
   121  			})
   122  
   123  			It("sets the accept header to application/json", func() {
   124  				Expect(request.HTTPReq.Header.Get("accept")).To(Equal("application/json"))
   125  			})
   126  
   127  			It("sets the user agent header", func() {
   128  				Expect(request.HTTPReq.Header.Get("User-Agent")).To(Equal("go-cli " + version.VersionString() + " / " + runtime.GOOS))
   129  			})
   130  		})
   131  
   132  		Context("when the body is a file", func() {
   133  			BeforeEach(func() {
   134  				f, _ := os.Open("../../fixtures/test.file")
   135  				request, apiErr = ccGateway.NewRequestForFile("PUT", "https://example.com/v2/apps", "BEARER my-access-token", f)
   136  				Expect(apiErr).NotTo(HaveOccurred())
   137  			})
   138  
   139  			It("Uses a ProgressReader as the SeekableBody", func() {
   140  				Expect(reflect.TypeOf(request.SeekableBody).String()).To(ContainSubstring("ProgressReader"))
   141  			})
   142  
   143  		})
   144  
   145  	})
   146  
   147  	Describe("PerformRequestForJSONResponse()", func() {
   148  		BeforeEach(func() {
   149  			ccServer = ghttp.NewServer()
   150  			ccServer.HTTPTestServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   151  			config.SetAPIEndpoint(ccServer.URL())
   152  		})
   153  
   154  		AfterEach(func() {
   155  			ccServer.Close()
   156  		})
   157  
   158  		Context("When CC response with an api error", func() {
   159  			BeforeEach(func() {
   160  				ccServer.AppendHandlers(
   161  					ghttp.CombineHandlers(
   162  						ghttp.VerifyRequest("GET", "/v2/some-endpoint"),
   163  						ghttp.VerifyHeader(http.Header{
   164  							"accept": []string{"application/json"},
   165  						}),
   166  						ghttp.RespondWith(http.StatusUnauthorized, `{
   167    "code": 10003,
   168    "description": "You are not authorized to perform the requested action",
   169    "error_code": "CF-NotAuthorized"
   170  }`),
   171  					),
   172  				)
   173  			})
   174  
   175  			It("tries to unmarshal error response into provided resource", func() {
   176  				type apiErrResponse struct {
   177  					Code        int    `json:"code,omitempty"`
   178  					Description string `json:"description,omitempty"`
   179  				}
   180  
   181  				errResponse := new(apiErrResponse)
   182  				request, _ := ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/some-endpoint", config.AccessToken(), nil)
   183  				_, apiErr := ccGateway.PerformRequestForJSONResponse(request, errResponse)
   184  
   185  				Expect(apiErr).To(HaveOccurred())
   186  				Expect(errResponse.Code).To(Equal(10003))
   187  			})
   188  
   189  			It("ignores any unmarshal error and does not alter the api err response", func() {
   190  				request, _ := ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/some-endpoint", config.AccessToken(), nil)
   191  				_, apiErr := ccGateway.PerformRequestForJSONResponse(request, nil)
   192  
   193  				Expect(apiErr.Error()).To(Equal("Server error, status code: 401, error code: 10003, message: You are not authorized to perform the requested action"))
   194  			})
   195  
   196  		})
   197  
   198  	})
   199  
   200  	Describe("CRUD methods", func() {
   201  		Describe("Delete", func() {
   202  			var apiServer *httptest.Server
   203  
   204  			Describe("DeleteResourceSynchronously", func() {
   205  				var queryParams string
   206  				BeforeEach(func() {
   207  					apiServer = httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, request *http.Request) {
   208  						queryParams = request.URL.RawQuery
   209  					}))
   210  					apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   211  					ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   212  				})
   213  
   214  				It("does not send the async=true flag", func() {
   215  					err := ccGateway.DeleteResourceSynchronously(apiServer.URL, "/v2/foobars/SOME_GUID")
   216  					Expect(err).NotTo(HaveOccurred())
   217  					Expect(queryParams).ToNot(ContainSubstring("async=true"))
   218  				})
   219  
   220  				It("deletes a resource", func() {
   221  					err := ccGateway.DeleteResource(apiServer.URL, "/v2/foobars/SOME_GUID")
   222  					Expect(err).ToNot(HaveOccurred())
   223  				})
   224  			})
   225  
   226  			Context("when the config has an async timeout", func() {
   227  				BeforeEach(func() {
   228  					count := 0
   229  					apiServer = httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
   230  						switch request.URL.Path {
   231  						case "/v2/foobars/SOME_GUID":
   232  							writer.WriteHeader(http.StatusNoContent)
   233  						case "/v2/foobars/TIMEOUT":
   234  							currentTime = currentTime.Add(time.Minute * 31)
   235  							fmt.Fprintln(writer, `
   236  {
   237    "metadata": {
   238      "guid": "8438916f-5c00-4d44-a19b-1df65abe9d52",
   239      "created_at": "2014-05-15T19:15:01+00:00",
   240      "url": "/v2/jobs/8438916f-5c00-4d44-a19b-1df65abe9d52"
   241    },
   242    "entity": {
   243      "guid": "8438916f-5c00-4d44-a19b-1df65abe9d52",
   244      "status": "queued"
   245    }
   246  }`)
   247  							writer.WriteHeader(http.StatusAccepted)
   248  						case "/v2/jobs/8438916f-5c00-4d44-a19b-1df65abe9d52":
   249  							if count == 0 {
   250  								count++
   251  								currentTime = currentTime.Add(time.Minute * 31)
   252  
   253  								writer.WriteHeader(http.StatusOK)
   254  								fmt.Fprintln(writer, `
   255  {
   256    "entity": {
   257      "guid": "8438916f-5c00-4d44-a19b-1df65abe9d52",
   258      "status": "queued"
   259    }
   260  }`)
   261  							} else {
   262  								panic("FAIL")
   263  							}
   264  						default:
   265  							panic("shouldn't have made call to this URL: " + request.URL.Path)
   266  						}
   267  					}))
   268  
   269  					config.SetAsyncTimeout(30)
   270  					ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   271  					apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   272  				})
   273  
   274  				AfterEach(func() {
   275  					apiServer.Close()
   276  				})
   277  
   278  				It("deletes a resource", func() {
   279  					err := ccGateway.DeleteResource(apiServer.URL, "/v2/foobars/SOME_GUID")
   280  					Expect(err).ToNot(HaveOccurred())
   281  				})
   282  
   283  				Context("when the request would take longer than the async timeout", func() {
   284  					It("returns an error", func() {
   285  						apiErr := ccGateway.DeleteResource(apiServer.URL, "/v2/foobars/TIMEOUT")
   286  						Expect(apiErr).To(HaveOccurred())
   287  						Expect(apiErr).To(BeAssignableToTypeOf(errors.NewAsyncTimeoutError("http://some.url")))
   288  					})
   289  				})
   290  			})
   291  		})
   292  	})
   293  
   294  	Describe("making an async request", func() {
   295  		var (
   296  			jobStatus     string
   297  			apiServer     *httptest.Server
   298  			authServer    *httptest.Server
   299  			statusChannel chan string
   300  		)
   301  
   302  		BeforeEach(func() {
   303  			jobStatus = "queued"
   304  			statusChannel = make(chan string, 10)
   305  
   306  			apiServer = httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
   307  				currentTime = currentTime.Add(time.Millisecond * 11)
   308  
   309  				updateStatus, ok := <-statusChannel
   310  				if ok {
   311  					jobStatus = updateStatus
   312  				}
   313  
   314  				switch request.URL.Path {
   315  				case "/v2/foo":
   316  					fmt.Fprintln(writer, `{ "metadata": { "url": "/v2/jobs/the-job-guid" } }`)
   317  				case "/v2/jobs/the-job-guid":
   318  					fmt.Fprintf(writer, `
   319  					{
   320  						"entity": {
   321  							"status": "%s",
   322  							"error_details": {
   323  								"description": "he's dead, Jim"
   324  							}
   325  						}
   326  					}`, jobStatus)
   327  				default:
   328  					writer.WriteHeader(http.StatusInternalServerError)
   329  					fmt.Fprintf(writer, `"Unexpected request path '%s'"`, request.URL.Path)
   330  				}
   331  			}))
   332  
   333  			authServer, _ = testnet.NewTLSServer([]testnet.TestRequest{})
   334  
   335  			config, authRepo = createAuthenticationRepository(apiServer, authServer)
   336  			ccGateway.SetTokenRefresher(authRepo)
   337  
   338  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   339  			apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   340  			authServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   341  		})
   342  
   343  		AfterEach(func() {
   344  			apiServer.Close()
   345  			authServer.Close()
   346  		})
   347  
   348  		It("returns the last response if the job completes before the timeout", func() {
   349  			go func() {
   350  				statusChannel <- "queued"
   351  				statusChannel <- "finished"
   352  			}()
   353  
   354  			request, _ := ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/foo", config.AccessToken(), nil)
   355  			_, apiErr := ccGateway.PerformPollingRequestForJSONResponse(config.APIEndpoint(), request, new(struct{}), 500*time.Millisecond)
   356  			Expect(apiErr).NotTo(HaveOccurred())
   357  		})
   358  
   359  		It("returns an error with the right message when the job fails", func() {
   360  			go func() {
   361  				statusChannel <- "queued"
   362  				statusChannel <- "failed"
   363  			}()
   364  
   365  			request, _ := ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/foo", config.AccessToken(), nil)
   366  			_, apiErr := ccGateway.PerformPollingRequestForJSONResponse(config.APIEndpoint(), request, new(struct{}), 500*time.Millisecond)
   367  			Expect(apiErr.Error()).To(ContainSubstring("he's dead, Jim"))
   368  		})
   369  
   370  		It("returns an error if jobs takes longer than the timeout", func() {
   371  			go func() {
   372  				statusChannel <- "queued"
   373  				statusChannel <- "OHNOES"
   374  			}()
   375  			request, _ := ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/foo", config.AccessToken(), nil)
   376  			_, apiErr := ccGateway.PerformPollingRequestForJSONResponse(config.APIEndpoint(), request, new(struct{}), 10*time.Millisecond)
   377  			Expect(apiErr).To(HaveOccurred())
   378  			Expect(apiErr).To(BeAssignableToTypeOf(errors.NewAsyncTimeoutError("http://some.url")))
   379  		})
   380  	})
   381  
   382  	Describe("when uploading a file", func() {
   383  		var (
   384  			err          error
   385  			request      *Request
   386  			apiErr       error
   387  			apiServer    *httptest.Server
   388  			authServer   *httptest.Server
   389  			fileToUpload *os.File
   390  		)
   391  
   392  		BeforeEach(func() {
   393  			apiServer = httptest.NewTLSServer(refreshTokenAPIEndPoint(
   394  				`{ "code": 1000, "description": "Auth token is invalid" }`,
   395  				testnet.TestResponse{Status: http.StatusOK},
   396  			))
   397  
   398  			authServer = httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
   399  				fmt.Fprintln(
   400  					writer,
   401  					`{ "access_token": "new-access-token", "token_type": "bearer", "refresh_token": "new-refresh-token"}`)
   402  			}))
   403  			apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   404  			authServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   405  
   406  			fileToUpload, err = ioutil.TempFile("", "test-gateway")
   407  			strings.NewReader("expected body").WriteTo(fileToUpload)
   408  
   409  			config, auth := createAuthenticationRepository(apiServer, authServer)
   410  			ccGateway.SetTokenRefresher(auth)
   411  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   412  
   413  			request, apiErr = ccGateway.NewRequestForFile("POST", config.APIEndpoint()+"/v2/foo", config.AccessToken(), fileToUpload)
   414  		})
   415  
   416  		AfterEach(func() {
   417  			apiServer.Close()
   418  			authServer.Close()
   419  			fileToUpload.Close()
   420  			os.Remove(fileToUpload.Name())
   421  		})
   422  
   423  		It("sets the content length to the size of the file", func() {
   424  			Expect(err).NotTo(HaveOccurred())
   425  			Expect(apiErr).NotTo(HaveOccurred())
   426  			Expect(request.HTTPReq.ContentLength).To(Equal(int64(13)))
   427  		})
   428  
   429  		Describe("when the access token expires during the upload", func() {
   430  			It("successfully re-sends the file on the second request", func() {
   431  				_, apiErr = ccGateway.PerformRequest(request)
   432  				Expect(apiErr).NotTo(HaveOccurred())
   433  			})
   434  		})
   435  	})
   436  
   437  	Describe("refreshing the auth token", func() {
   438  		var authServer *httptest.Server
   439  
   440  		BeforeEach(func() {
   441  			authServer = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   442  				fmt.Fprintln(w, `{
   443  				 	"access_token": "new-access-token",
   444  				 	"token_type": "bearer",
   445  					"refresh_token": "new-refresh-token"
   446  				}`)
   447  			}))
   448  
   449  			uaaGateway.SetTrustedCerts(authServer.TLS.Certificates)
   450  			authServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   451  		})
   452  
   453  		AfterEach(func() {
   454  			authServer.Close()
   455  		})
   456  
   457  		It("refreshes the token when UAA requests fail", func() {
   458  			apiServer := httptest.NewTLSServer(refreshTokenAPIEndPoint(
   459  				`{ "error": "invalid_token", "error_description": "Auth token is invalid" }`,
   460  				testnet.TestResponse{Status: http.StatusOK},
   461  			))
   462  			defer apiServer.Close()
   463  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   464  
   465  			config, auth := createAuthenticationRepository(apiServer, authServer)
   466  			uaaGateway.SetTokenRefresher(auth)
   467  			request, apiErr := uaaGateway.NewRequest("POST", config.APIEndpoint()+"/v2/foo", config.AccessToken(), strings.NewReader("expected body"))
   468  			_, apiErr = uaaGateway.PerformRequest(request)
   469  
   470  			Expect(apiErr).NotTo(HaveOccurred())
   471  			Expect(config.AccessToken()).To(Equal("bearer new-access-token"))
   472  			Expect(config.RefreshToken()).To(Equal("new-refresh-token"))
   473  		})
   474  
   475  		It("refreshes the token when CC requests fail", func() {
   476  			apiServer := httptest.NewTLSServer(refreshTokenAPIEndPoint(
   477  				`{ "code": 1000, "description": "Auth token is invalid" }`,
   478  				testnet.TestResponse{Status: http.StatusOK}))
   479  			defer apiServer.Close()
   480  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   481  
   482  			config, auth := createAuthenticationRepository(apiServer, authServer)
   483  			ccGateway.SetTokenRefresher(auth)
   484  			request, apiErr := ccGateway.NewRequest("POST", config.APIEndpoint()+"/v2/foo", config.AccessToken(), strings.NewReader("expected body"))
   485  			_, apiErr = ccGateway.PerformRequest(request)
   486  
   487  			Expect(apiErr).NotTo(HaveOccurred())
   488  			Expect(config.AccessToken()).To(Equal("bearer new-access-token"))
   489  			Expect(config.RefreshToken()).To(Equal("new-refresh-token"))
   490  		})
   491  
   492  		It("returns a failure response when token refresh fails after a UAA request", func() {
   493  			apiServer := httptest.NewTLSServer(refreshTokenAPIEndPoint(
   494  				`{ "error": "invalid_token", "error_description": "Auth token is invalid" }`,
   495  				testnet.TestResponse{Status: http.StatusBadRequest, Body: `{
   496  					"error": "333", "error_description": "bad request"
   497  				}`}))
   498  			defer apiServer.Close()
   499  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   500  			apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   501  
   502  			config, auth := createAuthenticationRepository(apiServer, authServer)
   503  			uaaGateway.SetTokenRefresher(auth)
   504  			request, apiErr := uaaGateway.NewRequest("POST", config.APIEndpoint()+"/v2/foo", config.AccessToken(), strings.NewReader("expected body"))
   505  			_, apiErr = uaaGateway.PerformRequest(request)
   506  
   507  			Expect(apiErr).To(HaveOccurred())
   508  			Expect(apiErr.(errors.HTTPError).ErrorCode()).To(Equal("333"))
   509  		})
   510  
   511  		It("returns a failure response when token refresh fails after a CC request", func() {
   512  			apiServer := httptest.NewTLSServer(refreshTokenAPIEndPoint(
   513  				`{ "code": 1000, "description": "Auth token is invalid" }`,
   514  				testnet.TestResponse{Status: http.StatusBadRequest, Body: `{
   515  					"code": 333, "description": "bad request"
   516  				}`}))
   517  			defer apiServer.Close()
   518  			apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   519  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   520  
   521  			config, auth := createAuthenticationRepository(apiServer, authServer)
   522  			ccGateway.SetTokenRefresher(auth)
   523  			request, apiErr := ccGateway.NewRequest("POST", config.APIEndpoint()+"/v2/foo", config.AccessToken(), strings.NewReader("expected body"))
   524  			_, apiErr = ccGateway.PerformRequest(request)
   525  
   526  			Expect(apiErr).To(HaveOccurred())
   527  			Expect(apiErr.(errors.HTTPError).ErrorCode()).To(Equal("333"))
   528  		})
   529  	})
   530  
   531  	Describe("SSL certificate validation errors", func() {
   532  		var (
   533  			request   *Request
   534  			apiServer *httptest.Server
   535  		)
   536  
   537  		BeforeEach(func() {
   538  			apiServer = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
   539  				fmt.Fprintln(w, `{}`)
   540  			}))
   541  			apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   542  			request, _ = ccGateway.NewRequest("POST", apiServer.URL+"/v2/foo", "the-access-token", nil)
   543  		})
   544  
   545  		AfterEach(func() {
   546  			apiServer.Close()
   547  		})
   548  
   549  		Context("when SSL validation is enabled", func() {
   550  			It("returns an invalid cert error if the server's CA is unknown (e.g. cert is self-signed)", func() {
   551  				apiServer.TLS.Certificates = []tls.Certificate{testnet.MakeSelfSignedTLSCert()}
   552  
   553  				_, apiErr := ccGateway.PerformRequest(request)
   554  				certErr, ok := apiErr.(*errors.InvalidSSLCert)
   555  				Expect(ok).To(BeTrue())
   556  				Expect(certErr.URL).To(Equal(getHost(apiServer.URL)))
   557  				Expect(certErr.Reason).To(Equal("unknown authority"))
   558  			})
   559  
   560  			It("returns an invalid cert error if the server's cert doesn't match its host", func() {
   561  				apiServer.TLS.Certificates = []tls.Certificate{testnet.MakeTLSCertWithInvalidHost()}
   562  
   563  				_, apiErr := ccGateway.PerformRequest(request)
   564  				certErr, ok := apiErr.(*errors.InvalidSSLCert)
   565  				Expect(ok).To(BeTrue())
   566  				Expect(certErr.URL).To(Equal(getHost(apiServer.URL)))
   567  				if runtime.GOOS != "windows" {
   568  					Expect(certErr.Reason).To(Equal("not valid for the requested host"))
   569  				}
   570  			})
   571  
   572  			It("returns an invalid cert error if the server's cert has expired", func() {
   573  				apiServer.TLS.Certificates = []tls.Certificate{testnet.MakeExpiredTLSCert()}
   574  
   575  				_, apiErr := ccGateway.PerformRequest(request)
   576  				certErr, ok := apiErr.(*errors.InvalidSSLCert)
   577  				Expect(ok).To(BeTrue())
   578  				Expect(certErr.URL).To(Equal(getHost(apiServer.URL)))
   579  				if runtime.GOOS != "windows" {
   580  					Expect(certErr.Reason).To(Equal(""))
   581  				}
   582  			})
   583  		})
   584  
   585  		Context("when SSL validation is disabled", func() {
   586  			BeforeEach(func() {
   587  				apiServer.TLS.Certificates = []tls.Certificate{testnet.MakeExpiredTLSCert()}
   588  				config.SetSSLDisabled(true)
   589  			})
   590  
   591  			It("succeeds", func() {
   592  				_, apiErr := ccGateway.PerformRequest(request)
   593  				Expect(apiErr).NotTo(HaveOccurred())
   594  			})
   595  		})
   596  
   597  	})
   598  
   599  	Describe("collecting warnings", func() {
   600  		var (
   601  			apiServer  *httptest.Server
   602  			authServer *httptest.Server
   603  		)
   604  
   605  		BeforeEach(func() {
   606  			apiServer = httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
   607  				switch request.URL.Path {
   608  				case "/v2/happy":
   609  					fmt.Fprintln(writer, `{ "metadata": { "url": "/v2/jobs/the-job-guid" } }`)
   610  				case "/v2/warning1":
   611  					writer.Header().Add("X-Cf-Warnings", url.QueryEscape("Something not too awful has happened"))
   612  					fmt.Fprintln(writer, `{ "metadata": { "url": "/v2/jobs/the-job-guid" } }`)
   613  				case "/v2/warning2":
   614  					writer.Header().Add("X-Cf-Warnings", url.QueryEscape("Something a little awful"))
   615  					writer.Header().Add("X-Cf-Warnings", url.QueryEscape("Don't worry, but be careful"))
   616  					writer.WriteHeader(http.StatusInternalServerError)
   617  					fmt.Fprintf(writer, `{ "key": "value" }`)
   618  				}
   619  			}))
   620  
   621  			authServer, _ = testnet.NewTLSServer([]testnet.TestRequest{})
   622  
   623  			config, authRepo = createAuthenticationRepository(apiServer, authServer)
   624  			ccGateway.SetTokenRefresher(authRepo)
   625  
   626  			ccGateway.SetTrustedCerts(apiServer.TLS.Certificates)
   627  			apiServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   628  			authServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   629  
   630  			config, authRepo = createAuthenticationRepository(apiServer, authServer)
   631  		})
   632  
   633  		AfterEach(func() {
   634  			apiServer.Close()
   635  			authServer.Close()
   636  		})
   637  
   638  		It("saves all X-Cf-Warnings headers and exposes them", func() {
   639  			request, _ := ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/happy", config.AccessToken(), nil)
   640  			ccGateway.PerformRequest(request)
   641  			request, _ = ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/warning1", config.AccessToken(), nil)
   642  			ccGateway.PerformRequest(request)
   643  			request, _ = ccGateway.NewRequest("GET", config.APIEndpoint()+"/v2/warning2", config.AccessToken(), nil)
   644  			ccGateway.PerformRequest(request)
   645  
   646  			Expect(ccGateway.Warnings()).To(Equal(
   647  				[]string{"Something not too awful has happened", "Something a little awful", "Don't worry, but be careful"},
   648  			))
   649  		})
   650  
   651  		It("defaults warnings to an empty slice", func() {
   652  			Expect(ccGateway.Warnings()).ToNot(BeNil())
   653  		})
   654  	})
   655  })
   656  
   657  func getHost(urlString string) string {
   658  	url, err := url.Parse(urlString)
   659  	Expect(err).NotTo(HaveOccurred())
   660  	return url.Host
   661  }
   662  
   663  func refreshTokenAPIEndPoint(unauthorizedBody string, secondReqResp testnet.TestResponse) http.HandlerFunc {
   664  	return func(writer http.ResponseWriter, request *http.Request) {
   665  		var jsonResponse string
   666  
   667  		bodyBytes, err := ioutil.ReadAll(request.Body)
   668  		if err != nil || string(bodyBytes) != "expected body" {
   669  			writer.WriteHeader(http.StatusInternalServerError)
   670  			return
   671  		}
   672  
   673  		switch request.Header.Get("Authorization") {
   674  		case "bearer initial-access-token":
   675  			writer.WriteHeader(http.StatusUnauthorized)
   676  			jsonResponse = unauthorizedBody
   677  		case "bearer new-access-token":
   678  			writer.WriteHeader(secondReqResp.Status)
   679  			jsonResponse = secondReqResp.Body
   680  		default:
   681  			writer.WriteHeader(http.StatusInternalServerError)
   682  		}
   683  
   684  		fmt.Fprintln(writer, jsonResponse)
   685  	}
   686  }
   687  
   688  func createAuthenticationRepository(apiServer *httptest.Server, authServer *httptest.Server) (coreconfig.ReadWriter, authentication.Repository) {
   689  	config := testconfig.NewRepository()
   690  	config.SetAuthenticationEndpoint(authServer.URL)
   691  	config.SetAPIEndpoint(apiServer.URL)
   692  	config.SetAccessToken("bearer initial-access-token")
   693  	config.SetRefreshToken("initial-refresh-token")
   694  
   695  	authGateway := NewUAAGateway(config, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "")
   696  	authGateway.SetTrustedCerts(authServer.TLS.Certificates)
   697  
   698  	fakePrinter := new(tracefakes.FakePrinter)
   699  	dumper := NewRequestDumper(fakePrinter)
   700  	authenticator := authentication.NewUAARepository(authGateway, config, dumper)
   701  
   702  	return config, authenticator
   703  }