github.com/ablease/cli@v6.37.1-0.20180613014814-3adbb7d7fb19+incompatible/api/cloudcontroller/ccv3/package_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"os"
     8  	"strings"
     9  
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    11  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  	. "github.com/onsi/gomega/gbytes"
    16  	. "github.com/onsi/gomega/ghttp"
    17  )
    18  
    19  var _ = Describe("Package", func() {
    20  	var client *Client
    21  
    22  	BeforeEach(func() {
    23  		client = NewTestClient()
    24  	})
    25  
    26  	Describe("CreatePackage", func() {
    27  		var (
    28  			inputPackage Package
    29  
    30  			pkg        Package
    31  			warnings   Warnings
    32  			executeErr error
    33  		)
    34  
    35  		JustBeforeEach(func() {
    36  			pkg, warnings, executeErr = client.CreatePackage(inputPackage)
    37  		})
    38  
    39  		Context("when the package successfully is created", func() {
    40  			Context("when creating a docker package", func() {
    41  				BeforeEach(func() {
    42  					inputPackage = Package{
    43  						Type: constant.PackageTypeDocker,
    44  						Relationships: Relationships{
    45  							constant.RelationshipTypeApplication: Relationship{GUID: "some-app-guid"},
    46  						},
    47  						DockerImage:    "some-docker-image",
    48  						DockerUsername: "some-username",
    49  						DockerPassword: "some-password",
    50  					}
    51  
    52  					response := `{
    53  					"data": {
    54  						"image": "some-docker-image",
    55  						"username": "some-username",
    56  						"password": "some-password"
    57  					},
    58  					"guid": "some-pkg-guid",
    59  					"type": "docker",
    60  					"state": "PROCESSING_UPLOAD",
    61  					"links": {
    62  						"upload": {
    63  							"href": "some-package-upload-url",
    64  							"method": "POST"
    65  						}
    66  					}
    67  				}`
    68  
    69  					expectedBody := map[string]interface{}{
    70  						"type": "docker",
    71  						"data": map[string]string{
    72  							"image":    "some-docker-image",
    73  							"username": "some-username",
    74  							"password": "some-password",
    75  						},
    76  						"relationships": map[string]interface{}{
    77  							"app": map[string]interface{}{
    78  								"data": map[string]string{
    79  									"guid": "some-app-guid",
    80  								},
    81  							},
    82  						},
    83  					}
    84  					server.AppendHandlers(
    85  						CombineHandlers(
    86  							VerifyRequest(http.MethodPost, "/v3/packages"),
    87  							VerifyJSONRepresenting(expectedBody),
    88  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
    89  						),
    90  					)
    91  				})
    92  
    93  				It("returns the created package and warnings", func() {
    94  					Expect(executeErr).NotTo(HaveOccurred())
    95  					Expect(warnings).To(ConsistOf("this is a warning"))
    96  
    97  					expectedPackage := Package{
    98  						GUID:  "some-pkg-guid",
    99  						Type:  constant.PackageTypeDocker,
   100  						State: constant.PackageProcessingUpload,
   101  						Links: map[string]APILink{
   102  							"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   103  						},
   104  						DockerImage:    "some-docker-image",
   105  						DockerUsername: "some-username",
   106  						DockerPassword: "some-password",
   107  					}
   108  					Expect(pkg).To(Equal(expectedPackage))
   109  				})
   110  			})
   111  
   112  			Context("when creating a bits package", func() {
   113  				BeforeEach(func() {
   114  					inputPackage = Package{
   115  						Type: constant.PackageTypeBits,
   116  						Relationships: Relationships{
   117  							constant.RelationshipTypeApplication: Relationship{GUID: "some-app-guid"},
   118  						},
   119  					}
   120  					response := `{
   121  					"guid": "some-pkg-guid",
   122  					"type": "bits",
   123  					"state": "PROCESSING_UPLOAD",
   124  					"links": {
   125  						"upload": {
   126  							"href": "some-package-upload-url",
   127  							"method": "POST"
   128  						}
   129  					}
   130  				}`
   131  
   132  					expectedBody := map[string]interface{}{
   133  						"type": "bits",
   134  						"relationships": map[string]interface{}{
   135  							"app": map[string]interface{}{
   136  								"data": map[string]string{
   137  									"guid": "some-app-guid",
   138  								},
   139  							},
   140  						},
   141  					}
   142  					server.AppendHandlers(
   143  						CombineHandlers(
   144  							VerifyRequest(http.MethodPost, "/v3/packages"),
   145  							VerifyJSONRepresenting(expectedBody),
   146  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   147  						),
   148  					)
   149  				})
   150  
   151  				It("omits data, and returns the created package and warnings", func() {
   152  					Expect(executeErr).NotTo(HaveOccurred())
   153  					Expect(warnings).To(ConsistOf("this is a warning"))
   154  
   155  					expectedPackage := Package{
   156  						GUID:  "some-pkg-guid",
   157  						Type:  constant.PackageTypeBits,
   158  						State: constant.PackageProcessingUpload,
   159  						Links: map[string]APILink{
   160  							"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   161  						},
   162  					}
   163  					Expect(pkg).To(Equal(expectedPackage))
   164  				})
   165  			})
   166  		})
   167  
   168  		Context("when cc returns back an error or warnings", func() {
   169  			BeforeEach(func() {
   170  				inputPackage = Package{}
   171  				response := ` {
   172    "errors": [
   173      {
   174        "code": 10008,
   175        "detail": "The request is semantically invalid: command presence",
   176        "title": "CF-UnprocessableEntity"
   177      },
   178      {
   179        "code": 10010,
   180        "detail": "Package not found",
   181        "title": "CF-ResourceNotFound"
   182      }
   183    ]
   184  }`
   185  				server.AppendHandlers(
   186  					CombineHandlers(
   187  						VerifyRequest(http.MethodPost, "/v3/packages"),
   188  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   189  					),
   190  				)
   191  			})
   192  
   193  			It("returns the error and all warnings", func() {
   194  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   195  					ResponseCode: http.StatusTeapot,
   196  					Errors: []ccerror.V3Error{
   197  						{
   198  							Code:   10008,
   199  							Detail: "The request is semantically invalid: command presence",
   200  							Title:  "CF-UnprocessableEntity",
   201  						},
   202  						{
   203  							Code:   10010,
   204  							Detail: "Package not found",
   205  							Title:  "CF-ResourceNotFound",
   206  						},
   207  					},
   208  				}))
   209  				Expect(warnings).To(ConsistOf("this is a warning"))
   210  			})
   211  		})
   212  	})
   213  
   214  	Describe("GetPackage", func() {
   215  		var (
   216  			pkg        Package
   217  			warnings   Warnings
   218  			executeErr error
   219  		)
   220  
   221  		JustBeforeEach(func() {
   222  			pkg, warnings, executeErr = client.GetPackage("some-pkg-guid")
   223  		})
   224  
   225  		Context("when the package exists", func() {
   226  			BeforeEach(func() {
   227  				response := `{
   228    "guid": "some-pkg-guid",
   229    "state": "PROCESSING_UPLOAD",
   230  	"links": {
   231      "upload": {
   232        "href": "some-package-upload-url",
   233        "method": "POST"
   234      }
   235  	}
   236  }`
   237  				server.AppendHandlers(
   238  					CombineHandlers(
   239  						VerifyRequest(http.MethodGet, "/v3/packages/some-pkg-guid"),
   240  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   241  					),
   242  				)
   243  			})
   244  
   245  			It("returns the queried package and all warnings", func() {
   246  				Expect(executeErr).NotTo(HaveOccurred())
   247  
   248  				expectedPackage := Package{
   249  					GUID:  "some-pkg-guid",
   250  					State: constant.PackageProcessingUpload,
   251  					Links: map[string]APILink{
   252  						"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   253  					},
   254  				}
   255  				Expect(pkg).To(Equal(expectedPackage))
   256  				Expect(warnings).To(ConsistOf("this is a warning"))
   257  			})
   258  		})
   259  
   260  		Context("when the cloud controller returns errors and warnings", func() {
   261  			BeforeEach(func() {
   262  				response := `{
   263    "errors": [
   264      {
   265        "code": 10008,
   266        "detail": "The request is semantically invalid: command presence",
   267        "title": "CF-UnprocessableEntity"
   268      },
   269      {
   270        "code": 10010,
   271        "detail": "Package not found",
   272        "title": "CF-ResourceNotFound"
   273      }
   274    ]
   275  }`
   276  				server.AppendHandlers(
   277  					CombineHandlers(
   278  						VerifyRequest(http.MethodGet, "/v3/packages/some-pkg-guid"),
   279  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   280  					),
   281  				)
   282  			})
   283  
   284  			It("returns the error and all warnings", func() {
   285  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   286  					ResponseCode: http.StatusTeapot,
   287  					Errors: []ccerror.V3Error{
   288  						{
   289  							Code:   10008,
   290  							Detail: "The request is semantically invalid: command presence",
   291  							Title:  "CF-UnprocessableEntity",
   292  						},
   293  						{
   294  							Code:   10010,
   295  							Detail: "Package not found",
   296  							Title:  "CF-ResourceNotFound",
   297  						},
   298  					},
   299  				}))
   300  				Expect(warnings).To(ConsistOf("this is a warning"))
   301  			})
   302  		})
   303  	})
   304  
   305  	Describe("GetPackages", func() {
   306  		var (
   307  			pkgs       []Package
   308  			warnings   Warnings
   309  			executeErr error
   310  		)
   311  
   312  		JustBeforeEach(func() {
   313  			pkgs, warnings, executeErr = client.GetPackages(Query{Key: AppGUIDFilter, Values: []string{"some-app-guid"}})
   314  		})
   315  
   316  		Context("when cloud controller returns list of packages", func() {
   317  			BeforeEach(func() {
   318  				response := `{
   319  					"resources": [
   320  					  {
   321  						  "guid": "some-pkg-guid-1",
   322  							"type": "bits",
   323  						  "state": "PROCESSING_UPLOAD",
   324  							"created_at": "2017-08-14T21:16:12Z",
   325  							"links": {
   326  								"upload": {
   327  									"href": "some-pkg-upload-url-1",
   328  									"method": "POST"
   329  								}
   330  							}
   331  					  },
   332  					  {
   333  						  "guid": "some-pkg-guid-2",
   334  							"type": "bits",
   335  						  "state": "READY",
   336  							"created_at": "2017-08-14T21:20:13Z",
   337  							"links": {
   338  								"upload": {
   339  									"href": "some-pkg-upload-url-2",
   340  									"method": "POST"
   341  								}
   342  							}
   343  					  }
   344  					]
   345  				}`
   346  				server.AppendHandlers(
   347  					CombineHandlers(
   348  						VerifyRequest(http.MethodGet, "/v3/packages", "app_guids=some-app-guid"),
   349  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   350  					),
   351  				)
   352  			})
   353  
   354  			It("returns the queried packages and all warnings", func() {
   355  				Expect(executeErr).NotTo(HaveOccurred())
   356  
   357  				Expect(pkgs).To(Equal([]Package{
   358  					{
   359  						GUID:      "some-pkg-guid-1",
   360  						Type:      constant.PackageTypeBits,
   361  						State:     constant.PackageProcessingUpload,
   362  						CreatedAt: "2017-08-14T21:16:12Z",
   363  						Links: map[string]APILink{
   364  							"upload": APILink{HREF: "some-pkg-upload-url-1", Method: http.MethodPost},
   365  						},
   366  					},
   367  					{
   368  						GUID:      "some-pkg-guid-2",
   369  						Type:      constant.PackageTypeBits,
   370  						State:     constant.PackageReady,
   371  						CreatedAt: "2017-08-14T21:20:13Z",
   372  						Links: map[string]APILink{
   373  							"upload": APILink{HREF: "some-pkg-upload-url-2", Method: http.MethodPost},
   374  						},
   375  					},
   376  				}))
   377  				Expect(warnings).To(ConsistOf("this is a warning"))
   378  			})
   379  		})
   380  
   381  		Context("when the cloud controller returns errors and warnings", func() {
   382  			BeforeEach(func() {
   383  				response := `{
   384  					"errors": [
   385  						{
   386  							"code": 10008,
   387  							"detail": "The request is semantically invalid: command presence",
   388  							"title": "CF-UnprocessableEntity"
   389  						},
   390  						{
   391  							"code": 10010,
   392  							"detail": "Package not found",
   393  							"title": "CF-ResourceNotFound"
   394  						}
   395  					]
   396  				}`
   397  				server.AppendHandlers(
   398  					CombineHandlers(
   399  						VerifyRequest(http.MethodGet, "/v3/packages", "app_guids=some-app-guid"),
   400  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   401  					),
   402  				)
   403  			})
   404  
   405  			It("returns the error and all warnings", func() {
   406  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   407  					ResponseCode: http.StatusTeapot,
   408  					Errors: []ccerror.V3Error{
   409  						{
   410  							Code:   10008,
   411  							Detail: "The request is semantically invalid: command presence",
   412  							Title:  "CF-UnprocessableEntity",
   413  						},
   414  						{
   415  							Code:   10010,
   416  							Detail: "Package not found",
   417  							Title:  "CF-ResourceNotFound",
   418  						},
   419  					},
   420  				}))
   421  				Expect(warnings).To(ConsistOf("this is a warning"))
   422  			})
   423  		})
   424  	})
   425  
   426  	Describe("UploadPackage", func() {
   427  		var (
   428  			inputPackage Package
   429  			fileToUpload string
   430  
   431  			pkg        Package
   432  			warnings   Warnings
   433  			executeErr error
   434  		)
   435  
   436  		JustBeforeEach(func() {
   437  			pkg, warnings, executeErr = client.UploadPackage(inputPackage, fileToUpload)
   438  		})
   439  
   440  		Context("when the package successfully is created", func() {
   441  			var tempFile *os.File
   442  
   443  			BeforeEach(func() {
   444  				var err error
   445  
   446  				inputPackage = Package{
   447  					State: constant.PackageAwaitingUpload,
   448  					Links: map[string]APILink{
   449  						"upload": APILink{
   450  							HREF:   fmt.Sprintf("%s/v3/my-special-endpoint/some-pkg-guid/upload", server.URL()),
   451  							Method: http.MethodPost,
   452  						},
   453  					},
   454  				}
   455  
   456  				tempFile, err = ioutil.TempFile("", "package-upload")
   457  				Expect(err).ToNot(HaveOccurred())
   458  				defer tempFile.Close()
   459  
   460  				fileToUpload = tempFile.Name()
   461  
   462  				fileSize := 1024
   463  				contents := strings.Repeat("A", fileSize)
   464  				err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666)
   465  				Expect(err).NotTo(HaveOccurred())
   466  
   467  				verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) {
   468  					contentType := req.Header.Get("Content-Type")
   469  					Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   470  
   471  					boundary := contentType[30:]
   472  
   473  					defer req.Body.Close()
   474  					rawBody, err := ioutil.ReadAll(req.Body)
   475  					Expect(err).NotTo(HaveOccurred())
   476  					body := BufferWithBytes(rawBody)
   477  					Expect(body).To(Say("--%s", boundary))
   478  					Expect(body).To(Say(`name="bits"`))
   479  					Expect(body).To(Say(contents))
   480  					Expect(body).To(Say("--%s--", boundary))
   481  				}
   482  
   483  				response := `{
   484  					"guid": "some-pkg-guid",
   485  					"state": "PROCESSING_UPLOAD",
   486  					"links": {
   487  						"upload": {
   488  							"href": "some-package-upload-url",
   489  							"method": "POST"
   490  						}
   491  					}
   492  				}`
   493  
   494  				server.AppendHandlers(
   495  					CombineHandlers(
   496  						VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"),
   497  						verifyHeaderAndBody,
   498  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   499  					),
   500  				)
   501  			})
   502  
   503  			AfterEach(func() {
   504  				if tempFile != nil {
   505  					Expect(os.RemoveAll(tempFile.Name())).ToNot(HaveOccurred())
   506  				}
   507  			})
   508  
   509  			It("returns the created package and warnings", func() {
   510  				Expect(executeErr).NotTo(HaveOccurred())
   511  
   512  				expectedPackage := Package{
   513  					GUID:  "some-pkg-guid",
   514  					State: constant.PackageProcessingUpload,
   515  					Links: map[string]APILink{
   516  						"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   517  					},
   518  				}
   519  				Expect(pkg).To(Equal(expectedPackage))
   520  				Expect(warnings).To(ConsistOf("this is a warning"))
   521  			})
   522  		})
   523  
   524  		Context("when the package does not have an upload link", func() {
   525  			BeforeEach(func() {
   526  				inputPackage = Package{GUID: "some-pkg-guid", State: constant.PackageAwaitingUpload}
   527  				fileToUpload = "/path/to/foo"
   528  			})
   529  
   530  			It("returns an UploadLinkNotFoundError", func() {
   531  				Expect(executeErr).To(MatchError(ccerror.UploadLinkNotFoundError{PackageGUID: "some-pkg-guid"}))
   532  			})
   533  		})
   534  
   535  		Context("when cc returns back an error or warnings", func() {
   536  			var tempFile *os.File
   537  
   538  			BeforeEach(func() {
   539  				var err error
   540  
   541  				inputPackage = Package{
   542  					State: constant.PackageAwaitingUpload,
   543  					Links: map[string]APILink{
   544  						"upload": APILink{
   545  							HREF:   fmt.Sprintf("%s/v3/my-special-endpoint/some-pkg-guid/upload", server.URL()),
   546  							Method: http.MethodPost,
   547  						},
   548  					},
   549  				}
   550  
   551  				tempFile, err = ioutil.TempFile("", "package-upload")
   552  				Expect(err).ToNot(HaveOccurred())
   553  				defer tempFile.Close()
   554  
   555  				fileToUpload = tempFile.Name()
   556  
   557  				fileSize := 1024
   558  				contents := strings.Repeat("A", fileSize)
   559  				err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666)
   560  				Expect(err).NotTo(HaveOccurred())
   561  
   562  				response := ` {
   563  					"errors": [
   564  						{
   565  							"code": 10008,
   566  							"detail": "The request is semantically invalid: command presence",
   567  							"title": "CF-UnprocessableEntity"
   568  						},
   569  						{
   570  							"code": 10008,
   571  							"detail": "The request is semantically invalid: command presence",
   572  							"title": "CF-UnprocessableEntity"
   573  						}
   574  					]
   575  				}`
   576  
   577  				server.AppendHandlers(
   578  					CombineHandlers(
   579  						VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"),
   580  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   581  					),
   582  				)
   583  			})
   584  
   585  			AfterEach(func() {
   586  				if tempFile != nil {
   587  					Expect(os.RemoveAll(tempFile.Name())).ToNot(HaveOccurred())
   588  				}
   589  			})
   590  
   591  			It("returns the error and all warnings", func() {
   592  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   593  					ResponseCode: http.StatusTeapot,
   594  					Errors: []ccerror.V3Error{
   595  						{
   596  							Code:   10008,
   597  							Detail: "The request is semantically invalid: command presence",
   598  							Title:  "CF-UnprocessableEntity",
   599  						},
   600  						{
   601  							Code:   10008,
   602  							Detail: "The request is semantically invalid: command presence",
   603  							Title:  "CF-UnprocessableEntity",
   604  						},
   605  					},
   606  				}))
   607  				Expect(warnings).To(ConsistOf("this is a warning"))
   608  			})
   609  
   610  		})
   611  	})
   612  })