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