github.com/willmadison/cli@v6.40.1-0.20181018160101-29d5937903ff+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:     "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: "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  			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  			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("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("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("process"),
   173  					}))
   174  				})
   175  			})
   176  		})
   177  	})
   178  
   179  	Describe("CreateApplicationProcessScale", func() {
   180  		var passedProcess Process
   181  
   182  		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  		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  		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  		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  		When("the process exists", func() {
   338  			BeforeEach(func() {
   339  				response := `{
   340  					"guid": "process-1-guid",
   341  					"type": "some-type",
   342  					"command": "start-command-1",
   343  					"instances": 22,
   344  					"memory_in_mb": 32,
   345  					"disk_in_mb": 1024,
   346  					"health_check": {
   347  						"type": "http",
   348  						"data": {
   349  							"timeout": 90,
   350  							"endpoint": "/health",
   351  							"invocation_timeout": 42
   352  						}
   353  					}
   354  				}`
   355  				server.AppendHandlers(
   356  					CombineHandlers(
   357  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   358  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   359  					),
   360  				)
   361  			})
   362  
   363  			It("returns the process and all warnings", func() {
   364  				Expect(err).NotTo(HaveOccurred())
   365  				Expect(warnings).To(ConsistOf("this is a warning"))
   366  				Expect(process).To(MatchAllFields(Fields{
   367  					"GUID":                         Equal("process-1-guid"),
   368  					"Type":                         Equal("some-type"),
   369  					"Command":                      Equal("start-command-1"),
   370  					"Instances":                    Equal(types.NullInt{Value: 22, IsSet: true}),
   371  					"MemoryInMB":                   Equal(types.NullUint64{Value: 32, IsSet: true}),
   372  					"DiskInMB":                     Equal(types.NullUint64{Value: 1024, IsSet: true}),
   373  					"HealthCheckType":              Equal("http"),
   374  					"HealthCheckEndpoint":          Equal("/health"),
   375  					"HealthCheckInvocationTimeout": Equal(42),
   376  				}))
   377  			})
   378  		})
   379  
   380  		When("the application does not exist", func() {
   381  			BeforeEach(func() {
   382  				response := `{
   383  					"errors": [
   384  						{
   385  							"detail": "Application not found",
   386  							"title": "CF-ResourceNotFound",
   387  							"code": 10010
   388  						}
   389  					]
   390  				}`
   391  				server.AppendHandlers(
   392  					CombineHandlers(
   393  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   394  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   395  					),
   396  				)
   397  			})
   398  
   399  			It("returns a ResourceNotFoundError", func() {
   400  				Expect(warnings).To(ConsistOf("this is a warning"))
   401  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"}))
   402  			})
   403  		})
   404  
   405  		When("the cloud controller returns errors and warnings", func() {
   406  			BeforeEach(func() {
   407  				response := `{
   408  					"errors": [
   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  				server.AppendHandlers(
   422  					CombineHandlers(
   423  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   424  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   425  					),
   426  				)
   427  			})
   428  
   429  			It("returns the error and all warnings", func() {
   430  				Expect(err).To(MatchError(ccerror.MultiError{
   431  					ResponseCode: http.StatusTeapot,
   432  					Errors: []ccerror.V3Error{
   433  						{
   434  							Code:   10008,
   435  							Detail: "The request is semantically invalid: command presence",
   436  							Title:  "CF-UnprocessableEntity",
   437  						},
   438  						{
   439  							Code:   10009,
   440  							Detail: "Some CC Error",
   441  							Title:  "CF-SomeNewError",
   442  						},
   443  					},
   444  				}))
   445  				Expect(warnings).To(ConsistOf("this is a warning"))
   446  			})
   447  		})
   448  	})
   449  
   450  	Describe("GetApplicationProcesses", func() {
   451  		When("the application exists", func() {
   452  			BeforeEach(func() {
   453  				response1 := fmt.Sprintf(`
   454  					{
   455  						"pagination": {
   456  							"next": {
   457  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
   458  							}
   459  						},
   460  						"resources": [
   461  							{
   462  								"guid": "process-1-guid",
   463  								"type": "web",
   464  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   465  								"memory_in_mb": 32,
   466  								"health_check": {
   467                    "type": "port",
   468                    "data": {
   469                      "timeout": null,
   470                      "endpoint": null
   471                    }
   472                  }
   473  							},
   474  							{
   475  								"guid": "process-2-guid",
   476  								"type": "worker",
   477  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   478  								"memory_in_mb": 64,
   479  								"health_check": {
   480                    "type": "http",
   481                    "data": {
   482                      "timeout": 60,
   483                      "endpoint": "/health"
   484                    }
   485                  }
   486  							}
   487  						]
   488  					}`, server.URL())
   489  				response2 := `
   490  					{
   491  						"pagination": {
   492  							"next": null
   493  						},
   494  						"resources": [
   495  							{
   496  								"guid": "process-3-guid",
   497  								"type": "console",
   498  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   499  								"memory_in_mb": 128,
   500  								"health_check": {
   501                    "type": "process",
   502                    "data": {
   503                      "timeout": 90,
   504                      "endpoint": null
   505                    }
   506                  }
   507  							}
   508  						]
   509  					}`
   510  				server.AppendHandlers(
   511  					CombineHandlers(
   512  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   513  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   514  					),
   515  				)
   516  				server.AppendHandlers(
   517  					CombineHandlers(
   518  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
   519  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   520  					),
   521  				)
   522  			})
   523  
   524  			It("returns a list of processes associated with the application and all warnings", func() {
   525  				processes, warnings, err := client.GetApplicationProcesses("some-app-guid")
   526  				Expect(err).ToNot(HaveOccurred())
   527  
   528  				Expect(processes).To(ConsistOf(
   529  					Process{
   530  						GUID:            "process-1-guid",
   531  						Type:            constant.ProcessTypeWeb,
   532  						Command:         "[PRIVATE DATA HIDDEN IN LISTS]",
   533  						MemoryInMB:      types.NullUint64{Value: 32, IsSet: true},
   534  						HealthCheckType: "port",
   535  					},
   536  					Process{
   537  						GUID:                "process-2-guid",
   538  						Type:                "worker",
   539  						Command:             "[PRIVATE DATA HIDDEN IN LISTS]",
   540  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   541  						HealthCheckType:     "http",
   542  						HealthCheckEndpoint: "/health",
   543  					},
   544  					Process{
   545  						GUID:            "process-3-guid",
   546  						Type:            "console",
   547  						Command:         "[PRIVATE DATA HIDDEN IN LISTS]",
   548  						MemoryInMB:      types.NullUint64{Value: 128, IsSet: true},
   549  						HealthCheckType: "process",
   550  					},
   551  				))
   552  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   553  			})
   554  		})
   555  
   556  		When("cloud controller returns an error", func() {
   557  			BeforeEach(func() {
   558  				response := `{
   559  					"errors": [
   560  						{
   561  							"code": 10010,
   562  							"detail": "App not found",
   563  							"title": "CF-ResourceNotFound"
   564  						}
   565  					]
   566  				}`
   567  				server.AppendHandlers(
   568  					CombineHandlers(
   569  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   570  						RespondWith(http.StatusNotFound, response),
   571  					),
   572  				)
   573  			})
   574  
   575  			It("returns the error", func() {
   576  				_, _, err := client.GetApplicationProcesses("some-app-guid")
   577  				Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{}))
   578  			})
   579  		})
   580  	})
   581  
   582  	Describe("PatchApplicationProcessHealthCheck", func() {
   583  		var (
   584  			endpoint          string
   585  			invocationTimeout int
   586  
   587  			process  Process
   588  			warnings []string
   589  			err      error
   590  		)
   591  
   592  		JustBeforeEach(func() {
   593  			process, warnings, err = client.PatchApplicationProcessHealthCheck("some-process-guid", "some-type", endpoint, invocationTimeout)
   594  		})
   595  
   596  		When("patching the process succeeds", func() {
   597  			Context("and the endpoint is set", func() {
   598  				BeforeEach(func() {
   599  					endpoint = "some-endpoint"
   600  					invocationTimeout = 0
   601  
   602  					expectedBody := `{
   603  					"health_check": {
   604  						"type": "some-type",
   605  						"data": {
   606  							"endpoint": "some-endpoint"
   607  						}
   608  					}
   609  				}`
   610  					expectedResponse := `{
   611  					"health_check": {
   612  						"type": "some-type",
   613  						"data": {
   614  							"endpoint": "some-endpoint",
   615  							"invocation_timeout": null
   616  						}
   617  					}
   618  				}`
   619  					server.AppendHandlers(
   620  						CombineHandlers(
   621  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   622  							VerifyJSON(expectedBody),
   623  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   624  						),
   625  					)
   626  				})
   627  
   628  				It("patches this process's health check", func() {
   629  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   630  						"HealthCheckType":     Equal("some-type"),
   631  						"HealthCheckEndpoint": Equal("some-endpoint"),
   632  					}))
   633  					Expect(err).ToNot(HaveOccurred())
   634  					Expect(warnings).To(ConsistOf("this is a warning"))
   635  				})
   636  			})
   637  
   638  			Context("and invocation timeout is set", func() {
   639  				BeforeEach(func() {
   640  					endpoint = ""
   641  					invocationTimeout = 42
   642  
   643  					expectedBody := `{
   644  					"health_check": {
   645  						"type": "some-type",
   646  						"data": {
   647  							"endpoint": null,
   648  							"invocation_timeout": 42
   649  						}
   650  					}
   651  				}`
   652  					expectedResponse := `{
   653  					"health_check": {
   654  						"type": "some-type",
   655  						"data": {
   656  							"endpoint": null,
   657  							"invocation_timeout": 42
   658  						}
   659  					}
   660  				}`
   661  					server.AppendHandlers(
   662  						CombineHandlers(
   663  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   664  							VerifyJSON(expectedBody),
   665  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   666  						),
   667  					)
   668  				})
   669  
   670  				It("patches this process's health check", func() {
   671  					Expect(process).To(Equal(Process{
   672  						HealthCheckType:              "some-type",
   673  						HealthCheckEndpoint:          "",
   674  						HealthCheckInvocationTimeout: 42,
   675  					}))
   676  					Expect(err).ToNot(HaveOccurred())
   677  					Expect(warnings).To(ConsistOf("this is a warning"))
   678  				})
   679  			})
   680  
   681  			Context("and the endpoint and timeout are not set", func() {
   682  				BeforeEach(func() {
   683  					endpoint = ""
   684  					invocationTimeout = 0
   685  
   686  					expectedBody := `{
   687  					"health_check": {
   688  						"type": "some-type",
   689  						"data": {
   690  							"endpoint": null
   691  						}
   692  					}
   693  				}`
   694  					responseBody := `{
   695  					"health_check": {
   696  						"type": "some-type",
   697  						"data": {
   698  							"endpoint": null
   699  						}
   700  					}
   701  				}`
   702  					server.AppendHandlers(
   703  						CombineHandlers(
   704  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   705  							VerifyJSON(expectedBody),
   706  							RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   707  						),
   708  					)
   709  				})
   710  
   711  				It("patches this process's health check", func() {
   712  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   713  						"HealthCheckType":     Equal("some-type"),
   714  						"HealthCheckEndpoint": Equal(""),
   715  					}))
   716  					Expect(err).ToNot(HaveOccurred())
   717  					Expect(warnings).To(ConsistOf("this is a warning"))
   718  				})
   719  			})
   720  		})
   721  
   722  		When("the process does not exist", func() {
   723  			BeforeEach(func() {
   724  				endpoint = "some-endpoint"
   725  				response := `{
   726  					"errors": [
   727  						{
   728  							"detail": "Process not found",
   729  							"title": "CF-ResourceNotFound",
   730  							"code": 10010
   731  						}
   732  					]
   733  				}`
   734  
   735  				server.AppendHandlers(
   736  					CombineHandlers(
   737  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   738  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   739  					),
   740  				)
   741  			})
   742  
   743  			It("returns an error and warnings", func() {
   744  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
   745  				Expect(warnings).To(ConsistOf("this is a warning"))
   746  			})
   747  		})
   748  
   749  		When("the cloud controller returns errors and warnings", func() {
   750  			BeforeEach(func() {
   751  				endpoint = "some-endpoint"
   752  				response := `{
   753  						"errors": [
   754  							{
   755  								"code": 10008,
   756  								"detail": "The request is semantically invalid: command presence",
   757  								"title": "CF-UnprocessableEntity"
   758  							},
   759  							{
   760  								"code": 10009,
   761  								"detail": "Some CC Error",
   762  								"title": "CF-SomeNewError"
   763  							}
   764  						]
   765  					}`
   766  				server.AppendHandlers(
   767  					CombineHandlers(
   768  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   769  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   770  					),
   771  				)
   772  			})
   773  
   774  			It("returns the error and all warnings", func() {
   775  				Expect(err).To(MatchError(ccerror.MultiError{
   776  					ResponseCode: http.StatusTeapot,
   777  					Errors: []ccerror.V3Error{
   778  						{
   779  							Code:   10008,
   780  							Detail: "The request is semantically invalid: command presence",
   781  							Title:  "CF-UnprocessableEntity",
   782  						},
   783  						{
   784  							Code:   10009,
   785  							Detail: "Some CC Error",
   786  							Title:  "CF-SomeNewError",
   787  						},
   788  					},
   789  				}))
   790  				Expect(warnings).To(ConsistOf("this is a warning"))
   791  			})
   792  		})
   793  	})
   794  })