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