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