github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/api/cloudcontroller/ccv3/process_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
     9  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    11  	"code.cloudfoundry.org/cli/types"
    12  	. "github.com/onsi/ginkgo"
    13  	. "github.com/onsi/gomega"
    14  	. "github.com/onsi/gomega/ghttp"
    15  	. "github.com/onsi/gomega/gstruct"
    16  )
    17  
    18  var _ = Describe("Process", func() {
    19  	var client *Client
    20  
    21  	BeforeEach(func() {
    22  		client, _ = NewTestClient()
    23  	})
    24  
    25  	Describe("Process", func() {
    26  		Describe("MarshalJSON", func() {
    27  			var (
    28  				process      Process
    29  				processBytes []byte
    30  				err          error
    31  			)
    32  
    33  			BeforeEach(func() {
    34  				process = Process{}
    35  			})
    36  
    37  			JustBeforeEach(func() {
    38  				processBytes, err = process.MarshalJSON()
    39  				Expect(err).ToNot(HaveOccurred())
    40  			})
    41  
    42  			When("instances is provided", func() {
    43  				BeforeEach(func() {
    44  					process = Process{
    45  						Instances: types.NullInt{Value: 0, IsSet: true},
    46  					}
    47  				})
    48  
    49  				It("sets the instances to be set", func() {
    50  					Expect(string(processBytes)).To(MatchJSON(`{"instances": 0}`))
    51  				})
    52  			})
    53  
    54  			When("memory is provided", func() {
    55  				BeforeEach(func() {
    56  					process = Process{
    57  						MemoryInMB: types.NullUint64{Value: 0, IsSet: true},
    58  					}
    59  				})
    60  
    61  				It("sets the memory to be set", func() {
    62  					Expect(string(processBytes)).To(MatchJSON(`{"memory_in_mb": 0}`))
    63  				})
    64  			})
    65  
    66  			When("disk is provided", func() {
    67  				BeforeEach(func() {
    68  					process = Process{
    69  						DiskInMB: types.NullUint64{Value: 0, IsSet: true},
    70  					}
    71  				})
    72  
    73  				It("sets the disk to be set", func() {
    74  					Expect(string(processBytes)).To(MatchJSON(`{"disk_in_mb": 0}`))
    75  				})
    76  			})
    77  
    78  			When("health check type http is provided", func() {
    79  				BeforeEach(func() {
    80  					process = Process{
    81  						HealthCheckType:     constant.HTTP,
    82  						HealthCheckEndpoint: "some-endpoint",
    83  					}
    84  				})
    85  
    86  				It("sets the health check type to http and has an endpoint", func() {
    87  					Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`))
    88  				})
    89  			})
    90  
    91  			When("health check type port is provided", func() {
    92  				BeforeEach(func() {
    93  					process = Process{
    94  						HealthCheckType: constant.Port,
    95  					}
    96  				})
    97  
    98  				It("sets the health check type to port", func() {
    99  					Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`))
   100  				})
   101  			})
   102  
   103  			When("health check type process is provided", func() {
   104  				BeforeEach(func() {
   105  					process = Process{
   106  						HealthCheckType: constant.Process,
   107  					}
   108  				})
   109  
   110  				It("sets the health check type to process", func() {
   111  					Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"process", "data": {"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(constant.HTTP),
   148  						"HealthCheckEndpoint": Equal("some-endpoint"),
   149  					}))
   150  				})
   151  			})
   152  
   153  			When("health check type port is provided", func() {
   154  				BeforeEach(func() {
   155  					processBytes = []byte(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`)
   156  				})
   157  
   158  				It("sets the health check type to port", func() {
   159  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   160  						"HealthCheckType": Equal(constant.Port),
   161  					}))
   162  				})
   163  			})
   164  
   165  			When("health check type process is provided", func() {
   166  				BeforeEach(func() {
   167  					processBytes = []byte(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`)
   168  				})
   169  
   170  				It("sets the health check type to process", func() {
   171  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   172  						"HealthCheckType": Equal(constant.Process),
   173  					}))
   174  				})
   175  			})
   176  		})
   177  	})
   178  
   179  	Describe("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(types.FilteredString{IsSet: true, Value: "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(constant.HTTP),
   374  					"HealthCheckEndpoint":          Equal("/health"),
   375  					"HealthCheckInvocationTimeout": BeEquivalentTo(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:         types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   533  						MemoryInMB:      types.NullUint64{Value: 32, IsSet: true},
   534  						HealthCheckType: constant.Port,
   535  					},
   536  					Process{
   537  						GUID:                "process-2-guid",
   538  						Type:                "worker",
   539  						Command:             types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   540  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   541  						HealthCheckType:     constant.HTTP,
   542  						HealthCheckEndpoint: "/health",
   543  					},
   544  					Process{
   545  						GUID:            "process-3-guid",
   546  						Type:            "console",
   547  						Command:         types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   548  						MemoryInMB:      types.NullUint64{Value: 128, IsSet: true},
   549  						HealthCheckType: constant.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("UpdateProcess", func() {
   583  		var (
   584  			inputProcess Process
   585  
   586  			process  Process
   587  			warnings []string
   588  			err      error
   589  		)
   590  
   591  		BeforeEach(func() {
   592  			inputProcess = Process{
   593  				GUID: "some-process-guid",
   594  			}
   595  		})
   596  
   597  		JustBeforeEach(func() {
   598  			process, warnings, err = client.UpdateProcess(inputProcess)
   599  		})
   600  
   601  		When("patching the process succeeds", func() {
   602  			When("the command is set", func() {
   603  				When("the start command is an arbitrary command", func() {
   604  					BeforeEach(func() {
   605  						inputProcess.Command = types.FilteredString{IsSet: true, Value: "some-command"}
   606  
   607  						expectedBody := `{
   608  							"command": "some-command"
   609  						}`
   610  
   611  						expectedResponse := `{
   612  							"command": "some-command"
   613  						}`
   614  
   615  						server.AppendHandlers(
   616  							CombineHandlers(
   617  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   618  								VerifyJSON(expectedBody),
   619  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   620  							),
   621  						)
   622  					})
   623  
   624  					It("patches this process's command with the provided command", func() {
   625  						Expect(err).ToNot(HaveOccurred())
   626  						Expect(warnings).To(ConsistOf("this is a warning"))
   627  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   628  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-command"}),
   629  						}))
   630  					})
   631  				})
   632  
   633  				When("the start command reset", func() {
   634  					BeforeEach(func() {
   635  						inputProcess.Command = types.FilteredString{IsSet: true}
   636  
   637  						expectedBody := `{
   638  							"command": null
   639  						}`
   640  
   641  						expectedResponse := `{
   642  							"command": "some-default-command"
   643  						}`
   644  
   645  						server.AppendHandlers(
   646  							CombineHandlers(
   647  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   648  								VerifyJSON(expectedBody),
   649  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   650  							),
   651  						)
   652  					})
   653  
   654  					It("patches this process's command with 'null' and returns the default command", func() {
   655  						Expect(err).ToNot(HaveOccurred())
   656  						Expect(warnings).To(ConsistOf("this is a warning"))
   657  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   658  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-default-command"}),
   659  						}))
   660  					})
   661  				})
   662  			})
   663  
   664  			When("the endpoint is set", func() {
   665  				BeforeEach(func() {
   666  					inputProcess.HealthCheckEndpoint = "some-endpoint"
   667  					inputProcess.HealthCheckType = "some-type"
   668  
   669  					expectedBody := `{
   670  					"health_check": {
   671  						"type": "some-type",
   672  						"data": {
   673  							"endpoint": "some-endpoint"
   674  						}
   675  					}
   676  				}`
   677  					expectedResponse := `{
   678  					"health_check": {
   679  						"type": "some-type",
   680  						"data": {
   681  							"endpoint": "some-endpoint",
   682  							"invocation_timeout": null
   683  						}
   684  					}
   685  				}`
   686  					server.AppendHandlers(
   687  						CombineHandlers(
   688  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   689  							VerifyJSON(expectedBody),
   690  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   691  						),
   692  					)
   693  				})
   694  
   695  				It("patches this process's health check", func() {
   696  					Expect(err).ToNot(HaveOccurred())
   697  					Expect(warnings).To(ConsistOf("this is a warning"))
   698  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   699  						"HealthCheckType":     Equal(constant.HealthCheckType("some-type")),
   700  						"HealthCheckEndpoint": Equal("some-endpoint"),
   701  					}))
   702  				})
   703  			})
   704  
   705  			When("the invocation timeout is set", func() {
   706  				BeforeEach(func() {
   707  					inputProcess.HealthCheckInvocationTimeout = 42
   708  					inputProcess.HealthCheckType = "some-type"
   709  
   710  					expectedBody := `{
   711  					"health_check": {
   712  						"type": "some-type",
   713  						"data": {
   714  							"endpoint": null,
   715  							"invocation_timeout": 42
   716  						}
   717  					}
   718  				}`
   719  					expectedResponse := `{
   720  					"health_check": {
   721  						"type": "some-type",
   722  						"data": {
   723  							"endpoint": null,
   724  							"invocation_timeout": 42
   725  						}
   726  					}
   727  				}`
   728  					server.AppendHandlers(
   729  						CombineHandlers(
   730  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   731  							VerifyJSON(expectedBody),
   732  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   733  						),
   734  					)
   735  				})
   736  
   737  				It("patches this process's health check", func() {
   738  					Expect(err).ToNot(HaveOccurred())
   739  					Expect(warnings).To(ConsistOf("this is a warning"))
   740  					Expect(process).To(Equal(Process{
   741  						HealthCheckType:              "some-type",
   742  						HealthCheckEndpoint:          "",
   743  						HealthCheckInvocationTimeout: 42,
   744  					}))
   745  				})
   746  			})
   747  
   748  			When("the endpoint and timeout are not set", func() {
   749  				BeforeEach(func() {
   750  					inputProcess.HealthCheckType = "some-type"
   751  
   752  					expectedBody := `{
   753  					"health_check": {
   754  						"type": "some-type",
   755  						"data": {
   756  							"endpoint": null
   757  						}
   758  					}
   759  				}`
   760  					responseBody := `{
   761  					"health_check": {
   762  						"type": "some-type",
   763  						"data": {
   764  							"endpoint": null
   765  						}
   766  					}
   767  				}`
   768  					server.AppendHandlers(
   769  						CombineHandlers(
   770  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   771  							VerifyJSON(expectedBody),
   772  							RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   773  						),
   774  					)
   775  				})
   776  
   777  				It("patches this process's health check", func() {
   778  					Expect(err).ToNot(HaveOccurred())
   779  					Expect(warnings).To(ConsistOf("this is a warning"))
   780  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   781  						"HealthCheckType":     Equal(constant.HealthCheckType("some-type")),
   782  						"HealthCheckEndpoint": BeEmpty(),
   783  					}))
   784  				})
   785  			})
   786  		})
   787  
   788  		When("the process does not exist", func() {
   789  			BeforeEach(func() {
   790  				response := `{
   791  					"errors": [
   792  						{
   793  							"detail": "Process not found",
   794  							"title": "CF-ResourceNotFound",
   795  							"code": 10010
   796  						}
   797  					]
   798  				}`
   799  
   800  				server.AppendHandlers(
   801  					CombineHandlers(
   802  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   803  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   804  					),
   805  				)
   806  			})
   807  
   808  			It("returns an error and warnings", func() {
   809  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
   810  				Expect(warnings).To(ConsistOf("this is a warning"))
   811  			})
   812  		})
   813  
   814  		When("the cloud controller returns errors and warnings", func() {
   815  			BeforeEach(func() {
   816  				response := `{
   817  						"errors": [
   818  							{
   819  								"code": 10008,
   820  								"detail": "The request is semantically invalid: command presence",
   821  								"title": "CF-UnprocessableEntity"
   822  							},
   823  							{
   824  								"code": 10009,
   825  								"detail": "Some CC Error",
   826  								"title": "CF-SomeNewError"
   827  							}
   828  						]
   829  					}`
   830  				server.AppendHandlers(
   831  					CombineHandlers(
   832  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   833  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   834  					),
   835  				)
   836  			})
   837  
   838  			It("returns the error and all warnings", func() {
   839  				Expect(err).To(MatchError(ccerror.MultiError{
   840  					ResponseCode: http.StatusTeapot,
   841  					Errors: []ccerror.V3Error{
   842  						{
   843  							Code:   10008,
   844  							Detail: "The request is semantically invalid: command presence",
   845  							Title:  "CF-UnprocessableEntity",
   846  						},
   847  						{
   848  							Code:   10009,
   849  							Detail: "Some CC Error",
   850  							Title:  "CF-SomeNewError",
   851  						},
   852  					},
   853  				}))
   854  				Expect(warnings).To(ConsistOf("this is a warning"))
   855  			})
   856  		})
   857  	})
   858  })