github.com/ablease/cli@v6.37.1-0.20180613014814-3adbb7d7fb19+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 Context("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 Context("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 Context("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 Context("when health check type http is provided", func() { 79 BeforeEach(func() { 80 process = Process{ 81 HealthCheckType: "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 Context("when health check type port is provided", func() { 92 BeforeEach(func() { 93 process = Process{ 94 HealthCheckType: "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": {"endpoint": null}}}`)) 100 }) 101 }) 102 103 Context("when health check type process is provided", func() { 104 BeforeEach(func() { 105 process = Process{ 106 HealthCheckType: "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": {"endpoint": null}}}`)) 112 }) 113 }) 114 115 Context("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 Context("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("http"), 148 "HealthCheckEndpoint": Equal("some-endpoint"), 149 })) 150 }) 151 }) 152 153 Context("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("port"), 161 })) 162 }) 163 }) 164 165 Context("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("process"), 173 })) 174 }) 175 }) 176 }) 177 }) 178 179 Describe("CreateApplicationProcessScale", func() { 180 var passedProcess Process 181 182 Context("when providing all scale options", func() { 183 BeforeEach(func() { 184 passedProcess = Process{ 185 Type: constant.ProcessTypeWeb, 186 Instances: types.NullInt{Value: 2, IsSet: true}, 187 MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, 188 DiskInMB: types.NullUint64{Value: 200, IsSet: true}, 189 } 190 expectedBody := `{ 191 "instances": 2, 192 "memory_in_mb": 100, 193 "disk_in_mb": 200 194 }` 195 response := `{ 196 "guid": "some-process-guid" 197 }` 198 server.AppendHandlers( 199 CombineHandlers( 200 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 201 VerifyJSON(expectedBody), 202 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 203 ), 204 ) 205 }) 206 207 It("scales the application process; returns the scaled process and all warnings", func() { 208 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 209 Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")})) 210 Expect(err).ToNot(HaveOccurred()) 211 Expect(warnings).To(ConsistOf("this is a warning")) 212 }) 213 }) 214 215 Context("when providing all scale options with 0 values", func() { 216 BeforeEach(func() { 217 passedProcess = Process{ 218 Type: constant.ProcessTypeWeb, 219 Instances: types.NullInt{Value: 0, IsSet: true}, 220 MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, 221 DiskInMB: types.NullUint64{Value: 0, IsSet: true}, 222 } 223 expectedBody := `{ 224 "instances": 0, 225 "memory_in_mb": 0, 226 "disk_in_mb": 0 227 }` 228 response := `{ 229 "guid": "some-process-guid" 230 }` 231 server.AppendHandlers( 232 CombineHandlers( 233 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 234 VerifyJSON(expectedBody), 235 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 236 ), 237 ) 238 }) 239 240 It("scales the application process to 0 values; returns the scaled process and all warnings", func() { 241 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 242 Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")})) 243 Expect(err).ToNot(HaveOccurred()) 244 Expect(warnings).To(ConsistOf("this is a warning")) 245 }) 246 }) 247 248 Context("when providing only one scale option", func() { 249 BeforeEach(func() { 250 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 251 expectedBody := `{ 252 "instances": 2 253 }` 254 response := `{ 255 "guid": "some-process-guid", 256 "instances": 2 257 }` 258 server.AppendHandlers( 259 CombineHandlers( 260 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 261 VerifyJSON(expectedBody), 262 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 263 ), 264 ) 265 }) 266 267 It("scales the application process; returns the process object and all warnings", func() { 268 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 269 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 270 "GUID": Equal("some-process-guid"), 271 "Instances": Equal(types.NullInt{Value: 2, IsSet: true}), 272 })) 273 Expect(err).ToNot(HaveOccurred()) 274 Expect(warnings).To(ConsistOf("this is a warning")) 275 }) 276 }) 277 278 Context("when an error is encountered", func() { 279 BeforeEach(func() { 280 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 281 response := `{ 282 "errors": [ 283 { 284 "code": 10008, 285 "detail": "The request is semantically invalid: command presence", 286 "title": "CF-UnprocessableEntity" 287 }, 288 { 289 "code": 10009, 290 "detail": "Some CC Error", 291 "title": "CF-SomeNewError" 292 } 293 ] 294 }` 295 server.AppendHandlers( 296 CombineHandlers( 297 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 298 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 299 ), 300 ) 301 }) 302 303 It("returns an empty process, the error and all warnings", func() { 304 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 305 Expect(process).To(BeZero()) 306 Expect(err).To(MatchError(ccerror.MultiError{ 307 ResponseCode: http.StatusTeapot, 308 Errors: []ccerror.V3Error{ 309 { 310 Code: 10008, 311 Detail: "The request is semantically invalid: command presence", 312 Title: "CF-UnprocessableEntity", 313 }, 314 { 315 Code: 10009, 316 Detail: "Some CC Error", 317 Title: "CF-SomeNewError", 318 }, 319 }, 320 })) 321 Expect(warnings).To(ConsistOf("this is a warning")) 322 }) 323 }) 324 }) 325 326 Describe("GetApplicationProcessByType", func() { 327 var ( 328 process Process 329 warnings []string 330 err error 331 ) 332 333 JustBeforeEach(func() { 334 process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type") 335 }) 336 337 Context("when the process exists", func() { 338 BeforeEach(func() { 339 response := `{ 340 "guid": "process-1-guid", 341 "type": "some-type", 342 "instances": 22, 343 "memory_in_mb": 32, 344 "disk_in_mb": 1024, 345 "health_check": { 346 "type": "http", 347 "data": { 348 "timeout": 90, 349 "endpoint": "/health", 350 "invocation_timeout": 42 351 } 352 } 353 }` 354 server.AppendHandlers( 355 CombineHandlers( 356 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 357 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 358 ), 359 ) 360 }) 361 362 It("returns the process and all warnings", func() { 363 Expect(err).NotTo(HaveOccurred()) 364 Expect(warnings).To(ConsistOf("this is a warning")) 365 Expect(process).To(MatchAllFields(Fields{ 366 "GUID": Equal("process-1-guid"), 367 "Type": Equal("some-type"), 368 "Instances": Equal(types.NullInt{Value: 22, IsSet: true}), 369 "MemoryInMB": Equal(types.NullUint64{Value: 32, IsSet: true}), 370 "DiskInMB": Equal(types.NullUint64{Value: 1024, IsSet: true}), 371 "HealthCheckType": Equal("http"), 372 "HealthCheckEndpoint": Equal("/health"), 373 "HealthCheckInvocationTimeout": Equal(42), 374 })) 375 }) 376 }) 377 378 Context("when the application does not exist", func() { 379 BeforeEach(func() { 380 response := `{ 381 "errors": [ 382 { 383 "detail": "Application not found", 384 "title": "CF-ResourceNotFound", 385 "code": 10010 386 } 387 ] 388 }` 389 server.AppendHandlers( 390 CombineHandlers( 391 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 392 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 393 ), 394 ) 395 }) 396 397 It("returns a ResourceNotFoundError", func() { 398 Expect(warnings).To(ConsistOf("this is a warning")) 399 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"})) 400 }) 401 }) 402 403 Context("when the cloud controller returns errors and warnings", func() { 404 BeforeEach(func() { 405 response := `{ 406 "errors": [ 407 { 408 "code": 10008, 409 "detail": "The request is semantically invalid: command presence", 410 "title": "CF-UnprocessableEntity" 411 }, 412 { 413 "code": 10009, 414 "detail": "Some CC Error", 415 "title": "CF-SomeNewError" 416 } 417 ] 418 }` 419 server.AppendHandlers( 420 CombineHandlers( 421 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 422 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 423 ), 424 ) 425 }) 426 427 It("returns the error and all warnings", func() { 428 Expect(err).To(MatchError(ccerror.MultiError{ 429 ResponseCode: http.StatusTeapot, 430 Errors: []ccerror.V3Error{ 431 { 432 Code: 10008, 433 Detail: "The request is semantically invalid: command presence", 434 Title: "CF-UnprocessableEntity", 435 }, 436 { 437 Code: 10009, 438 Detail: "Some CC Error", 439 Title: "CF-SomeNewError", 440 }, 441 }, 442 })) 443 Expect(warnings).To(ConsistOf("this is a warning")) 444 }) 445 }) 446 }) 447 448 Describe("GetApplicationProcesses", func() { 449 Context("when the application exists", func() { 450 BeforeEach(func() { 451 response1 := fmt.Sprintf(` 452 { 453 "pagination": { 454 "next": { 455 "href": "%s/v3/apps/some-app-guid/processes?page=2" 456 } 457 }, 458 "resources": [ 459 { 460 "guid": "process-1-guid", 461 "type": "web", 462 "memory_in_mb": 32, 463 "health_check": { 464 "type": "port", 465 "data": { 466 "timeout": null, 467 "endpoint": null 468 } 469 } 470 }, 471 { 472 "guid": "process-2-guid", 473 "type": "worker", 474 "memory_in_mb": 64, 475 "health_check": { 476 "type": "http", 477 "data": { 478 "timeout": 60, 479 "endpoint": "/health" 480 } 481 } 482 } 483 ] 484 }`, server.URL()) 485 response2 := ` 486 { 487 "pagination": { 488 "next": null 489 }, 490 "resources": [ 491 { 492 "guid": "process-3-guid", 493 "type": "console", 494 "memory_in_mb": 128, 495 "health_check": { 496 "type": "process", 497 "data": { 498 "timeout": 90, 499 "endpoint": null 500 } 501 } 502 } 503 ] 504 }` 505 server.AppendHandlers( 506 CombineHandlers( 507 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 508 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 509 ), 510 ) 511 server.AppendHandlers( 512 CombineHandlers( 513 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"), 514 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 515 ), 516 ) 517 }) 518 519 It("returns a list of processes associated with the application and all warnings", func() { 520 processes, warnings, err := client.GetApplicationProcesses("some-app-guid") 521 Expect(err).ToNot(HaveOccurred()) 522 523 Expect(processes).To(ConsistOf( 524 Process{ 525 GUID: "process-1-guid", 526 Type: constant.ProcessTypeWeb, 527 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 528 HealthCheckType: "port", 529 }, 530 Process{ 531 GUID: "process-2-guid", 532 Type: "worker", 533 MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, 534 HealthCheckType: "http", 535 HealthCheckEndpoint: "/health", 536 }, 537 Process{ 538 GUID: "process-3-guid", 539 Type: "console", 540 MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, 541 HealthCheckType: "process", 542 }, 543 )) 544 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 545 }) 546 }) 547 548 Context("when cloud controller returns an error", func() { 549 BeforeEach(func() { 550 response := `{ 551 "errors": [ 552 { 553 "code": 10010, 554 "detail": "App not found", 555 "title": "CF-ResourceNotFound" 556 } 557 ] 558 }` 559 server.AppendHandlers( 560 CombineHandlers( 561 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 562 RespondWith(http.StatusNotFound, response), 563 ), 564 ) 565 }) 566 567 It("returns the error", func() { 568 _, _, err := client.GetApplicationProcesses("some-app-guid") 569 Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{})) 570 }) 571 }) 572 }) 573 574 Describe("PatchApplicationProcessHealthCheck", func() { 575 var ( 576 endpoint string 577 invocationTimeout int 578 579 process Process 580 warnings []string 581 err error 582 ) 583 584 JustBeforeEach(func() { 585 process, warnings, err = client.PatchApplicationProcessHealthCheck("some-process-guid", "some-type", endpoint, invocationTimeout) 586 }) 587 588 Context("when patching the process succeeds", func() { 589 Context("and the endpoint is set", func() { 590 BeforeEach(func() { 591 endpoint = "some-endpoint" 592 invocationTimeout = 0 593 594 expectedBody := `{ 595 "health_check": { 596 "type": "some-type", 597 "data": { 598 "endpoint": "some-endpoint" 599 } 600 } 601 }` 602 expectedResponse := `{ 603 "health_check": { 604 "type": "some-type", 605 "data": { 606 "endpoint": "some-endpoint", 607 "invocation_timeout": null 608 } 609 } 610 }` 611 server.AppendHandlers( 612 CombineHandlers( 613 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 614 VerifyJSON(expectedBody), 615 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 616 ), 617 ) 618 }) 619 620 It("patches this process's health check", func() { 621 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 622 "HealthCheckType": Equal("some-type"), 623 "HealthCheckEndpoint": Equal("some-endpoint"), 624 })) 625 Expect(err).ToNot(HaveOccurred()) 626 Expect(warnings).To(ConsistOf("this is a warning")) 627 }) 628 }) 629 630 Context("and invocation timeout is set", func() { 631 BeforeEach(func() { 632 endpoint = "" 633 invocationTimeout = 42 634 635 expectedBody := `{ 636 "health_check": { 637 "type": "some-type", 638 "data": { 639 "endpoint": null, 640 "invocation_timeout": 42 641 } 642 } 643 }` 644 expectedResponse := `{ 645 "health_check": { 646 "type": "some-type", 647 "data": { 648 "endpoint": null, 649 "invocation_timeout": 42 650 } 651 } 652 }` 653 server.AppendHandlers( 654 CombineHandlers( 655 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 656 VerifyJSON(expectedBody), 657 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 658 ), 659 ) 660 }) 661 662 It("patches this process's health check", func() { 663 Expect(process).To(Equal(Process{ 664 HealthCheckType: "some-type", 665 HealthCheckEndpoint: "", 666 HealthCheckInvocationTimeout: 42, 667 })) 668 Expect(err).ToNot(HaveOccurred()) 669 Expect(warnings).To(ConsistOf("this is a warning")) 670 }) 671 }) 672 673 Context("and the endpoint and timeout are not set", func() { 674 BeforeEach(func() { 675 endpoint = "" 676 invocationTimeout = 0 677 678 expectedBody := `{ 679 "health_check": { 680 "type": "some-type", 681 "data": { 682 "endpoint": null 683 } 684 } 685 }` 686 responseBody := `{ 687 "health_check": { 688 "type": "some-type", 689 "data": { 690 "endpoint": null 691 } 692 } 693 }` 694 server.AppendHandlers( 695 CombineHandlers( 696 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 697 VerifyJSON(expectedBody), 698 RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 699 ), 700 ) 701 }) 702 703 It("patches this process's health check", func() { 704 Expect(process).To(MatchFields(IgnoreExtras, Fields{ 705 "HealthCheckType": Equal("some-type"), 706 "HealthCheckEndpoint": Equal(""), 707 })) 708 Expect(err).ToNot(HaveOccurred()) 709 Expect(warnings).To(ConsistOf("this is a warning")) 710 }) 711 }) 712 }) 713 714 Context("when the process does not exist", func() { 715 BeforeEach(func() { 716 endpoint = "some-endpoint" 717 response := `{ 718 "errors": [ 719 { 720 "detail": "Process not found", 721 "title": "CF-ResourceNotFound", 722 "code": 10010 723 } 724 ] 725 }` 726 727 server.AppendHandlers( 728 CombineHandlers( 729 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 730 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 731 ), 732 ) 733 }) 734 735 It("returns an error and warnings", func() { 736 Expect(err).To(MatchError(ccerror.ProcessNotFoundError{})) 737 Expect(warnings).To(ConsistOf("this is a warning")) 738 }) 739 }) 740 741 Context("when the cloud controller returns errors and warnings", func() { 742 BeforeEach(func() { 743 endpoint = "some-endpoint" 744 response := `{ 745 "errors": [ 746 { 747 "code": 10008, 748 "detail": "The request is semantically invalid: command presence", 749 "title": "CF-UnprocessableEntity" 750 }, 751 { 752 "code": 10009, 753 "detail": "Some CC Error", 754 "title": "CF-SomeNewError" 755 } 756 ] 757 }` 758 server.AppendHandlers( 759 CombineHandlers( 760 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 761 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 762 ), 763 ) 764 }) 765 766 It("returns the error and all warnings", func() { 767 Expect(err).To(MatchError(ccerror.MultiError{ 768 ResponseCode: http.StatusTeapot, 769 Errors: []ccerror.V3Error{ 770 { 771 Code: 10008, 772 Detail: "The request is semantically invalid: command presence", 773 Title: "CF-UnprocessableEntity", 774 }, 775 { 776 Code: 10009, 777 Detail: "Some CC Error", 778 Title: "CF-SomeNewError", 779 }, 780 }, 781 })) 782 Expect(warnings).To(ConsistOf("this is a warning")) 783 }) 784 }) 785 }) 786 })