github.com/franc20/ayesa_sap@v7.0.0-beta.28.0.20200124003224-302d4d52fa6c+incompatible/api/cloudcontroller/ccv2/buildpack_test.go (about)

     1  package ccv2_test
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/ioutil"
     7  	"mime/multipart"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    13  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2"
    14  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/ccv2fakes"
    15  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/constant"
    16  	"code.cloudfoundry.org/cli/api/cloudcontroller/wrapper"
    17  	"code.cloudfoundry.org/cli/types"
    18  	. "github.com/onsi/ginkgo"
    19  	. "github.com/onsi/gomega"
    20  	. "github.com/onsi/gomega/ghttp"
    21  )
    22  
    23  var _ = Describe("Buildpack", func() {
    24  	var client *Client
    25  
    26  	BeforeEach(func() {
    27  		client = NewTestClient()
    28  	})
    29  
    30  	Describe("CreateBuildpack", func() {
    31  		var (
    32  			inputBuildpack Buildpack
    33  
    34  			resultBuildpack Buildpack
    35  			warnings        Warnings
    36  			executeErr      error
    37  		)
    38  
    39  		JustBeforeEach(func() {
    40  			resultBuildpack, warnings, executeErr = client.CreateBuildpack(inputBuildpack)
    41  		})
    42  
    43  		When("the creation is successful", func() {
    44  			When("all the properties are passed", func() {
    45  				BeforeEach(func() {
    46  					inputBuildpack = Buildpack{
    47  						Name:     "potato",
    48  						Position: types.NullInt{IsSet: true, Value: 1},
    49  						Enabled:  types.NullBool{IsSet: true, Value: true},
    50  						Stack:    "foobar",
    51  					}
    52  
    53  					response := `
    54  				{
    55  					"metadata": {
    56  						"guid": "some-guid"
    57  					},
    58  					"entity": {
    59  						"name": "potato",
    60  						"stack": "foobar",
    61  						"position": 1,
    62  						"enabled": true
    63  					}
    64  				}`
    65  					requestBody := map[string]interface{}{
    66  						"name":     "potato",
    67  						"position": 1,
    68  						"enabled":  true,
    69  						"stack":    "foobar",
    70  					}
    71  					server.AppendHandlers(
    72  						CombineHandlers(
    73  							VerifyRequest(http.MethodPost, "/v2/buildpacks"),
    74  							VerifyJSONRepresenting(requestBody),
    75  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
    76  						),
    77  					)
    78  				})
    79  
    80  				It("creates a buildpack and returns it with any warnings", func() {
    81  					Expect(executeErr).ToNot(HaveOccurred())
    82  
    83  					validateV2InfoPlusNumberOfRequests(1)
    84  
    85  					Expect(resultBuildpack).To(Equal(Buildpack{
    86  						GUID:     "some-guid",
    87  						Name:     "potato",
    88  						Enabled:  types.NullBool{IsSet: true, Value: true},
    89  						Position: types.NullInt{IsSet: true, Value: 1},
    90  						Stack:    "foobar",
    91  					}))
    92  					Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
    93  				})
    94  			})
    95  
    96  			When("the minimum properties are being passed", func() {
    97  				BeforeEach(func() {
    98  					inputBuildpack = Buildpack{
    99  						Name: "potato",
   100  					}
   101  
   102  					response := `
   103  				{
   104  					"metadata": {
   105  						"guid": "some-guid"
   106  					},
   107  					"entity": {
   108  						"name": "potato",
   109  						"stack": null,
   110  						"position": 10000,
   111  						"enabled": true
   112  					}
   113  				}`
   114  					requestBody := map[string]interface{}{
   115  						"name": "potato",
   116  					}
   117  					server.AppendHandlers(
   118  						CombineHandlers(
   119  							VerifyRequest(http.MethodPost, "/v2/buildpacks"),
   120  							VerifyJSONRepresenting(requestBody),
   121  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   122  						),
   123  					)
   124  				})
   125  
   126  				It("creates a buildpack and returns it with any warnings", func() {
   127  					Expect(executeErr).ToNot(HaveOccurred())
   128  
   129  					validateV2InfoPlusNumberOfRequests(1)
   130  
   131  					Expect(resultBuildpack).To(Equal(Buildpack{
   132  						GUID:     "some-guid",
   133  						Name:     "potato",
   134  						Enabled:  types.NullBool{IsSet: true, Value: true},
   135  						Position: types.NullInt{IsSet: true, Value: 10000},
   136  					}))
   137  					Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   138  				})
   139  			})
   140  
   141  		})
   142  
   143  		When("the create returns an error", func() {
   144  			BeforeEach(func() {
   145  				response := `
   146  										{
   147  											"description": "Request invalid due to parse error: Field: name, Error: Missing field name",
   148  											"error_code": "CF-MessageParseError",
   149  											"code": 1001
   150  										}
   151  									`
   152  				server.AppendHandlers(
   153  					CombineHandlers(
   154  						VerifyRequest(http.MethodPost, "/v2/buildpacks"),
   155  						RespondWith(http.StatusBadRequest, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   156  					),
   157  				)
   158  			})
   159  
   160  			It("returns the error and warnings", func() {
   161  				Expect(executeErr).To(MatchError(ccerror.BadRequestError{Message: "Request invalid due to parse error: Field: name, Error: Missing field name"}))
   162  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   163  			})
   164  		})
   165  	})
   166  
   167  	Describe("GetBuildpacks", func() {
   168  		var (
   169  			buildpacks []Buildpack
   170  			warnings   Warnings
   171  			executeErr error
   172  		)
   173  
   174  		JustBeforeEach(func() {
   175  			bpName := Filter{
   176  				Type:     constant.NameFilter,
   177  				Operator: constant.EqualOperator,
   178  				Values:   []string{"some-bp-name"},
   179  			}
   180  
   181  			buildpacks, warnings, executeErr = client.GetBuildpacks(bpName)
   182  		})
   183  
   184  		When("buildpacks are found", func() {
   185  			BeforeEach(func() {
   186  				response1 := `{
   187  										"next_url": "/v2/buildpacks?q=name:some-bp-name",
   188  										"resources": [
   189  											{
   190  												"metadata": {
   191  												"guid": "some-bp-guid1"
   192  												},
   193  												"entity": {
   194  													"name": "some-bp-name1",
   195  													"stack": null,
   196  													"position": 2,
   197  													"enabled": true
   198  												}
   199  											},
   200  											{
   201  												"metadata": {
   202  													"guid": "some-bp-guid2"
   203  												},
   204  												"entity": {
   205  													"name": "some-bp-name2",
   206  													"stack": null,
   207  													"position": 3,
   208  													"enabled": false
   209  												}
   210  											}
   211  										]
   212  									}`
   213  				response2 := `{
   214  										"next_url": null,
   215  										"resources": [
   216  											{
   217  												"metadata": {
   218  													"guid": "some-bp-guid3"
   219  												},
   220  												"entity": {
   221  													"name": "some-bp-name3",
   222  													"stack": "cflinuxfs2",
   223  													"position": 4,
   224  													"enabled": true
   225  												}
   226  											}
   227  										]
   228  									}`
   229  				server.AppendHandlers(
   230  					CombineHandlers(
   231  						VerifyRequest(http.MethodGet, "/v2/buildpacks", "q=name:some-bp-name"),
   232  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"first warning"}}),
   233  					),
   234  				)
   235  				server.AppendHandlers(
   236  					CombineHandlers(
   237  						VerifyRequest(http.MethodGet, "/v2/buildpacks", "q=name:some-bp-name"),
   238  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"second warning"}}),
   239  					),
   240  				)
   241  			})
   242  
   243  			It("returns the buildpacks", func() {
   244  				Expect(executeErr).ToNot(HaveOccurred())
   245  				Expect(buildpacks).To(Equal([]Buildpack{
   246  					{
   247  						Name:     "some-bp-name1",
   248  						GUID:     "some-bp-guid1",
   249  						Enabled:  types.NullBool{IsSet: true, Value: true},
   250  						Position: types.NullInt{IsSet: true, Value: 2},
   251  						Stack:    "",
   252  					},
   253  					{
   254  						Name:     "some-bp-name2",
   255  						GUID:     "some-bp-guid2",
   256  						Enabled:  types.NullBool{IsSet: true, Value: false},
   257  						Position: types.NullInt{IsSet: true, Value: 3},
   258  						Stack:    "",
   259  					},
   260  					{
   261  						Name:     "some-bp-name3",
   262  						GUID:     "some-bp-guid3",
   263  						Enabled:  types.NullBool{IsSet: true, Value: true},
   264  						Position: types.NullInt{IsSet: true, Value: 4},
   265  						Stack:    "cflinuxfs2",
   266  					},
   267  				}))
   268  
   269  				Expect(warnings).To(ConsistOf(Warnings{"first warning", "second warning"}))
   270  			})
   271  		})
   272  
   273  		When("no buildpacks are found", func() {
   274  			BeforeEach(func() {
   275  				response := `{
   276  										"resources": []
   277  									}`
   278  				server.AppendHandlers(
   279  					CombineHandlers(
   280  						VerifyRequest(http.MethodGet, "/v2/buildpacks", "q=name:some-bp-name"),
   281  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   282  					),
   283  				)
   284  			})
   285  
   286  			It("returns an empty list", func() {
   287  				Expect(executeErr).ToNot(HaveOccurred())
   288  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   289  				Expect(buildpacks).To(HaveLen(0))
   290  			})
   291  		})
   292  
   293  		When("the API responds with an error", func() {
   294  			BeforeEach(func() {
   295  				response := `{
   296  										"code": 10001,
   297  										"description": "Whoops",
   298  										"error_code": "CF-SomeError"
   299  									}`
   300  				server.AppendHandlers(
   301  					CombineHandlers(
   302  						VerifyRequest(http.MethodGet, "/v2/buildpacks", "q=name:some-bp-name"),
   303  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   304  					),
   305  				)
   306  			})
   307  
   308  			It("returns warnings and the error", func() {
   309  				Expect(executeErr).To(MatchError(ccerror.V2UnexpectedResponseError{
   310  					ResponseCode: http.StatusTeapot,
   311  					V2ErrorResponse: ccerror.V2ErrorResponse{
   312  						Code:        10001,
   313  						Description: "Whoops",
   314  						ErrorCode:   "CF-SomeError",
   315  					},
   316  				}))
   317  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   318  			})
   319  		})
   320  	})
   321  
   322  	Describe("UpdateBuildpack", func() {
   323  		var (
   324  			buildpack        Buildpack
   325  			updatedBuildpack Buildpack
   326  			warnings         Warnings
   327  			executeErr       error
   328  		)
   329  
   330  		JustBeforeEach(func() {
   331  			updatedBuildpack, warnings, executeErr = client.UpdateBuildpack(buildpack)
   332  		})
   333  
   334  		When("the buildpack exists", func() {
   335  			When("all the properties are provided", func() {
   336  				When("the provided properties are golang non-zero values", func() {
   337  					BeforeEach(func() {
   338  						buildpack = Buildpack{
   339  							Name:     "some-bp-name",
   340  							Position: types.NullInt{IsSet: true, Value: 10},
   341  							Enabled:  types.NullBool{IsSet: true, Value: true},
   342  							Locked:   types.NullBool{IsSet: true, Value: true},
   343  							GUID:     "some-bp-guid",
   344  						}
   345  
   346  						response := `
   347  										{
   348  											"metadata": {
   349  											     "guid": "some-bp-guid"
   350  											},
   351  											"entity": {
   352  												"name": "some-bp-name",
   353  												"stack": null,
   354  												"position": 10,
   355  												"enabled": true,
   356  												"locked": true
   357  											}
   358  										}
   359  									`
   360  
   361  						requestBody := map[string]interface{}{
   362  							"name":     "some-bp-name",
   363  							"position": 10,
   364  							"enabled":  true,
   365  							"locked":   true,
   366  						}
   367  
   368  						server.AppendHandlers(
   369  							CombineHandlers(
   370  								VerifyRequest(http.MethodPut, "/v2/buildpacks/some-bp-guid"),
   371  								VerifyJSONRepresenting(requestBody),
   372  								RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   373  							),
   374  						)
   375  					})
   376  
   377  					It("updates and returns the updated buildpack", func() {
   378  						Expect(executeErr).ToNot(HaveOccurred())
   379  						validateV2InfoPlusNumberOfRequests(1)
   380  						Expect(warnings).To(ConsistOf("this is a warning"))
   381  						Expect(updatedBuildpack).To(Equal(buildpack))
   382  					})
   383  				})
   384  
   385  				When("the provided properties are golang zero values", func() {
   386  					BeforeEach(func() {
   387  						buildpack = Buildpack{
   388  							Name:     "some-bp-name",
   389  							GUID:     "some-bp-guid",
   390  							Position: types.NullInt{IsSet: true, Value: 0},
   391  							Enabled:  types.NullBool{IsSet: true, Value: false},
   392  							Locked:   types.NullBool{IsSet: true, Value: false},
   393  						}
   394  
   395  						response := `
   396  										{
   397  											"metadata": {
   398  											"guid": "some-bp-guid"
   399  											},
   400  											"entity": {
   401  												"name": "some-bp-name",
   402  												"stack": null,
   403  												"position": 0,
   404  												"enabled": false,
   405  												"locked": false
   406  											}
   407  										}
   408  									`
   409  						requestBody := map[string]interface{}{
   410  							"name":     "some-bp-name",
   411  							"position": 0,
   412  							"enabled":  false,
   413  							"locked":   false,
   414  						}
   415  
   416  						server.AppendHandlers(
   417  							CombineHandlers(
   418  								VerifyRequest(http.MethodPut, "/v2/buildpacks/some-bp-guid"),
   419  								VerifyJSONRepresenting(requestBody),
   420  								RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   421  							),
   422  						)
   423  					})
   424  
   425  					It("updates and returns the updated buildpack", func() {
   426  						Expect(executeErr).ToNot(HaveOccurred())
   427  						validateV2InfoPlusNumberOfRequests(1)
   428  						Expect(warnings).To(ConsistOf("this is a warning"))
   429  						Expect(updatedBuildpack).To(Equal(buildpack))
   430  					})
   431  				})
   432  			})
   433  		})
   434  
   435  		When("the buildpack does not exist", func() {
   436  			BeforeEach(func() {
   437  				response := `{
   438  										"description": "buildpack not found",
   439  										"error_code": "CF-NotFound",
   440  										"code": 10000
   441  									}`
   442  
   443  				server.AppendHandlers(
   444  					CombineHandlers(
   445  						VerifyRequest(http.MethodPut, "/v2/buildpacks/some-bp-guid"),
   446  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   447  					),
   448  				)
   449  
   450  				buildpack = Buildpack{
   451  					GUID: "some-bp-guid",
   452  				}
   453  
   454  			})
   455  
   456  			It("returns the error and warnings", func() {
   457  				Expect(executeErr).To(MatchError(ccerror.ResourceNotFoundError{
   458  					Message: "buildpack not found",
   459  				}))
   460  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   461  			})
   462  		})
   463  
   464  		When("the API errors", func() {
   465  			BeforeEach(func() {
   466  				response := `{
   467  										"code": 10001,
   468  										"description": "Some Error",
   469  										"error_code": "CF-SomeError"
   470  									}`
   471  
   472  				server.AppendHandlers(
   473  					CombineHandlers(
   474  						VerifyRequest(http.MethodPut, "/v2/buildpacks/some-bp-guid"),
   475  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   476  					),
   477  				)
   478  
   479  				buildpack = Buildpack{
   480  					GUID: "some-bp-guid",
   481  				}
   482  			})
   483  
   484  			It("returns the error and warnings", func() {
   485  				Expect(executeErr).To(MatchError(ccerror.V2UnexpectedResponseError{
   486  					ResponseCode: http.StatusTeapot,
   487  					V2ErrorResponse: ccerror.V2ErrorResponse{
   488  						Code:        10001,
   489  						Description: "Some Error",
   490  						ErrorCode:   "CF-SomeError",
   491  					},
   492  				}))
   493  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   494  			})
   495  		})
   496  	})
   497  
   498  	Describe("UploadBuildpack", func() {
   499  		var (
   500  			warnings   Warnings
   501  			executeErr error
   502  			bpFile     io.Reader
   503  			bpFilePath string
   504  			bpContent  string
   505  		)
   506  
   507  		BeforeEach(func() {
   508  			bpContent = "some-content"
   509  			bpFile = strings.NewReader(bpContent)
   510  			bpFilePath = "some/fake-buildpack.zip"
   511  		})
   512  
   513  		JustBeforeEach(func() {
   514  			warnings, executeErr = client.UploadBuildpack("some-buildpack-guid", bpFilePath, bpFile, int64(len(bpContent)))
   515  		})
   516  
   517  		When("the upload is successful", func() {
   518  			BeforeEach(func() {
   519  				response := `{
   520  										"metadata": {
   521  											"guid": "some-buildpack-guid",
   522  											"url": "/v2/buildpacks/buildpack-guid/bits"
   523  										},
   524  										"entity": {
   525  											"guid": "some-buildpack-guid",
   526  											"status": "queued"
   527  										}
   528  									}`
   529  
   530  				verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) {
   531  					contentType := req.Header.Get("Content-Type")
   532  					Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   533  
   534  					defer req.Body.Close()
   535  					requestReader := multipart.NewReader(req.Body, contentType[30:])
   536  
   537  					buildpackPart, err := requestReader.NextPart()
   538  					Expect(err).NotTo(HaveOccurred())
   539  
   540  					Expect(buildpackPart.FormName()).To(Equal("buildpack"))
   541  					Expect(buildpackPart.FileName()).To(Equal("fake-buildpack.zip"))
   542  
   543  					defer buildpackPart.Close()
   544  					partContents, err := ioutil.ReadAll(buildpackPart)
   545  					Expect(err).ToNot(HaveOccurred())
   546  					Expect(string(partContents)).To(Equal(bpContent))
   547  				}
   548  
   549  				server.AppendHandlers(
   550  					CombineHandlers(
   551  						VerifyRequest(http.MethodPut, "/v2/buildpacks/some-buildpack-guid/bits"),
   552  						verifyHeaderAndBody,
   553  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   554  					),
   555  				)
   556  			})
   557  
   558  			It("returns warnings", func() {
   559  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   560  				Expect(executeErr).ToNot(HaveOccurred())
   561  			})
   562  		})
   563  
   564  		When("there is an error reading the buildpack", func() {
   565  			var (
   566  				fakeReader  *ccv2fakes.FakeReader
   567  				expectedErr error
   568  			)
   569  
   570  			BeforeEach(func() {
   571  				expectedErr = errors.New("some read error")
   572  				fakeReader = new(ccv2fakes.FakeReader)
   573  				fakeReader.ReadReturns(0, expectedErr)
   574  				bpFile = fakeReader
   575  
   576  				server.AppendHandlers(
   577  					VerifyRequest(http.MethodPut, "/v2/buildpacks/some-buildpack-guid/bits"),
   578  				)
   579  			})
   580  
   581  			It("returns the error", func() {
   582  				Expect(executeErr).To(MatchError(expectedErr))
   583  			})
   584  		})
   585  
   586  		When("the upload returns an error", func() {
   587  			BeforeEach(func() {
   588  				response := `{
   589  										"code": 30003,
   590  										"description": "The buildpack could not be found: some-buildpack-guid",
   591  										"error_code": "CF-Banana"
   592  									}`
   593  
   594  				server.AppendHandlers(
   595  					CombineHandlers(
   596  						VerifyRequest(http.MethodPut, "/v2/buildpacks/some-buildpack-guid/bits"),
   597  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   598  					),
   599  				)
   600  			})
   601  
   602  			It("returns the error and warnings", func() {
   603  				Expect(executeErr).To(MatchError(ccerror.ResourceNotFoundError{Message: "The buildpack could not be found: some-buildpack-guid"}))
   604  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   605  			})
   606  		})
   607  
   608  		When("a retryable error occurs", func() {
   609  			BeforeEach(func() {
   610  				wrapper := &wrapper.CustomWrapper{
   611  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   612  						defer GinkgoRecover() // Since this will be running in a thread
   613  
   614  						if strings.HasSuffix(request.URL.String(), "/v2/buildpacks/some-buildpack-guid/bits") {
   615  							_, err := ioutil.ReadAll(request.Body)
   616  							Expect(err).ToNot(HaveOccurred())
   617  							Expect(request.Body.Close()).ToNot(HaveOccurred())
   618  							return request.ResetBody()
   619  						}
   620  						return connection.Make(request, response)
   621  					},
   622  				}
   623  
   624  				client = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   625  			})
   626  
   627  			It("returns the PipeSeekError", func() {
   628  				Expect(executeErr).To(MatchError(ccerror.PipeSeekError{}))
   629  			})
   630  		})
   631  
   632  		When("an http error occurs mid-transfer", func() {
   633  			var expectedErr error
   634  
   635  			BeforeEach(func() {
   636  				expectedErr = errors.New("some read error")
   637  
   638  				wrapper := &wrapper.CustomWrapper{
   639  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   640  						defer GinkgoRecover() // Since this will be running in a thread
   641  
   642  						if strings.HasSuffix(request.URL.String(), "/v2/buildpacks/some-buildpack-guid/bits") {
   643  							defer request.Body.Close()
   644  							readBytes, err := ioutil.ReadAll(request.Body)
   645  							Expect(err).ToNot(HaveOccurred())
   646  							Expect(len(readBytes)).To(BeNumerically(">", len(bpContent)))
   647  							return expectedErr
   648  						}
   649  						return connection.Make(request, response)
   650  					},
   651  				}
   652  
   653  				client = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   654  			})
   655  
   656  			It("returns the http error", func() {
   657  				Expect(executeErr).To(MatchError(expectedErr))
   658  			})
   659  		})
   660  	})
   661  })