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