github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/api/cloudcontroller/ccv3/application_test.go (about) 1 package ccv3_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 9 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 11 "code.cloudfoundry.org/cli/types" 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 . "github.com/onsi/gomega/ghttp" 15 ) 16 17 var _ = Describe("Application", func() { 18 var client *Client 19 20 BeforeEach(func() { 21 client, _ = NewTestClient() 22 }) 23 24 Describe("Application", func() { 25 Describe("MarshalJSON", func() { 26 var ( 27 app Application 28 appBytes []byte 29 err error 30 ) 31 32 BeforeEach(func() { 33 app = Application{} 34 }) 35 36 JustBeforeEach(func() { 37 appBytes, err = app.MarshalJSON() 38 Expect(err).ToNot(HaveOccurred()) 39 }) 40 41 When("no lifecycle is provided", func() { 42 BeforeEach(func() { 43 app = Application{} 44 }) 45 46 It("omits the lifecycle from the JSON", func() { 47 Expect(string(appBytes)).To(Equal("{}")) 48 }) 49 }) 50 51 When("lifecycle type docker is provided", func() { 52 BeforeEach(func() { 53 app = Application{ 54 LifecycleType: constant.AppLifecycleTypeDocker, 55 } 56 }) 57 58 It("sets lifecycle type to docker with empty data", func() { 59 Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"type":"docker","data":{}}}`)) 60 }) 61 }) 62 63 When("lifecycle type buildpack is provided", func() { 64 BeforeEach(func() { 65 app.LifecycleType = constant.AppLifecycleTypeBuildpack 66 }) 67 68 When("no buildpacks are provided", func() { 69 It("omits the lifecycle from the JSON", func() { 70 Expect(string(appBytes)).To(MatchJSON("{}")) 71 }) 72 73 When("but you do specify a stack", func() { 74 BeforeEach(func() { 75 app.StackName = "cflinuxfs9000" 76 }) 77 78 It("does, in fact, send the stack in the json", func() { 79 Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"stack":"cflinuxfs9000"},"type":"buildpack"}}`)) 80 }) 81 }) 82 }) 83 84 When("default buildpack is provided", func() { 85 BeforeEach(func() { 86 app.LifecycleBuildpacks = []string{"default"} 87 }) 88 89 It("sets the lifecycle buildpack to be empty in the JSON", func() { 90 Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":null},"type":"buildpack"}}`)) 91 }) 92 }) 93 94 When("null buildpack is provided", func() { 95 BeforeEach(func() { 96 app.LifecycleBuildpacks = []string{"null"} 97 }) 98 99 It("sets the Lifecycle buildpack to be empty in the JSON", func() { 100 Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":null},"type":"buildpack"}}`)) 101 }) 102 }) 103 104 When("other buildpacks are provided", func() { 105 BeforeEach(func() { 106 app.LifecycleBuildpacks = []string{"some-buildpack"} 107 }) 108 109 It("sets them in the JSON", func() { 110 Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`)) 111 }) 112 }) 113 }) 114 115 When("metadata is provided", func() { 116 BeforeEach(func() { 117 app = Application{ 118 Metadata: &Metadata{ 119 Labels: map[string]types.NullString{ 120 "some-key": types.NewNullString("some-value"), 121 "other-key": types.NewNullString("other-value\nwith a newline & a \" quote")}, 122 }, 123 } 124 }) 125 126 It("should include the labels in the JSON", func() { 127 Expect(string(appBytes)).To(MatchJSON(`{ 128 "metadata": { 129 "labels": { 130 "some-key":"some-value", 131 "other-key":"other-value\nwith a newline & a \" quote" 132 } 133 } 134 }`)) 135 }) 136 137 When("labels need to be removed", func() { 138 BeforeEach(func() { 139 app = Application{ 140 Metadata: &Metadata{ 141 Labels: map[string]types.NullString{ 142 "some-key": types.NewNullString("some-value"), 143 "other-key": types.NewNullString("other-value\nwith a newline & a \" quote"), 144 "key-to-delete": types.NewNullString(), 145 }, 146 }, 147 } 148 }) 149 150 It("should send nulls for those labels", func() { 151 Expect(string(appBytes)).To(MatchJSON(`{ 152 "metadata": { 153 "labels": { 154 "some-key":"some-value", 155 "other-key":"other-value\nwith a newline & a \" quote", 156 "key-to-delete":null 157 } 158 } 159 }`)) 160 }) 161 }) 162 }) 163 }) 164 165 Describe("UnmarshalJSON", func() { 166 var ( 167 app Application 168 appBytes []byte 169 err error 170 ) 171 172 BeforeEach(func() { 173 appBytes = []byte("{}") 174 app = Application{} 175 }) 176 177 JustBeforeEach(func() { 178 err = json.Unmarshal(appBytes, &app) 179 Expect(err).ToNot(HaveOccurred()) 180 }) 181 182 When("no lifecycle is provided", func() { 183 BeforeEach(func() { 184 appBytes = []byte("{}") 185 }) 186 187 It("omits the lifecycle from the JSON", func() { 188 Expect(app).To(Equal(Application{})) 189 }) 190 }) 191 192 When("lifecycle type docker is provided", func() { 193 BeforeEach(func() { 194 appBytes = []byte(`{"lifecycle":{"type":"docker","data":{}}}`) 195 }) 196 It("sets the lifecycle type to docker with empty data", func() { 197 Expect(app).To(Equal(Application{ 198 LifecycleType: constant.AppLifecycleTypeDocker, 199 })) 200 }) 201 }) 202 203 When("lifecycle type buildpack is provided", func() { 204 205 When("other buildpacks are provided", func() { 206 BeforeEach(func() { 207 appBytes = []byte(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`) 208 }) 209 210 It("sets them in the JSON", func() { 211 Expect(app).To(Equal(Application{ 212 LifecycleType: constant.AppLifecycleTypeBuildpack, 213 LifecycleBuildpacks: []string{"some-buildpack"}, 214 })) 215 }) 216 }) 217 }) 218 219 When("Labels are provided", func() { 220 BeforeEach(func() { 221 appBytes = []byte(`{"metadata":{"labels":{"some-key":"some-value"}}}`) 222 }) 223 224 It("sets the labels", func() { 225 Expect(app).To(Equal(Application{ 226 Metadata: &Metadata{ 227 Labels: map[string]types.NullString{ 228 "some-key": types.NewNullString("some-value"), 229 }, 230 }, 231 })) 232 }) 233 }) 234 }) 235 }) 236 237 Describe("CreateApplication", func() { 238 var ( 239 appToCreate Application 240 241 createdApp Application 242 warnings Warnings 243 executeErr error 244 ) 245 246 JustBeforeEach(func() { 247 createdApp, warnings, executeErr = client.CreateApplication(appToCreate) 248 }) 249 250 When("the application successfully is created", func() { 251 BeforeEach(func() { 252 response := `{ 253 "guid": "some-app-guid", 254 "name": "some-app-name" 255 }` 256 257 expectedBody := map[string]interface{}{ 258 "name": "some-app-name", 259 "relationships": map[string]interface{}{ 260 "space": map[string]interface{}{ 261 "data": map[string]string{ 262 "guid": "some-space-guid", 263 }, 264 }, 265 }, 266 } 267 server.AppendHandlers( 268 CombineHandlers( 269 VerifyRequest(http.MethodPost, "/v3/apps"), 270 VerifyJSONRepresenting(expectedBody), 271 RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 272 ), 273 ) 274 275 appToCreate = Application{ 276 Name: "some-app-name", 277 Relationships: Relationships{ 278 constant.RelationshipTypeSpace: Relationship{GUID: "some-space-guid"}, 279 }, 280 } 281 }) 282 283 It("returns the created app and warnings", func() { 284 Expect(executeErr).NotTo(HaveOccurred()) 285 Expect(warnings).To(ConsistOf("this is a warning")) 286 287 Expect(createdApp).To(Equal(Application{ 288 Name: "some-app-name", 289 GUID: "some-app-guid", 290 })) 291 }) 292 }) 293 294 When("the caller specifies a buildpack", func() { 295 BeforeEach(func() { 296 response := `{ 297 "guid": "some-app-guid", 298 "name": "some-app-name", 299 "lifecycle": { 300 "type": "buildpack", 301 "data": { 302 "buildpacks": ["some-buildpack"] 303 } 304 } 305 }` 306 307 expectedBody := map[string]interface{}{ 308 "name": "some-app-name", 309 "lifecycle": map[string]interface{}{ 310 "type": "buildpack", 311 "data": map[string]interface{}{ 312 "buildpacks": []string{"some-buildpack"}, 313 }, 314 }, 315 "relationships": map[string]interface{}{ 316 "space": map[string]interface{}{ 317 "data": map[string]string{ 318 "guid": "some-space-guid", 319 }, 320 }, 321 }, 322 } 323 server.AppendHandlers( 324 CombineHandlers( 325 VerifyRequest(http.MethodPost, "/v3/apps"), 326 VerifyJSONRepresenting(expectedBody), 327 RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 328 ), 329 ) 330 331 appToCreate = Application{ 332 Name: "some-app-name", 333 LifecycleType: constant.AppLifecycleTypeBuildpack, 334 LifecycleBuildpacks: []string{"some-buildpack"}, 335 Relationships: Relationships{ 336 constant.RelationshipTypeSpace: Relationship{GUID: "some-space-guid"}, 337 }, 338 } 339 }) 340 341 It("returns the created app and warnings", func() { 342 Expect(executeErr).NotTo(HaveOccurred()) 343 Expect(warnings).To(ConsistOf("this is a warning")) 344 345 Expect(createdApp).To(Equal(Application{ 346 Name: "some-app-name", 347 GUID: "some-app-guid", 348 LifecycleType: constant.AppLifecycleTypeBuildpack, 349 LifecycleBuildpacks: []string{"some-buildpack"}, 350 })) 351 }) 352 }) 353 354 When("cc returns back an error or warnings", func() { 355 BeforeEach(func() { 356 response := `{ 357 "errors": [ 358 { 359 "code": 10008, 360 "detail": "The request is semantically invalid: command presence", 361 "title": "CF-UnprocessableEntity" 362 }, 363 { 364 "code": 10010, 365 "detail": "App not found", 366 "title": "CF-ResourceNotFound" 367 } 368 ] 369 }` 370 server.AppendHandlers( 371 CombineHandlers( 372 VerifyRequest(http.MethodPost, "/v3/apps"), 373 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 374 ), 375 ) 376 }) 377 378 It("returns the error and all warnings", func() { 379 Expect(executeErr).To(MatchError(ccerror.MultiError{ 380 ResponseCode: http.StatusTeapot, 381 Errors: []ccerror.V3Error{ 382 { 383 Code: 10008, 384 Detail: "The request is semantically invalid: command presence", 385 Title: "CF-UnprocessableEntity", 386 }, 387 { 388 Code: 10010, 389 Detail: "App not found", 390 Title: "CF-ResourceNotFound", 391 }, 392 }, 393 })) 394 Expect(warnings).To(ConsistOf("this is a warning")) 395 }) 396 }) 397 }) 398 399 Describe("GetApplications", func() { 400 var ( 401 filters []Query 402 403 apps []Application 404 warnings Warnings 405 executeErr error 406 ) 407 408 JustBeforeEach(func() { 409 apps, warnings, executeErr = client.GetApplications(filters...) 410 }) 411 412 When("applications exist", func() { 413 BeforeEach(func() { 414 response1 := fmt.Sprintf(`{ 415 "pagination": { 416 "next": { 417 "href": "%s/v3/apps?space_guids=some-space-guid&names=some-app-name&page=2&per_page=2" 418 } 419 }, 420 "resources": [ 421 { 422 "name": "app-name-1", 423 "guid": "app-guid-1", 424 "lifecycle": { 425 "type": "buildpack", 426 "data": { 427 "buildpacks": ["some-buildpack"], 428 "stack": "some-stack" 429 } 430 } 431 }, 432 { 433 "name": "app-name-2", 434 "guid": "app-guid-2" 435 } 436 ] 437 }`, server.URL()) 438 response2 := `{ 439 "pagination": { 440 "next": null 441 }, 442 "resources": [ 443 { 444 "name": "app-name-3", 445 "guid": "app-guid-3" 446 } 447 ] 448 }` 449 server.AppendHandlers( 450 CombineHandlers( 451 VerifyRequest(http.MethodGet, "/v3/apps", "space_guids=some-space-guid&names=some-app-name"), 452 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 453 ), 454 ) 455 server.AppendHandlers( 456 CombineHandlers( 457 VerifyRequest(http.MethodGet, "/v3/apps", "space_guids=some-space-guid&names=some-app-name&page=2&per_page=2"), 458 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 459 ), 460 ) 461 462 filters = []Query{ 463 {Key: SpaceGUIDFilter, Values: []string{"some-space-guid"}}, 464 {Key: NameFilter, Values: []string{"some-app-name"}}, 465 } 466 }) 467 468 It("returns the queried applications and all warnings", func() { 469 Expect(executeErr).NotTo(HaveOccurred()) 470 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 471 472 Expect(apps).To(ConsistOf( 473 Application{ 474 Name: "app-name-1", 475 GUID: "app-guid-1", 476 StackName: "some-stack", 477 LifecycleType: constant.AppLifecycleTypeBuildpack, 478 LifecycleBuildpacks: []string{"some-buildpack"}, 479 }, 480 Application{Name: "app-name-2", GUID: "app-guid-2"}, 481 Application{Name: "app-name-3", GUID: "app-guid-3"}, 482 )) 483 }) 484 }) 485 486 When("the cloud controller returns errors and warnings", func() { 487 BeforeEach(func() { 488 response := `{ 489 "errors": [ 490 { 491 "code": 10008, 492 "detail": "The request is semantically invalid: command presence", 493 "title": "CF-UnprocessableEntity" 494 }, 495 { 496 "code": 10010, 497 "detail": "App not found", 498 "title": "CF-ResourceNotFound" 499 } 500 ] 501 }` 502 server.AppendHandlers( 503 CombineHandlers( 504 VerifyRequest(http.MethodGet, "/v3/apps"), 505 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 506 ), 507 ) 508 }) 509 510 It("returns the error and all warnings", func() { 511 Expect(executeErr).To(MatchError(ccerror.MultiError{ 512 ResponseCode: http.StatusTeapot, 513 Errors: []ccerror.V3Error{ 514 { 515 Code: 10008, 516 Detail: "The request is semantically invalid: command presence", 517 Title: "CF-UnprocessableEntity", 518 }, 519 { 520 Code: 10010, 521 Detail: "App not found", 522 Title: "CF-ResourceNotFound", 523 }, 524 }, 525 })) 526 Expect(warnings).To(ConsistOf("this is a warning")) 527 }) 528 }) 529 }) 530 531 Describe("UpdateApplication", func() { 532 var ( 533 appToUpdate Application 534 535 updatedApp Application 536 warnings Warnings 537 executeErr error 538 ) 539 540 JustBeforeEach(func() { 541 updatedApp, warnings, executeErr = client.UpdateApplication(appToUpdate) 542 }) 543 544 When("the application successfully is updated", func() { 545 BeforeEach(func() { 546 response := `{ 547 "guid": "some-app-guid", 548 "name": "some-app-name", 549 "lifecycle": { 550 "type": "buildpack", 551 "data": { 552 "buildpacks": ["some-buildpack"], 553 "stack": "some-stack-name" 554 } 555 } 556 }` 557 558 expectedBody := map[string]interface{}{ 559 "name": "some-app-name", 560 "lifecycle": map[string]interface{}{ 561 "type": "buildpack", 562 "data": map[string]interface{}{ 563 "buildpacks": []string{"some-buildpack"}, 564 "stack": "some-stack-name", 565 }, 566 }, 567 "relationships": map[string]interface{}{ 568 "space": map[string]interface{}{ 569 "data": map[string]string{ 570 "guid": "some-space-guid", 571 }, 572 }, 573 }, 574 } 575 server.AppendHandlers( 576 CombineHandlers( 577 VerifyRequest(http.MethodPatch, "/v3/apps/some-app-guid"), 578 VerifyJSONRepresenting(expectedBody), 579 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 580 ), 581 ) 582 583 appToUpdate = Application{ 584 GUID: "some-app-guid", 585 Name: "some-app-name", 586 StackName: "some-stack-name", 587 LifecycleType: constant.AppLifecycleTypeBuildpack, 588 LifecycleBuildpacks: []string{"some-buildpack"}, 589 Relationships: Relationships{ 590 constant.RelationshipTypeSpace: Relationship{GUID: "some-space-guid"}, 591 }, 592 } 593 }) 594 595 It("returns the updated app and warnings", func() { 596 Expect(executeErr).NotTo(HaveOccurred()) 597 Expect(warnings).To(ConsistOf("this is a warning")) 598 599 Expect(updatedApp).To(Equal(Application{ 600 GUID: "some-app-guid", 601 StackName: "some-stack-name", 602 LifecycleBuildpacks: []string{"some-buildpack"}, 603 LifecycleType: constant.AppLifecycleTypeBuildpack, 604 Name: "some-app-name", 605 })) 606 }) 607 }) 608 609 When("cc returns back an error or warnings", func() { 610 BeforeEach(func() { 611 response := `{ 612 "errors": [ 613 { 614 "code": 10008, 615 "detail": "The request is semantically invalid: command presence", 616 "title": "CF-UnprocessableEntity" 617 }, 618 { 619 "code": 10010, 620 "detail": "App not found", 621 "title": "CF-ResourceNotFound" 622 } 623 ] 624 }` 625 server.AppendHandlers( 626 CombineHandlers( 627 VerifyRequest(http.MethodPatch, "/v3/apps/some-app-guid"), 628 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 629 ), 630 ) 631 632 appToUpdate = Application{ 633 GUID: "some-app-guid", 634 } 635 }) 636 637 It("returns the error and all warnings", func() { 638 Expect(executeErr).To(MatchError(ccerror.MultiError{ 639 ResponseCode: http.StatusTeapot, 640 Errors: []ccerror.V3Error{ 641 { 642 Code: 10008, 643 Detail: "The request is semantically invalid: command presence", 644 Title: "CF-UnprocessableEntity", 645 }, 646 { 647 Code: 10010, 648 Detail: "App not found", 649 Title: "CF-ResourceNotFound", 650 }, 651 }, 652 })) 653 Expect(warnings).To(ConsistOf("this is a warning")) 654 }) 655 }) 656 }) 657 658 Describe("UpdateApplicationStop", func() { 659 var ( 660 responseApp Application 661 warnings Warnings 662 executeErr error 663 ) 664 665 JustBeforeEach(func() { 666 responseApp, warnings, executeErr = client.UpdateApplicationStop("some-app-guid") 667 }) 668 669 When("the response succeeds", func() { 670 BeforeEach(func() { 671 response := ` 672 { 673 "guid": "some-app-guid", 674 "name": "some-app", 675 "state": "STOPPED" 676 }` 677 server.AppendHandlers( 678 CombineHandlers( 679 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/stop"), 680 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 681 ), 682 ) 683 }) 684 685 It("returns the application, warnings, and no error", func() { 686 Expect(responseApp).To(Equal(Application{ 687 GUID: "some-app-guid", 688 Name: "some-app", 689 State: constant.ApplicationStopped, 690 })) 691 Expect(executeErr).ToNot(HaveOccurred()) 692 Expect(warnings).To(ConsistOf("this is a warning")) 693 }) 694 }) 695 696 When("the CC returns an error", func() { 697 BeforeEach(func() { 698 response := `{ 699 "errors": [ 700 { 701 "code": 10008, 702 "detail": "The request is semantically invalid: command presence", 703 "title": "CF-UnprocessableEntity" 704 }, 705 { 706 "code": 10010, 707 "detail": "App not found", 708 "title": "CF-ResourceNotFound" 709 } 710 ] 711 }` 712 server.AppendHandlers( 713 CombineHandlers( 714 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/stop"), 715 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 716 ), 717 ) 718 }) 719 720 It("returns no app, the error and all warnings", func() { 721 Expect(executeErr).To(MatchError(ccerror.MultiError{ 722 ResponseCode: http.StatusTeapot, 723 Errors: []ccerror.V3Error{ 724 { 725 Code: 10008, 726 Detail: "The request is semantically invalid: command presence", 727 Title: "CF-UnprocessableEntity", 728 }, 729 { 730 Code: 10010, 731 Detail: "App not found", 732 Title: "CF-ResourceNotFound", 733 }, 734 }, 735 })) 736 Expect(warnings).To(ConsistOf("this is a warning")) 737 }) 738 }) 739 }) 740 741 Describe("UpdateApplicationStart", func() { 742 var ( 743 app Application 744 warnings Warnings 745 executeErr error 746 ) 747 748 JustBeforeEach(func() { 749 app, warnings, executeErr = client.UpdateApplicationStart("some-app-guid") 750 }) 751 752 When("the response succeeds", func() { 753 BeforeEach(func() { 754 response := ` 755 { 756 "guid": "some-app-guid", 757 "name": "some-app" 758 }` 759 server.AppendHandlers( 760 CombineHandlers( 761 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/start"), 762 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 763 ), 764 ) 765 }) 766 767 It("returns warnings and no error", func() { 768 Expect(executeErr).ToNot(HaveOccurred()) 769 Expect(warnings).To(ConsistOf("this is a warning")) 770 Expect(app.GUID).To(Equal("some-app-guid")) 771 }) 772 }) 773 774 When("cc returns back an error or warnings", func() { 775 BeforeEach(func() { 776 response := `{ 777 "errors": [ 778 { 779 "code": 10008, 780 "detail": "The request is semantically invalid: command presence", 781 "title": "CF-UnprocessableEntity" 782 }, 783 { 784 "code": 10010, 785 "detail": "App not found", 786 "title": "CF-ResourceNotFound" 787 } 788 ] 789 }` 790 server.AppendHandlers( 791 CombineHandlers( 792 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/start"), 793 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 794 ), 795 ) 796 }) 797 798 It("returns the error and all warnings", func() { 799 Expect(executeErr).To(MatchError(ccerror.MultiError{ 800 ResponseCode: http.StatusTeapot, 801 Errors: []ccerror.V3Error{ 802 { 803 Code: 10008, 804 Detail: "The request is semantically invalid: command presence", 805 Title: "CF-UnprocessableEntity", 806 }, 807 { 808 Code: 10010, 809 Detail: "App not found", 810 Title: "CF-ResourceNotFound", 811 }, 812 }, 813 })) 814 Expect(warnings).To(ConsistOf("this is a warning")) 815 }) 816 }) 817 }) 818 819 Describe("UpdateApplicationRestart", func() { 820 var ( 821 responseApp Application 822 warnings Warnings 823 executeErr error 824 ) 825 826 JustBeforeEach(func() { 827 responseApp, warnings, executeErr = client.UpdateApplicationRestart("some-app-guid") 828 }) 829 830 When("the response succeeds", func() { 831 BeforeEach(func() { 832 response := ` 833 { 834 "guid": "some-app-guid", 835 "name": "some-app", 836 "state": "STARTED" 837 }` 838 server.AppendHandlers( 839 CombineHandlers( 840 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/restart"), 841 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 842 ), 843 ) 844 }) 845 846 It("returns the application, warnings, and no error", func() { 847 Expect(responseApp).To(Equal(Application{ 848 GUID: "some-app-guid", 849 Name: "some-app", 850 State: constant.ApplicationStarted, 851 })) 852 Expect(executeErr).ToNot(HaveOccurred()) 853 Expect(warnings).To(ConsistOf("this is a warning")) 854 }) 855 }) 856 857 When("the CC returns an error", func() { 858 BeforeEach(func() { 859 response := `{ 860 "errors": [ 861 { 862 "code": 10008, 863 "detail": "The request is semantically invalid: command presence", 864 "title": "CF-UnprocessableEntity" 865 }, 866 { 867 "code": 10010, 868 "detail": "App not found", 869 "title": "CF-ResourceNotFound" 870 } 871 ] 872 }` 873 server.AppendHandlers( 874 CombineHandlers( 875 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/restart"), 876 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 877 ), 878 ) 879 }) 880 881 It("returns no app, the error and all warnings", func() { 882 Expect(executeErr).To(MatchError(ccerror.MultiError{ 883 ResponseCode: http.StatusTeapot, 884 Errors: []ccerror.V3Error{ 885 { 886 Code: 10008, 887 Detail: "The request is semantically invalid: command presence", 888 Title: "CF-UnprocessableEntity", 889 }, 890 { 891 Code: 10010, 892 Detail: "App not found", 893 Title: "CF-ResourceNotFound", 894 }, 895 }, 896 })) 897 Expect(warnings).To(ConsistOf("this is a warning")) 898 }) 899 }) 900 }) 901 })