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