github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/api/plugin/plugin_connection_test.go (about)

     1  package plugin_test
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"runtime"
     9  	"strings"
    10  
    11  	. "code.cloudfoundry.org/cli/api/plugin"
    12  	"code.cloudfoundry.org/cli/api/plugin/pluginerror"
    13  	"code.cloudfoundry.org/cli/api/plugin/pluginfakes"
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  	. "github.com/onsi/gomega/ghttp"
    17  )
    18  
    19  type DummyResponse struct {
    20  	Val1 string      `json:"val1"`
    21  	Val2 int         `json:"val2"`
    22  	Val3 interface{} `json:"val3,omitempty"`
    23  }
    24  
    25  var _ = Describe("Plugin Connection", func() {
    26  	var (
    27  		connection      *PluginConnection
    28  		fakeProxyReader *pluginfakes.FakeProxyReader
    29  	)
    30  
    31  	BeforeEach(func() {
    32  		connection = NewConnection(true, 0)
    33  		fakeProxyReader = new(pluginfakes.FakeProxyReader)
    34  
    35  		fakeProxyReader.WrapStub = func(reader io.Reader) io.ReadCloser {
    36  			return ioutil.NopCloser(reader)
    37  		}
    38  	})
    39  
    40  	Describe("Make", func() {
    41  		Describe("Data Unmarshalling", func() {
    42  			var (
    43  				request      *http.Request
    44  				responseBody string
    45  			)
    46  
    47  			BeforeEach(func() {
    48  				responseBody = `{
    49  					"val1":"2.59.0",
    50  					"val2":2,
    51  					"val3":1111111111111111111
    52  				}`
    53  				server.AppendHandlers(
    54  					CombineHandlers(
    55  						VerifyRequest(http.MethodGet, "/list", ""),
    56  						RespondWith(http.StatusOK, responseBody),
    57  					),
    58  				)
    59  
    60  				var err error
    61  				request, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/list", server.URL()), nil)
    62  				Expect(err).ToNot(HaveOccurred())
    63  			})
    64  
    65  			When("passed a response with a result set", func() {
    66  				It("unmarshals the data into a struct", func() {
    67  					var body DummyResponse
    68  					response := Response{
    69  						Result: &body,
    70  					}
    71  
    72  					err := connection.Make(request, &response, fakeProxyReader)
    73  					Expect(err).NotTo(HaveOccurred())
    74  
    75  					Expect(body.Val1).To(Equal("2.59.0"))
    76  					Expect(body.Val2).To(Equal(2))
    77  
    78  					Expect(fakeProxyReader.StartCallCount()).To(Equal(1))
    79  					Expect(fakeProxyReader.StartArgsForCall(0)).To(BeEquivalentTo(len(responseBody)))
    80  
    81  					Expect(fakeProxyReader.WrapCallCount()).To(Equal(1))
    82  
    83  					Expect(fakeProxyReader.FinishCallCount()).To(Equal(1))
    84  				})
    85  
    86  				It("keeps numbers unmarshalled to interfaces as interfaces", func() {
    87  					var body DummyResponse
    88  					response := Response{
    89  						Result: &body,
    90  					}
    91  
    92  					err := connection.Make(request, &response, nil)
    93  					Expect(err).NotTo(HaveOccurred())
    94  					Expect(fmt.Sprint(body.Val3)).To(Equal("1111111111111111111"))
    95  				})
    96  			})
    97  
    98  			When("passed an empty response", func() {
    99  				It("skips the unmarshalling step", func() {
   100  					var response Response
   101  					err := connection.Make(request, &response, nil)
   102  					Expect(err).NotTo(HaveOccurred())
   103  					Expect(response.Result).To(BeNil())
   104  				})
   105  			})
   106  		})
   107  
   108  		Describe("HTTP Response", func() {
   109  			var request *http.Request
   110  
   111  			BeforeEach(func() {
   112  				response := `{}`
   113  				server.AppendHandlers(
   114  					CombineHandlers(
   115  						VerifyRequest(http.MethodGet, "/list", ""),
   116  						RespondWith(http.StatusOK, response),
   117  					),
   118  				)
   119  
   120  				var err error
   121  				request, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/list", server.URL()), nil)
   122  				Expect(err).ToNot(HaveOccurred())
   123  			})
   124  
   125  			It("returns the status", func() {
   126  				response := Response{}
   127  
   128  				err := connection.Make(request, &response, nil)
   129  				Expect(err).NotTo(HaveOccurred())
   130  
   131  				Expect(response.HTTPResponse.Status).To(Equal("200 OK"))
   132  			})
   133  		})
   134  
   135  		Describe("Request errors", func() {
   136  			When("the server does not exist", func() {
   137  				BeforeEach(func() {
   138  					connection = NewConnection(false, 0)
   139  				})
   140  
   141  				It("returns a RequestError", func() {
   142  					request, err := http.NewRequest(http.MethodGet, "http://i.hope.this.doesnt.exist.com/list", nil)
   143  					Expect(err).ToNot(HaveOccurred())
   144  
   145  					var response Response
   146  					err = connection.Make(request, &response, nil)
   147  					Expect(err).To(HaveOccurred())
   148  
   149  					requestErr, ok := err.(pluginerror.RequestError)
   150  					Expect(ok).To(BeTrue())
   151  					Expect(requestErr.Error()).To(MatchRegexp(".*http://i.hope.this.doesnt.exist.com/list.*"))
   152  				})
   153  			})
   154  
   155  			When("the server does not have a verified certificate", func() {
   156  				Context("skipSSLValidation is false", func() {
   157  					BeforeEach(func() {
   158  						if runtime.GOOS == "darwin" {
   159  							Skip("ssl verification is different on darwin")
   160  						}
   161  						server.AppendHandlers(
   162  							CombineHandlers(
   163  								VerifyRequest(http.MethodGet, "/list"),
   164  							),
   165  						)
   166  
   167  						connection = NewConnection(false, 0)
   168  					})
   169  
   170  					It("returns a UnverifiedServerError", func() {
   171  						request, err := http.NewRequest(http.MethodGet, server.URL(), nil)
   172  						Expect(err).ToNot(HaveOccurred())
   173  
   174  						var response Response
   175  						err = connection.Make(request, &response, nil)
   176  						Expect(err).To(MatchError(pluginerror.UnverifiedServerError{URL: server.URL()}))
   177  					})
   178  				})
   179  			})
   180  
   181  			When("the server's certificate does not match the hostname", func() {
   182  				Context("skipSSLValidation is false", func() {
   183  					BeforeEach(func() {
   184  						if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
   185  							Skip("ssl validation has a different order on windows/darwin, will not be returned properly")
   186  						}
   187  						server.AppendHandlers(
   188  							CombineHandlers(
   189  								VerifyRequest(http.MethodGet, "/"),
   190  							),
   191  						)
   192  
   193  						connection = NewConnection(false, 0)
   194  					})
   195  
   196  					// loopback.cli.fun is a custom DNS record setup to point to 127.0.0.1
   197  					It("returns a SSLValidationHostnameError", func() {
   198  						altHostURL := strings.Replace(server.URL(), "127.0.0.1", "loopback.cli.fun", -1)
   199  						request, err := http.NewRequest(http.MethodGet, altHostURL, nil)
   200  						Expect(err).ToNot(HaveOccurred())
   201  
   202  						var response Response
   203  						err = connection.Make(request, &response, nil)
   204  						Expect(err).To(MatchError(pluginerror.SSLValidationHostnameError{
   205  							Message: "x509: certificate is valid for example.com, not loopback.cli.fun",
   206  						}))
   207  					})
   208  				})
   209  			})
   210  		})
   211  
   212  		Describe("4xx and 5xx response codes", func() {
   213  			When("any 4xx or 5xx response codes are encountered", func() {
   214  				var rawResponse string
   215  
   216  				BeforeEach(func() {
   217  					rawResponse = `{
   218  						"error":"some error"
   219  						"description": "some error description",
   220  					}`
   221  					server.AppendHandlers(
   222  						CombineHandlers(
   223  							VerifyRequest(http.MethodGet, "/list"),
   224  							RespondWith(http.StatusTeapot, rawResponse),
   225  						),
   226  					)
   227  				})
   228  
   229  				It("returns a RawHTTPStatusError", func() {
   230  					request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/list", server.URL()), nil)
   231  					Expect(err).ToNot(HaveOccurred())
   232  
   233  					var response Response
   234  					err = connection.Make(request, &response, nil)
   235  					Expect(err).To(MatchError(pluginerror.RawHTTPStatusError{
   236  						Status:      "418 I'm a teapot",
   237  						RawResponse: []byte(rawResponse),
   238  					}))
   239  
   240  					Expect(server.ReceivedRequests()).To(HaveLen(1))
   241  				})
   242  			})
   243  		})
   244  	})
   245  })