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 })