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  })