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