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