github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/api/cloudcontroller/ccv3/process_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 . "github.com/onsi/gomega/gstruct" 16 ) 17 18 var _ = Describe("Process", func() { 19 var client *Client 20 21 BeforeEach(func() { 22 client, _ = NewTestClient() 23 }) 24 25 Describe("Process", func() { 26 Describe("MarshalJSON", func() { 27 var ( 28 process Process 29 processBytes []byte 30 err error 31 ) 32 33 BeforeEach(func() { 34 process = Process{} 35 }) 36 37 JustBeforeEach(func() { 38 processBytes, err = process.MarshalJSON() 39 Expect(err).ToNot(HaveOccurred()) 40 }) 41 42 When("instances is provided", func() { 43 BeforeEach(func() { 44 process = Process{ 45 Instances: types.NullInt{Value: 0, IsSet: true}, 46 } 47 }) 48 49 It("sets the instances to be set", func() { 50 Expect(string(processBytes)).To(MatchJSON(`{"instances": 0}`)) 51 }) 52 }) 53 54 When("memory is provided", func() { 55 BeforeEach(func() { 56 process = Process{ 57 MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, 58 } 59 }) 60 61 It("sets the memory to be set", func() { 62 Expect(string(processBytes)).To(MatchJSON(`{"memory_in_mb": 0}`)) 63 }) 64 }) 65 66 When("disk is provided", func() { 67 BeforeEach(func() { 68 process = Process{ 69 DiskInMB: types.NullUint64{Value: 0, IsSet: true}, 70 } 71 }) 72 73 It("sets the disk to be set", func() { 74 Expect(string(processBytes)).To(MatchJSON(`{"disk_in_mb": 0}`)) 75 }) 76 }) 77 78 When("health check type http is provided", func() { 79 BeforeEach(func() { 80 process = Process{ 81 HealthCheckType: constant.HTTP, 82 HealthCheckEndpoint: "some-endpoint", 83 } 84 }) 85 86 It("sets the health check type to http and has an endpoint", func() { 87 Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`)) 88 }) 89 }) 90 91 When("health check type port is provided", func() { 92 BeforeEach(func() { 93 process = Process{ 94 HealthCheckType: constant.Port, 95 } 96 }) 97 98 It("sets the health check type to port", func() { 99 Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"port", "data": {}}}`)) 100 }) 101 }) 102 103 When("health check type process is provided", func() { 104 BeforeEach(func() { 105 process = Process{ 106 HealthCheckType: constant.Process, 107 } 108 }) 109 110 It("sets the health check type to process", func() { 111 Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"process", "data": {}}}`)) 112 }) 113 }) 114 115 When("process has no fields provided", func() { 116 BeforeEach(func() { 117 process = Process{} 118 }) 119 120 It("sets the health check type to process", func() { 121 Expect(string(processBytes)).To(MatchJSON(`{}`)) 122 }) 123 }) 124 }) 125 126 Describe("UnmarshalJSON", func() { 127 var ( 128 process Process 129 processBytes []byte 130 err error 131 ) 132 BeforeEach(func() { 133 processBytes = []byte("{}") 134 }) 135 136 JustBeforeEach(func() { 137 err = json.Unmarshal(processBytes, &process) 138 Expect(err).ToNot(HaveOccurred()) 139 }) 140 When("health check type http is provided", func() { 141 BeforeEach(func() { 142 processBytes = []byte(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`) 143 }) 144 145 It("sets the health check type to http and has an endpoint", func() { 146 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 147 "HealthCheckType": Equal(constant.HTTP), 148 "HealthCheckEndpoint": Equal("some-endpoint"), 149 })) 150 }) 151 }) 152 153 When("health check type port is provided", func() { 154 BeforeEach(func() { 155 processBytes = []byte(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`) 156 }) 157 158 It("sets the health check type to port", func() { 159 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 160 "HealthCheckType": Equal(constant.Port), 161 })) 162 }) 163 }) 164 165 When("health check type process is provided", func() { 166 BeforeEach(func() { 167 processBytes = []byte(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`) 168 }) 169 170 It("sets the health check type to process", func() { 171 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 172 "HealthCheckType": Equal(constant.Process), 173 })) 174 }) 175 }) 176 }) 177 }) 178 179 Describe("GetProcess", func() { 180 var ( 181 process Process 182 warnings []string 183 err error 184 ) 185 186 JustBeforeEach(func() { 187 process, warnings, err = client.GetProcess("some-process-guid") 188 }) 189 190 When("the process exists", func() { 191 BeforeEach(func() { 192 response := `{ 193 "guid": "process-1-guid", 194 "type": "some-type", 195 "command": "start-command-1", 196 "instances": 22, 197 "memory_in_mb": 32, 198 "disk_in_mb": 1024, 199 "health_check": { 200 "type": "http", 201 "data": { 202 "timeout": 90, 203 "endpoint": "/health", 204 "invocation_timeout": 42 205 } 206 } 207 }` 208 server.AppendHandlers( 209 CombineHandlers( 210 VerifyRequest(http.MethodGet, "/v3/processes/some-process-guid"), 211 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 212 ), 213 ) 214 }) 215 216 It("returns the process and all warnings", func() { 217 Expect(err).NotTo(HaveOccurred()) 218 Expect(warnings).To(ConsistOf("this is a warning")) 219 Expect(process).To(MatchAllFields(Fields{ 220 "GUID": Equal("process-1-guid"), 221 "Type": Equal("some-type"), 222 "Command": Equal(types.FilteredString{IsSet: true, Value: "start-command-1"}), 223 "Instances": Equal(types.NullInt{Value: 22, IsSet: true}), 224 "MemoryInMB": Equal(types.NullUint64{Value: 32, IsSet: true}), 225 "DiskInMB": Equal(types.NullUint64{Value: 1024, IsSet: true}), 226 "HealthCheckType": Equal(constant.HTTP), 227 "HealthCheckEndpoint": Equal("/health"), 228 "HealthCheckInvocationTimeout": BeEquivalentTo(42), 229 "HealthCheckTimeout": BeEquivalentTo(90), 230 })) 231 }) 232 }) 233 234 When("the cloud controller returns errors and warnings", func() { 235 BeforeEach(func() { 236 response := `{ 237 "errors": [ 238 { 239 "code": 10008, 240 "detail": "The request is semantically invalid: command presence", 241 "title": "CF-UnprocessableEntity" 242 }, 243 { 244 "code": 10009, 245 "detail": "Some CC Error", 246 "title": "CF-SomeNewError" 247 } 248 ] 249 }` 250 server.AppendHandlers( 251 CombineHandlers( 252 VerifyRequest(http.MethodGet, "/v3/processes/some-process-guid"), 253 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 254 ), 255 ) 256 }) 257 258 It("returns the error and all warnings", func() { 259 Expect(err).To(MatchError(ccerror.MultiError{ 260 ResponseCode: http.StatusTeapot, 261 Errors: []ccerror.V3Error{ 262 { 263 Code: 10008, 264 Detail: "The request is semantically invalid: command presence", 265 Title: "CF-UnprocessableEntity", 266 }, 267 { 268 Code: 10009, 269 Detail: "Some CC Error", 270 Title: "CF-SomeNewError", 271 }, 272 }, 273 })) 274 Expect(warnings).To(ConsistOf("this is a warning")) 275 }) 276 }) 277 }) 278 279 Describe("CreateApplicationProcessScale", func() { 280 var passedProcess Process 281 282 When("providing all scale options", func() { 283 BeforeEach(func() { 284 passedProcess = Process{ 285 Type: constant.ProcessTypeWeb, 286 Instances: types.NullInt{Value: 2, IsSet: true}, 287 MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, 288 DiskInMB: types.NullUint64{Value: 200, IsSet: true}, 289 } 290 expectedBody := `{ 291 "instances": 2, 292 "memory_in_mb": 100, 293 "disk_in_mb": 200 294 }` 295 response := `{ 296 "guid": "some-process-guid" 297 }` 298 server.AppendHandlers( 299 CombineHandlers( 300 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 301 VerifyJSON(expectedBody), 302 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 303 ), 304 ) 305 }) 306 307 It("scales the application process; returns the scaled process and all warnings", func() { 308 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 309 Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")})) 310 Expect(err).ToNot(HaveOccurred()) 311 Expect(warnings).To(ConsistOf("this is a warning")) 312 }) 313 }) 314 315 When("providing all scale options with 0 values", func() { 316 BeforeEach(func() { 317 passedProcess = Process{ 318 Type: constant.ProcessTypeWeb, 319 Instances: types.NullInt{Value: 0, IsSet: true}, 320 MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, 321 DiskInMB: types.NullUint64{Value: 0, IsSet: true}, 322 } 323 expectedBody := `{ 324 "instances": 0, 325 "memory_in_mb": 0, 326 "disk_in_mb": 0 327 }` 328 response := `{ 329 "guid": "some-process-guid" 330 }` 331 server.AppendHandlers( 332 CombineHandlers( 333 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 334 VerifyJSON(expectedBody), 335 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 336 ), 337 ) 338 }) 339 340 It("scales the application process to 0 values; returns the scaled process and all warnings", func() { 341 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 342 Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")})) 343 Expect(err).ToNot(HaveOccurred()) 344 Expect(warnings).To(ConsistOf("this is a warning")) 345 }) 346 }) 347 348 When("providing only one scale option", func() { 349 BeforeEach(func() { 350 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 351 expectedBody := `{ 352 "instances": 2 353 }` 354 response := `{ 355 "guid": "some-process-guid", 356 "instances": 2 357 }` 358 server.AppendHandlers( 359 CombineHandlers( 360 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 361 VerifyJSON(expectedBody), 362 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 363 ), 364 ) 365 }) 366 367 It("scales the application process; returns the process object and all warnings", func() { 368 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 369 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 370 "GUID": Equal("some-process-guid"), 371 "Instances": Equal(types.NullInt{Value: 2, IsSet: true}), 372 })) 373 Expect(err).ToNot(HaveOccurred()) 374 Expect(warnings).To(ConsistOf("this is a warning")) 375 }) 376 }) 377 378 When("an error is encountered", func() { 379 BeforeEach(func() { 380 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 381 response := `{ 382 "errors": [ 383 { 384 "code": 10008, 385 "detail": "The request is semantically invalid: command presence", 386 "title": "CF-UnprocessableEntity" 387 }, 388 { 389 "code": 10009, 390 "detail": "Some CC Error", 391 "title": "CF-SomeNewError" 392 } 393 ] 394 }` 395 server.AppendHandlers( 396 CombineHandlers( 397 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 398 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 399 ), 400 ) 401 }) 402 403 It("returns an empty process, the error and all warnings", func() { 404 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 405 Expect(process).To(BeZero()) 406 Expect(err).To(MatchError(ccerror.MultiError{ 407 ResponseCode: http.StatusTeapot, 408 Errors: []ccerror.V3Error{ 409 { 410 Code: 10008, 411 Detail: "The request is semantically invalid: command presence", 412 Title: "CF-UnprocessableEntity", 413 }, 414 { 415 Code: 10009, 416 Detail: "Some CC Error", 417 Title: "CF-SomeNewError", 418 }, 419 }, 420 })) 421 Expect(warnings).To(ConsistOf("this is a warning")) 422 }) 423 }) 424 }) 425 426 Describe("GetApplicationProcessByType", func() { 427 var ( 428 process Process 429 warnings []string 430 err error 431 ) 432 433 JustBeforeEach(func() { 434 process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type") 435 }) 436 437 When("the process exists", func() { 438 BeforeEach(func() { 439 response := `{ 440 "guid": "process-1-guid", 441 "type": "some-type", 442 "command": "start-command-1", 443 "instances": 22, 444 "memory_in_mb": 32, 445 "disk_in_mb": 1024, 446 "health_check": { 447 "type": "http", 448 "data": { 449 "timeout": 90, 450 "endpoint": "/health", 451 "invocation_timeout": 42 452 } 453 } 454 }` 455 server.AppendHandlers( 456 CombineHandlers( 457 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 458 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 459 ), 460 ) 461 }) 462 463 It("returns the process and all warnings", func() { 464 Expect(err).NotTo(HaveOccurred()) 465 Expect(warnings).To(ConsistOf("this is a warning")) 466 Expect(process).To(MatchAllFields(Fields{ 467 "GUID": Equal("process-1-guid"), 468 "Type": Equal("some-type"), 469 "Command": Equal(types.FilteredString{IsSet: true, Value: "start-command-1"}), 470 "Instances": Equal(types.NullInt{Value: 22, IsSet: true}), 471 "MemoryInMB": Equal(types.NullUint64{Value: 32, IsSet: true}), 472 "DiskInMB": Equal(types.NullUint64{Value: 1024, IsSet: true}), 473 "HealthCheckType": Equal(constant.HTTP), 474 "HealthCheckEndpoint": Equal("/health"), 475 "HealthCheckInvocationTimeout": BeEquivalentTo(42), 476 "HealthCheckTimeout": BeEquivalentTo(90), 477 })) 478 }) 479 }) 480 481 When("the application does not exist", func() { 482 BeforeEach(func() { 483 response := `{ 484 "errors": [ 485 { 486 "detail": "Application not found", 487 "title": "CF-ResourceNotFound", 488 "code": 10010 489 } 490 ] 491 }` 492 server.AppendHandlers( 493 CombineHandlers( 494 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 495 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 496 ), 497 ) 498 }) 499 500 It("returns a ResourceNotFoundError", func() { 501 Expect(warnings).To(ConsistOf("this is a warning")) 502 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"})) 503 }) 504 }) 505 506 When("the cloud controller returns errors and warnings", func() { 507 BeforeEach(func() { 508 response := `{ 509 "errors": [ 510 { 511 "code": 10008, 512 "detail": "The request is semantically invalid: command presence", 513 "title": "CF-UnprocessableEntity" 514 }, 515 { 516 "code": 10009, 517 "detail": "Some CC Error", 518 "title": "CF-SomeNewError" 519 } 520 ] 521 }` 522 server.AppendHandlers( 523 CombineHandlers( 524 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 525 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 526 ), 527 ) 528 }) 529 530 It("returns the error and all warnings", func() { 531 Expect(err).To(MatchError(ccerror.MultiError{ 532 ResponseCode: http.StatusTeapot, 533 Errors: []ccerror.V3Error{ 534 { 535 Code: 10008, 536 Detail: "The request is semantically invalid: command presence", 537 Title: "CF-UnprocessableEntity", 538 }, 539 { 540 Code: 10009, 541 Detail: "Some CC Error", 542 Title: "CF-SomeNewError", 543 }, 544 }, 545 })) 546 Expect(warnings).To(ConsistOf("this is a warning")) 547 }) 548 }) 549 }) 550 551 Describe("GetApplicationProcesses", func() { 552 When("the application exists", func() { 553 BeforeEach(func() { 554 response1 := fmt.Sprintf(` 555 { 556 "pagination": { 557 "next": { 558 "href": "%s/v3/apps/some-app-guid/processes?page=2" 559 } 560 }, 561 "resources": [ 562 { 563 "guid": "process-1-guid", 564 "type": "web", 565 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 566 "memory_in_mb": 32, 567 "health_check": { 568 "type": "port", 569 "data": { 570 "timeout": null, 571 "endpoint": null 572 } 573 } 574 }, 575 { 576 "guid": "process-2-guid", 577 "type": "worker", 578 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 579 "memory_in_mb": 64, 580 "health_check": { 581 "type": "http", 582 "data": { 583 "timeout": 60, 584 "endpoint": "/health" 585 } 586 } 587 } 588 ] 589 }`, server.URL()) 590 response2 := ` 591 { 592 "pagination": { 593 "next": null 594 }, 595 "resources": [ 596 { 597 "guid": "process-3-guid", 598 "type": "console", 599 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 600 "memory_in_mb": 128, 601 "health_check": { 602 "type": "process", 603 "data": { 604 "timeout": 90, 605 "endpoint": null 606 } 607 } 608 } 609 ] 610 }` 611 server.AppendHandlers( 612 CombineHandlers( 613 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 614 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 615 ), 616 ) 617 server.AppendHandlers( 618 CombineHandlers( 619 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"), 620 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 621 ), 622 ) 623 }) 624 625 It("returns a list of processes associated with the application and all warnings", func() { 626 processes, warnings, err := client.GetApplicationProcesses("some-app-guid") 627 Expect(err).ToNot(HaveOccurred()) 628 629 Expect(processes).To(ConsistOf( 630 Process{ 631 GUID: "process-1-guid", 632 Type: constant.ProcessTypeWeb, 633 Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, 634 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 635 HealthCheckType: constant.Port, 636 HealthCheckTimeout: 0, 637 }, 638 Process{ 639 GUID: "process-2-guid", 640 Type: "worker", 641 Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, 642 MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, 643 HealthCheckType: constant.HTTP, 644 HealthCheckEndpoint: "/health", 645 HealthCheckTimeout: 60, 646 }, 647 Process{ 648 GUID: "process-3-guid", 649 Type: "console", 650 Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, 651 MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, 652 HealthCheckType: constant.Process, 653 HealthCheckTimeout: 90, 654 }, 655 )) 656 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 657 }) 658 }) 659 660 When("cloud controller returns an error", func() { 661 BeforeEach(func() { 662 response := `{ 663 "errors": [ 664 { 665 "code": 10010, 666 "detail": "App not found", 667 "title": "CF-ResourceNotFound" 668 } 669 ] 670 }` 671 server.AppendHandlers( 672 CombineHandlers( 673 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 674 RespondWith(http.StatusNotFound, response), 675 ), 676 ) 677 }) 678 679 It("returns the error", func() { 680 _, _, err := client.GetApplicationProcesses("some-app-guid") 681 Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{})) 682 }) 683 }) 684 }) 685 686 Describe("GetNewApplicationProcesses", func() { 687 When("the application exists", func() { 688 BeforeEach(func() { 689 response1 := fmt.Sprintf(` 690 { 691 "pagination": { 692 "next": { 693 "href": "%s/v3/apps/some-app-guid/processes?page=2" 694 } 695 }, 696 "resources": [ 697 { 698 "guid": "old-web-process-guid", 699 "type": "web", 700 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 701 "memory_in_mb": 32, 702 "health_check": { 703 "type": "port", 704 "data": { 705 "timeout": null, 706 "endpoint": null 707 } 708 } 709 }, 710 { 711 "guid": "new-web-process-guid", 712 "type": "web", 713 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 714 "memory_in_mb": 32, 715 "health_check": { 716 "type": "port", 717 "data": { 718 "timeout": null, 719 "endpoint": null 720 } 721 } 722 }, 723 { 724 "guid": "worker-process-guid", 725 "type": "worker", 726 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 727 "memory_in_mb": 64, 728 "health_check": { 729 "type": "http", 730 "data": { 731 "timeout": 60, 732 "endpoint": "/health" 733 } 734 } 735 } 736 ] 737 }`, server.URL()) 738 739 response2 := ` 740 { 741 "pagination": { 742 "next": null 743 }, 744 "resources": [ 745 { 746 "guid": "console-process-guid", 747 "type": "console", 748 "command": "[PRIVATE DATA HIDDEN IN LISTS]", 749 "memory_in_mb": 128, 750 "health_check": { 751 "type": "process", 752 "data": { 753 "timeout": 90, 754 "endpoint": null 755 } 756 } 757 } 758 ] 759 }` 760 761 deploymentResponse := ` 762 { 763 "state": "DEPLOYING", 764 "new_processes": [ 765 { 766 "guid": "new-web-process-guid", 767 "type": "web" 768 } 769 ] 770 } 771 ` 772 773 server.AppendHandlers( 774 CombineHandlers( 775 VerifyRequest(http.MethodGet, "/v3/deployments/some-deployment-guid"), 776 RespondWith(http.StatusOK, deploymentResponse, http.Header{"X-CF-Warnings": {"warning-1"}}), 777 ), 778 ) 779 server.AppendHandlers( 780 CombineHandlers( 781 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 782 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-2"}}), 783 ), 784 ) 785 server.AppendHandlers( 786 CombineHandlers( 787 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"), 788 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-3"}}), 789 ), 790 ) 791 }) 792 793 It("returns a list of processes associated with the application and all warnings", func() { 794 processes, warnings, err := client.GetNewApplicationProcesses("some-app-guid", "some-deployment-guid") 795 Expect(err).ToNot(HaveOccurred()) 796 797 Expect(processes).To(ConsistOf( 798 Process{ 799 GUID: "new-web-process-guid", 800 Type: constant.ProcessTypeWeb, 801 Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, 802 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 803 HealthCheckType: constant.Port, 804 HealthCheckTimeout: 0, 805 }, 806 Process{ 807 GUID: "worker-process-guid", 808 Type: "worker", 809 Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, 810 MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, 811 HealthCheckType: constant.HTTP, 812 HealthCheckEndpoint: "/health", 813 HealthCheckTimeout: 60, 814 }, 815 Process{ 816 GUID: "console-process-guid", 817 Type: "console", 818 Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"}, 819 MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, 820 HealthCheckType: constant.Process, 821 HealthCheckTimeout: 90, 822 }, 823 )) 824 Expect(warnings).To(ConsistOf("warning-1", "warning-2", "warning-3")) 825 }) 826 }) 827 828 When("cloud controller returns an error", func() { 829 BeforeEach(func() { 830 response := `{ 831 "errors": [ 832 { 833 "code": 10010, 834 "detail": "Deployment not found", 835 "title": "CF-ResourceNotFound" 836 } 837 ] 838 }` 839 server.AppendHandlers( 840 CombineHandlers( 841 VerifyRequest(http.MethodGet, "/v3/deployments/some-deployment-guid"), 842 RespondWith(http.StatusNotFound, response), 843 ), 844 ) 845 }) 846 847 It("returns the error", func() { 848 _, _, err := client.GetNewApplicationProcesses("some-app-guid", "some-deployment-guid") 849 Expect(err).To(MatchError(ccerror.DeploymentNotFoundError{})) 850 }) 851 }) 852 }) 853 854 Describe("UpdateProcess", func() { 855 var ( 856 inputProcess Process 857 858 process Process 859 warnings []string 860 err error 861 ) 862 863 BeforeEach(func() { 864 inputProcess = Process{ 865 GUID: "some-process-guid", 866 } 867 }) 868 869 JustBeforeEach(func() { 870 process, warnings, err = client.UpdateProcess(inputProcess) 871 }) 872 873 When("patching the process succeeds", func() { 874 When("the command is set", func() { 875 When("the start command is an arbitrary command", func() { 876 BeforeEach(func() { 877 inputProcess.Command = types.FilteredString{IsSet: true, Value: "some-command"} 878 879 expectedBody := `{ 880 "command": "some-command" 881 }` 882 883 expectedResponse := `{ 884 "command": "some-command" 885 }` 886 887 server.AppendHandlers( 888 CombineHandlers( 889 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 890 VerifyJSON(expectedBody), 891 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 892 ), 893 ) 894 }) 895 896 It("patches this process's command with the provided command", func() { 897 Expect(err).ToNot(HaveOccurred()) 898 Expect(warnings).To(ConsistOf("this is a warning")) 899 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 900 "Command": Equal(types.FilteredString{IsSet: true, Value: "some-command"}), 901 })) 902 }) 903 }) 904 905 When("the start command reset", func() { 906 BeforeEach(func() { 907 inputProcess.Command = types.FilteredString{IsSet: true} 908 909 expectedBody := `{ 910 "command": null 911 }` 912 913 expectedResponse := `{ 914 "command": "some-default-command" 915 }` 916 917 server.AppendHandlers( 918 CombineHandlers( 919 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 920 VerifyJSON(expectedBody), 921 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 922 ), 923 ) 924 }) 925 926 It("patches this process's command with 'null' and returns the default command", func() { 927 Expect(err).ToNot(HaveOccurred()) 928 Expect(warnings).To(ConsistOf("this is a warning")) 929 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 930 "Command": Equal(types.FilteredString{IsSet: true, Value: "some-default-command"}), 931 })) 932 }) 933 }) 934 }) 935 936 When("the endpoint is set", func() { 937 BeforeEach(func() { 938 inputProcess.HealthCheckEndpoint = "some-endpoint" 939 inputProcess.HealthCheckType = "some-type" 940 941 expectedBody := `{ 942 "health_check": { 943 "type": "some-type", 944 "data": { 945 "endpoint": "some-endpoint" 946 } 947 } 948 }` 949 expectedResponse := `{ 950 "health_check": { 951 "type": "some-type", 952 "data": { 953 "endpoint": "some-endpoint", 954 "invocation_timeout": null 955 } 956 } 957 }` 958 server.AppendHandlers( 959 CombineHandlers( 960 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 961 VerifyJSON(expectedBody), 962 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 963 ), 964 ) 965 }) 966 967 It("patches this process's health check", func() { 968 Expect(err).ToNot(HaveOccurred()) 969 Expect(warnings).To(ConsistOf("this is a warning")) 970 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 971 "HealthCheckType": Equal(constant.HealthCheckType("some-type")), 972 "HealthCheckEndpoint": Equal("some-endpoint"), 973 })) 974 }) 975 }) 976 977 When("the invocation timeout is set", func() { 978 BeforeEach(func() { 979 inputProcess.HealthCheckInvocationTimeout = 42 980 inputProcess.HealthCheckType = "some-type" 981 982 expectedBody := `{ 983 "health_check": { 984 "type": "some-type", 985 "data": { 986 "invocation_timeout": 42 987 } 988 } 989 }` 990 expectedResponse := `{ 991 "health_check": { 992 "type": "some-type", 993 "data": { 994 "invocation_timeout": 42 995 } 996 } 997 }` 998 server.AppendHandlers( 999 CombineHandlers( 1000 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 1001 VerifyJSON(expectedBody), 1002 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1003 ), 1004 ) 1005 }) 1006 1007 It("patches this process's health check", func() { 1008 Expect(err).ToNot(HaveOccurred()) 1009 Expect(warnings).To(ConsistOf("this is a warning")) 1010 Expect(process).To(Equal(Process{ 1011 HealthCheckType: "some-type", 1012 HealthCheckInvocationTimeout: 42, 1013 })) 1014 }) 1015 }) 1016 1017 When("the health check timeout is set", func() { 1018 BeforeEach(func() { 1019 inputProcess.HealthCheckTimeout = 77 1020 inputProcess.HealthCheckType = "some-type" 1021 1022 expectedBody := `{ 1023 "health_check": { 1024 "type": "some-type", 1025 "data": { 1026 "timeout": 77 1027 } 1028 } 1029 }` 1030 expectedResponse := `{ 1031 "health_check": { 1032 "type": "some-type", 1033 "data": { 1034 "timeout": 77 1035 } 1036 } 1037 }` 1038 server.AppendHandlers( 1039 CombineHandlers( 1040 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 1041 VerifyJSON(expectedBody), 1042 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1043 ), 1044 ) 1045 }) 1046 1047 It("patches this process's health check", func() { 1048 Expect(err).ToNot(HaveOccurred()) 1049 Expect(warnings).To(ConsistOf("this is a warning")) 1050 Expect(process).To(Equal(Process{ 1051 HealthCheckType: "some-type", 1052 HealthCheckEndpoint: "", 1053 HealthCheckTimeout: 77, 1054 })) 1055 }) 1056 }) 1057 1058 When("the endpoint and timeout are not set", func() { 1059 BeforeEach(func() { 1060 inputProcess.HealthCheckType = "some-type" 1061 1062 expectedBody := `{ 1063 "health_check": { 1064 "type": "some-type", 1065 "data": {} 1066 } 1067 }` 1068 responseBody := `{ 1069 "health_check": { 1070 "type": "some-type" 1071 } 1072 }` 1073 server.AppendHandlers( 1074 CombineHandlers( 1075 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 1076 VerifyJSON(expectedBody), 1077 RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1078 ), 1079 ) 1080 }) 1081 1082 It("patches this process's health check", func() { 1083 Expect(err).ToNot(HaveOccurred()) 1084 Expect(warnings).To(ConsistOf("this is a warning")) 1085 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 1086 "HealthCheckType": Equal(constant.HealthCheckType("some-type")), 1087 })) 1088 }) 1089 }) 1090 }) 1091 1092 When("the process does not exist", func() { 1093 BeforeEach(func() { 1094 response := `{ 1095 "errors": [ 1096 { 1097 "detail": "Process not found", 1098 "title": "CF-ResourceNotFound", 1099 "code": 10010 1100 } 1101 ] 1102 }` 1103 1104 server.AppendHandlers( 1105 CombineHandlers( 1106 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 1107 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1108 ), 1109 ) 1110 }) 1111 1112 It("returns an error and warnings", func() { 1113 Expect(err).To(MatchError(ccerror.ProcessNotFoundError{})) 1114 Expect(warnings).To(ConsistOf("this is a warning")) 1115 }) 1116 }) 1117 1118 When("the cloud controller returns errors and warnings", func() { 1119 BeforeEach(func() { 1120 response := `{ 1121 "errors": [ 1122 { 1123 "code": 10008, 1124 "detail": "The request is semantically invalid: command presence", 1125 "title": "CF-UnprocessableEntity" 1126 }, 1127 { 1128 "code": 10009, 1129 "detail": "Some CC Error", 1130 "title": "CF-SomeNewError" 1131 } 1132 ] 1133 }` 1134 server.AppendHandlers( 1135 CombineHandlers( 1136 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 1137 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1138 ), 1139 ) 1140 }) 1141 1142 It("returns the error and all warnings", func() { 1143 Expect(err).To(MatchError(ccerror.MultiError{ 1144 ResponseCode: http.StatusTeapot, 1145 Errors: []ccerror.V3Error{ 1146 { 1147 Code: 10008, 1148 Detail: "The request is semantically invalid: command presence", 1149 Title: "CF-UnprocessableEntity", 1150 }, 1151 { 1152 Code: 10009, 1153 Detail: "Some CC Error", 1154 Title: "CF-SomeNewError", 1155 }, 1156 }, 1157 })) 1158 Expect(warnings).To(ConsistOf("this is a warning")) 1159 }) 1160 }) 1161 }) 1162 })