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