github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/cloud_controller_connection_test.go (about)

     1  package cloudcontroller_test
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"runtime"
     7  	"strings"
     8  
     9  	. "code.cloudfoundry.org/cli/api/cloudcontroller"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  	. "github.com/onsi/gomega/ghttp"
    14  )
    15  
    16  type DummyResponse struct {
    17  	Val1 string      `json:"val1"`
    18  	Val2 int         `json:"val2"`
    19  	Val3 interface{} `json:"val3,omitempty"`
    20  }
    21  
    22  var _ = Describe("Cloud Controller Connection", func() {
    23  	var connection *CloudControllerConnection
    24  
    25  	BeforeEach(func() {
    26  		connection = NewConnection(Config{SkipSSLValidation: true})
    27  	})
    28  
    29  	Describe("Make", func() {
    30  		Describe("Data Unmarshalling", func() {
    31  			var request *Request
    32  
    33  			BeforeEach(func() {
    34  				response := `{
    35  					"val1":"2.59.0",
    36  					"val2":2,
    37  					"val3":1111111111111111111
    38  				}`
    39  				server.AppendHandlers(
    40  					CombineHandlers(
    41  						VerifyRequest(http.MethodGet, "/v2/foo", ""),
    42  						RespondWith(http.StatusOK, response),
    43  					),
    44  				)
    45  
    46  				req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
    47  				Expect(err).ToNot(HaveOccurred())
    48  				request = &Request{Request: req}
    49  			})
    50  
    51  			When("passed a response with a result set", func() {
    52  				It("unmarshals the data into a struct", func() {
    53  					var body DummyResponse
    54  					response := Response{
    55  						DecodeJSONResponseInto: &body,
    56  					}
    57  
    58  					err := connection.Make(request, &response)
    59  					Expect(err).NotTo(HaveOccurred())
    60  
    61  					Expect(body.Val1).To(Equal("2.59.0"))
    62  					Expect(body.Val2).To(Equal(2))
    63  				})
    64  
    65  				It("keeps numbers unmarshalled to interfaces as interfaces", func() {
    66  					var body DummyResponse
    67  					response := Response{
    68  						DecodeJSONResponseInto: &body,
    69  					}
    70  
    71  					err := connection.Make(request, &response)
    72  					Expect(err).NotTo(HaveOccurred())
    73  					Expect(fmt.Sprint(body.Val3)).To(Equal("1111111111111111111"))
    74  				})
    75  			})
    76  
    77  			When("passed an empty response", func() {
    78  				It("skips the unmarshalling step", func() {
    79  					var response Response
    80  					err := connection.Make(request, &response)
    81  					Expect(err).NotTo(HaveOccurred())
    82  					Expect(response.DecodeJSONResponseInto).To(BeNil())
    83  				})
    84  			})
    85  		})
    86  
    87  		Describe("HTTP Response", func() {
    88  			var request *Request
    89  
    90  			BeforeEach(func() {
    91  				response := `{}`
    92  				server.AppendHandlers(
    93  					CombineHandlers(
    94  						VerifyRequest(http.MethodGet, "/v2/foo", ""),
    95  						RespondWith(http.StatusOK, response),
    96  					),
    97  				)
    98  
    99  				req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
   100  				Expect(err).ToNot(HaveOccurred())
   101  				request = &Request{Request: req}
   102  			})
   103  
   104  			It("returns the status", func() {
   105  				response := Response{}
   106  
   107  				err := connection.Make(request, &response)
   108  				Expect(err).NotTo(HaveOccurred())
   109  
   110  				Expect(response.HTTPResponse.Status).To(Equal("200 OK"))
   111  			})
   112  		})
   113  
   114  		Describe("Response Headers", func() {
   115  			Describe("Location", func() {
   116  				BeforeEach(func() {
   117  					server.AppendHandlers(
   118  						CombineHandlers(
   119  							VerifyRequest(http.MethodGet, "/v2/foo"),
   120  							RespondWith(http.StatusAccepted, "{}", http.Header{"Location": {"/v2/some-location"}}),
   121  						),
   122  					)
   123  				})
   124  
   125  				It("returns the location in the ResourceLocationURL", func() {
   126  					req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
   127  					Expect(err).ToNot(HaveOccurred())
   128  					request := &Request{Request: req}
   129  
   130  					var response Response
   131  					err = connection.Make(request, &response)
   132  					Expect(err).NotTo(HaveOccurred())
   133  
   134  					Expect(server.ReceivedRequests()).To(HaveLen(1))
   135  					Expect(response.ResourceLocationURL).To(Equal("/v2/some-location"))
   136  				})
   137  			})
   138  			Describe("X-Cf-Warnings", func() {
   139  				When("there are warnings", func() {
   140  					BeforeEach(func() {
   141  						server.AppendHandlers(
   142  							CombineHandlers(
   143  								VerifyRequest(http.MethodGet, "/v2/foo"),
   144  								RespondWith(http.StatusOK, "{}", http.Header{"X-Cf-Warnings": {"42,+Ed+McMann,+the+1942+doggers,a%2Cb"}}),
   145  							),
   146  						)
   147  					})
   148  
   149  					It("returns them in Response", func() {
   150  						req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
   151  						Expect(err).ToNot(HaveOccurred())
   152  						request := &Request{Request: req}
   153  
   154  						var response Response
   155  						err = connection.Make(request, &response)
   156  						Expect(err).NotTo(HaveOccurred())
   157  
   158  						Expect(server.ReceivedRequests()).To(HaveLen(1))
   159  
   160  						warnings := response.Warnings
   161  						Expect(warnings).ToNot(BeNil())
   162  						Expect(warnings).To(HaveLen(4))
   163  						Expect(warnings).To(ContainElement("42"))
   164  						Expect(warnings).To(ContainElement("Ed McMann"))
   165  						Expect(warnings).To(ContainElement("the 1942 doggers"))
   166  						Expect(warnings).To(ContainElement("a,b"))
   167  					})
   168  				})
   169  
   170  				When("there are warnings using multi-value header", func() {
   171  					BeforeEach(func() {
   172  						server.AppendHandlers(
   173  							CombineHandlers(
   174  								VerifyRequest(http.MethodGet, "/v2/foo"),
   175  								RespondWith(http.StatusOK, "{}", http.Header{"X-Cf-Warnings": {
   176  									"42,+Ed+McMann,+the+1942+doggers,a%2Cb",
   177  									"something,simpler",
   178  								}}),
   179  							),
   180  						)
   181  					})
   182  
   183  					It("returns them in Response", func() {
   184  						req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
   185  						Expect(err).ToNot(HaveOccurred())
   186  						request := &Request{Request: req}
   187  
   188  						var response Response
   189  						err = connection.Make(request, &response)
   190  						Expect(err).NotTo(HaveOccurred())
   191  
   192  						Expect(server.ReceivedRequests()).To(HaveLen(1))
   193  
   194  						warnings := response.Warnings
   195  						Expect(warnings).ToNot(BeNil())
   196  						Expect(warnings).To(HaveLen(6))
   197  						Expect(warnings).To(ContainElement("42"))
   198  						Expect(warnings).To(ContainElement("Ed McMann"))
   199  						Expect(warnings).To(ContainElement("the 1942 doggers"))
   200  						Expect(warnings).To(ContainElement("a,b"))
   201  						Expect(warnings).To(ContainElement("something"))
   202  						Expect(warnings).To(ContainElement("simpler"))
   203  					})
   204  				})
   205  
   206  				When("there are no warnings", func() {
   207  					BeforeEach(func() {
   208  						server.AppendHandlers(
   209  							CombineHandlers(
   210  								VerifyRequest(http.MethodGet, "/v2/foo"),
   211  								RespondWith(http.StatusOK, "{}"),
   212  							),
   213  						)
   214  					})
   215  
   216  					It("returns them in Response", func() {
   217  						req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
   218  						Expect(err).ToNot(HaveOccurred())
   219  						request := &Request{Request: req}
   220  
   221  						var response Response
   222  						err = connection.Make(request, &response)
   223  						Expect(err).NotTo(HaveOccurred())
   224  
   225  						Expect(response.Warnings).To(BeEmpty())
   226  						Expect(server.ReceivedRequests()).To(HaveLen(1))
   227  					})
   228  				})
   229  			})
   230  		})
   231  
   232  		Describe("Errors", func() {
   233  			When("the server does not exist", func() {
   234  				BeforeEach(func() {
   235  					connection = NewConnection(Config{})
   236  				})
   237  
   238  				It("returns a RequestError", func() {
   239  					req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", "http://garbledyguk.com"), nil)
   240  					Expect(err).ToNot(HaveOccurred())
   241  					request := &Request{Request: req}
   242  
   243  					var response Response
   244  					err = connection.Make(request, &response)
   245  					Expect(err).To(HaveOccurred())
   246  
   247  					requestErr, ok := err.(ccerror.RequestError)
   248  					Expect(ok).To(BeTrue())
   249  					Expect(requestErr.Error()).To(MatchRegexp(".*http://garbledyguk.com/v2/foo.*[nN]o such host"))
   250  				})
   251  			})
   252  
   253  			When("the server does not have a verified certificate", func() {
   254  				Context("skipSSLValidation is false", func() {
   255  					BeforeEach(func() {
   256  						if runtime.GOOS == "darwin" {
   257  							Skip("ssl verification is different on darwin")
   258  						}
   259  						server.AppendHandlers(
   260  							CombineHandlers(
   261  								VerifyRequest(http.MethodGet, "/v2/foo"),
   262  							),
   263  						)
   264  
   265  						connection = NewConnection(Config{})
   266  					})
   267  
   268  					It("returns a UnverifiedServerError", func() {
   269  						req, err := http.NewRequest(http.MethodGet, server.URL(), nil)
   270  						Expect(err).ToNot(HaveOccurred())
   271  						request := &Request{Request: req}
   272  
   273  						var response Response
   274  						err = connection.Make(request, &response)
   275  						Expect(err).To(MatchError(ccerror.UnverifiedServerError{URL: server.URL()}))
   276  					})
   277  				})
   278  			})
   279  
   280  			When("the server's certificate does not match the hostname", func() {
   281  				Context("skipSSLValidation is false", func() {
   282  					BeforeEach(func() {
   283  						if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
   284  							Skip("ssl validation has a different order on windows/darwin, will not be returned properly")
   285  						}
   286  						server.AppendHandlers(
   287  							CombineHandlers(
   288  								VerifyRequest(http.MethodGet, "/"),
   289  							),
   290  						)
   291  
   292  						connection = NewConnection(Config{})
   293  					})
   294  
   295  					// loopback.cli.fun is a custom DNS record setup to point to 127.0.0.1
   296  					It("returns a SSLValidationHostnameError", func() {
   297  						altHostURL := strings.Replace(server.URL(), "127.0.0.1", "loopback.cli.fun", -1)
   298  						req, err := http.NewRequest(http.MethodGet, altHostURL, nil)
   299  						Expect(err).ToNot(HaveOccurred())
   300  						request := &Request{Request: req}
   301  
   302  						var response Response
   303  						err = connection.Make(request, &response)
   304  						Expect(err).To(MatchError(ccerror.SSLValidationHostnameError{
   305  							Message: "x509: certificate is valid for example.com, not loopback.cli.fun",
   306  						}))
   307  					})
   308  				})
   309  			})
   310  
   311  			Describe("RawHTTPStatusError", func() {
   312  				var ccResponse string
   313  				BeforeEach(func() {
   314  					ccResponse = `{
   315  						"code": 90004,
   316  						"description": "The service binding could not be found: some-guid",
   317  						"error_code": "CF-ServiceBindingNotFound"
   318  					}`
   319  
   320  					server.AppendHandlers(
   321  						CombineHandlers(
   322  							VerifyRequest(http.MethodGet, "/v2/foo"),
   323  							RespondWith(http.StatusNotFound, ccResponse, http.Header{"X-Vcap-Request-Id": {"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95", "6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f"}}),
   324  						),
   325  					)
   326  				})
   327  
   328  				It("returns a CCRawResponse", func() {
   329  					req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/foo", server.URL()), nil)
   330  					Expect(err).ToNot(HaveOccurred())
   331  					request := &Request{Request: req}
   332  
   333  					var response Response
   334  					err = connection.Make(request, &response)
   335  					Expect(err).To(MatchError(ccerror.RawHTTPStatusError{
   336  						StatusCode:  http.StatusNotFound,
   337  						RawResponse: []byte(ccResponse),
   338  						RequestIDs:  []string{"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95", "6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f"},
   339  					}))
   340  
   341  					Expect(server.ReceivedRequests()).To(HaveLen(1))
   342  				})
   343  			})
   344  		})
   345  	})
   346  })