github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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("GetPackage", func() {
    27  		Context("when the package exist", func() {
    28  			BeforeEach(func() {
    29  				response := `{
    30    "guid": "some-pkg-guid",
    31    "state": "PROCESSING_UPLOAD",
    32  	"links": {
    33      "upload": {
    34        "href": "some-package-upload-url",
    35        "method": "POST"
    36      }
    37  	}
    38  }`
    39  				server.AppendHandlers(
    40  					CombineHandlers(
    41  						VerifyRequest(http.MethodGet, "/v3/packages/some-pkg-guid"),
    42  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
    43  					),
    44  				)
    45  			})
    46  
    47  			It("returns the queried packages and all warnings", func() {
    48  				pkg, warnings, err := client.GetPackage("some-pkg-guid")
    49  				Expect(err).NotTo(HaveOccurred())
    50  
    51  				expectedPackage := Package{
    52  					GUID:  "some-pkg-guid",
    53  					State: constant.PackageProcessingUpload,
    54  					Links: map[string]APILink{
    55  						"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
    56  					},
    57  				}
    58  				Expect(pkg).To(Equal(expectedPackage))
    59  				Expect(warnings).To(ConsistOf("this is a warning"))
    60  			})
    61  		})
    62  
    63  		Context("when the cloud controller returns errors and warnings", func() {
    64  			BeforeEach(func() {
    65  				response := `{
    66    "errors": [
    67      {
    68        "code": 10008,
    69        "detail": "The request is semantically invalid: command presence",
    70        "title": "CF-UnprocessableEntity"
    71      },
    72      {
    73        "code": 10010,
    74        "detail": "Package not found",
    75        "title": "CF-ResourceNotFound"
    76      }
    77    ]
    78  }`
    79  				server.AppendHandlers(
    80  					CombineHandlers(
    81  						VerifyRequest(http.MethodGet, "/v3/packages/some-pkg-guid"),
    82  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
    83  					),
    84  				)
    85  			})
    86  
    87  			It("returns the error and all warnings", func() {
    88  				_, warnings, err := client.GetPackage("some-pkg-guid")
    89  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
    90  					ResponseCode: http.StatusTeapot,
    91  					V3ErrorResponse: ccerror.V3ErrorResponse{
    92  						Errors: []ccerror.V3Error{
    93  							{
    94  								Code:   10008,
    95  								Detail: "The request is semantically invalid: command presence",
    96  								Title:  "CF-UnprocessableEntity",
    97  							},
    98  							{
    99  								Code:   10010,
   100  								Detail: "Package not found",
   101  								Title:  "CF-ResourceNotFound",
   102  							},
   103  						},
   104  					},
   105  				}))
   106  				Expect(warnings).To(ConsistOf("this is a warning"))
   107  			})
   108  		})
   109  	})
   110  
   111  	Describe("CreatePackage", func() {
   112  		Context("when the package successfully is created", func() {
   113  			Context("when creating a docker package", func() {
   114  				BeforeEach(func() {
   115  					response := `{
   116  					"data": {
   117  						"image": "some-docker-image",
   118  						"username": "some-username",
   119  						"password": "some-password"
   120  					},
   121  					"guid": "some-pkg-guid",
   122  					"type": "docker",
   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": "docker",
   134  						"data": map[string]string{
   135  							"image":    "some-docker-image",
   136  							"username": "some-username",
   137  							"password": "some-password",
   138  						},
   139  						"relationships": map[string]interface{}{
   140  							"app": map[string]interface{}{
   141  								"data": map[string]string{
   142  									"guid": "some-app-guid",
   143  								},
   144  							},
   145  						},
   146  					}
   147  					server.AppendHandlers(
   148  						CombineHandlers(
   149  							VerifyRequest(http.MethodPost, "/v3/packages"),
   150  							VerifyJSONRepresenting(expectedBody),
   151  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   152  						),
   153  					)
   154  				})
   155  
   156  				It("returns the created package and warnings", func() {
   157  					pkg, warnings, err := client.CreatePackage(Package{
   158  						Type: constant.PackageTypeDocker,
   159  						Relationships: Relationships{
   160  							constant.ApplicationRelationship: Relationship{GUID: "some-app-guid"},
   161  						},
   162  						DockerImage:    "some-docker-image",
   163  						DockerUsername: "some-username",
   164  						DockerPassword: "some-password",
   165  					})
   166  
   167  					Expect(err).NotTo(HaveOccurred())
   168  					Expect(warnings).To(ConsistOf("this is a warning"))
   169  
   170  					expectedPackage := Package{
   171  						GUID:  "some-pkg-guid",
   172  						Type:  constant.PackageTypeDocker,
   173  						State: constant.PackageProcessingUpload,
   174  						Links: map[string]APILink{
   175  							"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   176  						},
   177  						DockerImage:    "some-docker-image",
   178  						DockerUsername: "some-username",
   179  						DockerPassword: "some-password",
   180  					}
   181  					Expect(pkg).To(Equal(expectedPackage))
   182  				})
   183  			})
   184  			Context("when creating a bits package", func() {
   185  				BeforeEach(func() {
   186  					response := `{
   187  					"guid": "some-pkg-guid",
   188  					"type": "bits",
   189  					"state": "PROCESSING_UPLOAD",
   190  					"links": {
   191  						"upload": {
   192  							"href": "some-package-upload-url",
   193  							"method": "POST"
   194  						}
   195  					}
   196  				}`
   197  
   198  					expectedBody := map[string]interface{}{
   199  						"type": "bits",
   200  						"relationships": map[string]interface{}{
   201  							"app": map[string]interface{}{
   202  								"data": map[string]string{
   203  									"guid": "some-app-guid",
   204  								},
   205  							},
   206  						},
   207  					}
   208  					server.AppendHandlers(
   209  						CombineHandlers(
   210  							VerifyRequest(http.MethodPost, "/v3/packages"),
   211  							VerifyJSONRepresenting(expectedBody),
   212  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   213  						),
   214  					)
   215  				})
   216  
   217  				It("omits data, and returns the created package and warnings", func() {
   218  					pkg, warnings, err := client.CreatePackage(Package{
   219  						Type: constant.PackageTypeBits,
   220  						Relationships: Relationships{
   221  							constant.ApplicationRelationship: Relationship{GUID: "some-app-guid"},
   222  						},
   223  					})
   224  
   225  					Expect(err).NotTo(HaveOccurred())
   226  					Expect(warnings).To(ConsistOf("this is a warning"))
   227  
   228  					expectedPackage := Package{
   229  						GUID:  "some-pkg-guid",
   230  						Type:  constant.PackageTypeBits,
   231  						State: constant.PackageProcessingUpload,
   232  						Links: map[string]APILink{
   233  							"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   234  						},
   235  					}
   236  					Expect(pkg).To(Equal(expectedPackage))
   237  				})
   238  			})
   239  		})
   240  
   241  		Context("when cc returns back an error or warnings", func() {
   242  			BeforeEach(func() {
   243  				response := ` {
   244    "errors": [
   245      {
   246        "code": 10008,
   247        "detail": "The request is semantically invalid: command presence",
   248        "title": "CF-UnprocessableEntity"
   249      },
   250      {
   251        "code": 10010,
   252        "detail": "Package not found",
   253        "title": "CF-ResourceNotFound"
   254      }
   255    ]
   256  }`
   257  				server.AppendHandlers(
   258  					CombineHandlers(
   259  						VerifyRequest(http.MethodPost, "/v3/packages"),
   260  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   261  					),
   262  				)
   263  			})
   264  
   265  			It("returns the error and all warnings", func() {
   266  				_, warnings, err := client.CreatePackage(Package{})
   267  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   268  					ResponseCode: http.StatusTeapot,
   269  					V3ErrorResponse: ccerror.V3ErrorResponse{
   270  						Errors: []ccerror.V3Error{
   271  							{
   272  								Code:   10008,
   273  								Detail: "The request is semantically invalid: command presence",
   274  								Title:  "CF-UnprocessableEntity",
   275  							},
   276  							{
   277  								Code:   10010,
   278  								Detail: "Package not found",
   279  								Title:  "CF-ResourceNotFound",
   280  							},
   281  						},
   282  					},
   283  				}))
   284  				Expect(warnings).To(ConsistOf("this is a warning"))
   285  			})
   286  		})
   287  	})
   288  
   289  	Describe("UploadPackage", func() {
   290  		Context("when the package successfully is created", func() {
   291  			var tempFile *os.File
   292  
   293  			BeforeEach(func() {
   294  				var err error
   295  				tempFile, err = ioutil.TempFile("", "package-upload")
   296  				Expect(err).ToNot(HaveOccurred())
   297  				defer tempFile.Close()
   298  
   299  				fileSize := 1024
   300  				contents := strings.Repeat("A", fileSize)
   301  				err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666)
   302  				Expect(err).NotTo(HaveOccurred())
   303  
   304  				verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) {
   305  					contentType := req.Header.Get("Content-Type")
   306  					Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   307  
   308  					boundary := contentType[30:]
   309  
   310  					defer req.Body.Close()
   311  					rawBody, err := ioutil.ReadAll(req.Body)
   312  					Expect(err).NotTo(HaveOccurred())
   313  					body := BufferWithBytes(rawBody)
   314  					Expect(body).To(Say("--%s", boundary))
   315  					Expect(body).To(Say(`name="bits"`))
   316  					Expect(body).To(Say(contents))
   317  					Expect(body).To(Say("--%s--", boundary))
   318  				}
   319  
   320  				response := `{
   321  					"guid": "some-pkg-guid",
   322  					"state": "PROCESSING_UPLOAD",
   323  					"links": {
   324  						"upload": {
   325  							"href": "some-package-upload-url",
   326  							"method": "POST"
   327  						}
   328  					}
   329  				}`
   330  
   331  				server.AppendHandlers(
   332  					CombineHandlers(
   333  						VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"),
   334  						verifyHeaderAndBody,
   335  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   336  					),
   337  				)
   338  			})
   339  
   340  			AfterEach(func() {
   341  				if tempFile != nil {
   342  					Expect(os.Remove(tempFile.Name())).ToNot(HaveOccurred())
   343  				}
   344  			})
   345  
   346  			It("returns the created package and warnings", func() {
   347  				pkg, warnings, err := client.UploadPackage(Package{
   348  					State: constant.PackageAwaitingUpload,
   349  					Links: map[string]APILink{
   350  						"upload": APILink{
   351  							HREF:   fmt.Sprintf("%s/v3/my-special-endpoint/some-pkg-guid/upload", server.URL()),
   352  							Method: http.MethodPost,
   353  						},
   354  					},
   355  				}, tempFile.Name())
   356  
   357  				Expect(err).NotTo(HaveOccurred())
   358  
   359  				expectedPackage := Package{
   360  					GUID:  "some-pkg-guid",
   361  					State: constant.PackageProcessingUpload,
   362  					Links: map[string]APILink{
   363  						"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   364  					},
   365  				}
   366  				Expect(pkg).To(Equal(expectedPackage))
   367  				Expect(warnings).To(ConsistOf("this is a warning"))
   368  			})
   369  		})
   370  
   371  		Context("when the package does not have an upload link", func() {
   372  			It("returns an UploadLinkNotFoundError", func() {
   373  				_, _, err := client.UploadPackage(Package{GUID: "some-pkg-guid", State: constant.PackageAwaitingUpload}, "/path/to/foo")
   374  				Expect(err).To(MatchError(ccerror.UploadLinkNotFoundError{PackageGUID: "some-pkg-guid"}))
   375  			})
   376  		})
   377  	})
   378  
   379  	Describe("GetPackages", func() {
   380  		Context("when cloud controller returns list of packages", func() {
   381  			BeforeEach(func() {
   382  				response := `{
   383  					"resources": [
   384  					  {
   385  						  "guid": "some-pkg-guid-1",
   386  							"type": "bits",
   387  						  "state": "PROCESSING_UPLOAD",
   388  							"created_at": "2017-08-14T21:16:12Z",
   389  							"links": {
   390  								"upload": {
   391  									"href": "some-pkg-upload-url-1",
   392  									"method": "POST"
   393  								}
   394  							}
   395  					  },
   396  					  {
   397  						  "guid": "some-pkg-guid-2",
   398  							"type": "bits",
   399  						  "state": "READY",
   400  							"created_at": "2017-08-14T21:20:13Z",
   401  							"links": {
   402  								"upload": {
   403  									"href": "some-pkg-upload-url-2",
   404  									"method": "POST"
   405  								}
   406  							}
   407  					  }
   408  					]
   409  				}`
   410  				server.AppendHandlers(
   411  					CombineHandlers(
   412  						VerifyRequest(http.MethodGet, "/v3/packages", "app_guids=some-app-guid"),
   413  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   414  					),
   415  				)
   416  			})
   417  
   418  			It("returns the queried packages and all warnings", func() {
   419  				packages, warnings, err := client.GetPackages(Query{Key: AppGUIDFilter, Values: []string{"some-app-guid"}})
   420  				Expect(err).NotTo(HaveOccurred())
   421  
   422  				Expect(packages).To(Equal([]Package{
   423  					{
   424  						GUID:      "some-pkg-guid-1",
   425  						Type:      constant.PackageTypeBits,
   426  						State:     constant.PackageProcessingUpload,
   427  						CreatedAt: "2017-08-14T21:16:12Z",
   428  						Links: map[string]APILink{
   429  							"upload": APILink{HREF: "some-pkg-upload-url-1", Method: http.MethodPost},
   430  						},
   431  					},
   432  					{
   433  						GUID:      "some-pkg-guid-2",
   434  						Type:      constant.PackageTypeBits,
   435  						State:     constant.PackageReady,
   436  						CreatedAt: "2017-08-14T21:20:13Z",
   437  						Links: map[string]APILink{
   438  							"upload": APILink{HREF: "some-pkg-upload-url-2", Method: http.MethodPost},
   439  						},
   440  					},
   441  				}))
   442  				Expect(warnings).To(ConsistOf("this is a warning"))
   443  			})
   444  		})
   445  
   446  		Context("when the cloud controller returns errors and warnings", func() {
   447  			BeforeEach(func() {
   448  				response := `{
   449  					"errors": [
   450  						{
   451  							"code": 10008,
   452  							"detail": "The request is semantically invalid: command presence",
   453  							"title": "CF-UnprocessableEntity"
   454  						},
   455  						{
   456  							"code": 10010,
   457  							"detail": "Package not found",
   458  							"title": "CF-ResourceNotFound"
   459  						}
   460  					]
   461  				}`
   462  				server.AppendHandlers(
   463  					CombineHandlers(
   464  						VerifyRequest(http.MethodGet, "/v3/packages", "app_guids=some-app-guid"),
   465  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   466  					),
   467  				)
   468  			})
   469  
   470  			It("returns the error and all warnings", func() {
   471  				_, warnings, err := client.GetPackages(Query{Key: AppGUIDFilter, Values: []string{"some-app-guid"}})
   472  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   473  					ResponseCode: http.StatusTeapot,
   474  					V3ErrorResponse: ccerror.V3ErrorResponse{
   475  						Errors: []ccerror.V3Error{
   476  							{
   477  								Code:   10008,
   478  								Detail: "The request is semantically invalid: command presence",
   479  								Title:  "CF-UnprocessableEntity",
   480  							},
   481  							{
   482  								Code:   10010,
   483  								Detail: "Package not found",
   484  								Title:  "CF-ResourceNotFound",
   485  							},
   486  						},
   487  					},
   488  				}))
   489  				Expect(warnings).To(ConsistOf("this is a warning"))
   490  			})
   491  		})
   492  
   493  	})
   494  })