github.com/arunkumar7540/cli@v6.45.0+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": {}}}`))
   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": {}}}`))
   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  					"HealthCheckTimeout":           BeEquivalentTo(90),
   377  				}))
   378  			})
   379  		})
   380  
   381  		When("the application does not exist", func() {
   382  			BeforeEach(func() {
   383  				response := `{
   384  					"errors": [
   385  						{
   386  							"detail": "Application not found",
   387  							"title": "CF-ResourceNotFound",
   388  							"code": 10010
   389  						}
   390  					]
   391  				}`
   392  				server.AppendHandlers(
   393  					CombineHandlers(
   394  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   395  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   396  					),
   397  				)
   398  			})
   399  
   400  			It("returns a ResourceNotFoundError", func() {
   401  				Expect(warnings).To(ConsistOf("this is a warning"))
   402  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"}))
   403  			})
   404  		})
   405  
   406  		When("the cloud controller returns errors and warnings", func() {
   407  			BeforeEach(func() {
   408  				response := `{
   409  					"errors": [
   410  						{
   411  							"code": 10008,
   412  							"detail": "The request is semantically invalid: command presence",
   413  							"title": "CF-UnprocessableEntity"
   414  						},
   415  						{
   416  							"code": 10009,
   417  							"detail": "Some CC Error",
   418  							"title": "CF-SomeNewError"
   419  						}
   420  					]
   421  				}`
   422  				server.AppendHandlers(
   423  					CombineHandlers(
   424  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   425  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   426  					),
   427  				)
   428  			})
   429  
   430  			It("returns the error and all warnings", func() {
   431  				Expect(err).To(MatchError(ccerror.MultiError{
   432  					ResponseCode: http.StatusTeapot,
   433  					Errors: []ccerror.V3Error{
   434  						{
   435  							Code:   10008,
   436  							Detail: "The request is semantically invalid: command presence",
   437  							Title:  "CF-UnprocessableEntity",
   438  						},
   439  						{
   440  							Code:   10009,
   441  							Detail: "Some CC Error",
   442  							Title:  "CF-SomeNewError",
   443  						},
   444  					},
   445  				}))
   446  				Expect(warnings).To(ConsistOf("this is a warning"))
   447  			})
   448  		})
   449  	})
   450  
   451  	Describe("GetApplicationProcesses", func() {
   452  		When("the application exists", func() {
   453  			BeforeEach(func() {
   454  				response1 := fmt.Sprintf(`
   455  					{
   456  						"pagination": {
   457  							"next": {
   458  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
   459  							}
   460  						},
   461  						"resources": [
   462  							{
   463  								"guid": "process-1-guid",
   464  								"type": "web",
   465  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   466  								"memory_in_mb": 32,
   467  								"health_check": {
   468                    "type": "port",
   469                    "data": {
   470                      "timeout": null,
   471                      "endpoint": null
   472                    }
   473                  }
   474  							},
   475  							{
   476  								"guid": "process-2-guid",
   477  								"type": "worker",
   478  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   479  								"memory_in_mb": 64,
   480  								"health_check": {
   481                    "type": "http",
   482                    "data": {
   483                      "timeout": 60,
   484                      "endpoint": "/health"
   485                    }
   486                  }
   487  							}
   488  						]
   489  					}`, server.URL())
   490  				response2 := `
   491  					{
   492  						"pagination": {
   493  							"next": null
   494  						},
   495  						"resources": [
   496  							{
   497  								"guid": "process-3-guid",
   498  								"type": "console",
   499  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   500  								"memory_in_mb": 128,
   501  								"health_check": {
   502                    "type": "process",
   503                    "data": {
   504                      "timeout": 90,
   505                      "endpoint": null
   506                    }
   507                  }
   508  							}
   509  						]
   510  					}`
   511  				server.AppendHandlers(
   512  					CombineHandlers(
   513  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   514  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   515  					),
   516  				)
   517  				server.AppendHandlers(
   518  					CombineHandlers(
   519  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
   520  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   521  					),
   522  				)
   523  			})
   524  
   525  			It("returns a list of processes associated with the application and all warnings", func() {
   526  				processes, warnings, err := client.GetApplicationProcesses("some-app-guid")
   527  				Expect(err).ToNot(HaveOccurred())
   528  
   529  				Expect(processes).To(ConsistOf(
   530  					Process{
   531  						GUID:               "process-1-guid",
   532  						Type:               constant.ProcessTypeWeb,
   533  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   534  						MemoryInMB:         types.NullUint64{Value: 32, IsSet: true},
   535  						HealthCheckType:    constant.Port,
   536  						HealthCheckTimeout: 0,
   537  					},
   538  					Process{
   539  						GUID:                "process-2-guid",
   540  						Type:                "worker",
   541  						Command:             types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   542  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   543  						HealthCheckType:     constant.HTTP,
   544  						HealthCheckEndpoint: "/health",
   545  						HealthCheckTimeout:  60,
   546  					},
   547  					Process{
   548  						GUID:               "process-3-guid",
   549  						Type:               "console",
   550  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   551  						MemoryInMB:         types.NullUint64{Value: 128, IsSet: true},
   552  						HealthCheckType:    constant.Process,
   553  						HealthCheckTimeout: 90,
   554  					},
   555  				))
   556  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   557  			})
   558  		})
   559  
   560  		When("cloud controller returns an error", func() {
   561  			BeforeEach(func() {
   562  				response := `{
   563  					"errors": [
   564  						{
   565  							"code": 10010,
   566  							"detail": "App not found",
   567  							"title": "CF-ResourceNotFound"
   568  						}
   569  					]
   570  				}`
   571  				server.AppendHandlers(
   572  					CombineHandlers(
   573  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   574  						RespondWith(http.StatusNotFound, response),
   575  					),
   576  				)
   577  			})
   578  
   579  			It("returns the error", func() {
   580  				_, _, err := client.GetApplicationProcesses("some-app-guid")
   581  				Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{}))
   582  			})
   583  		})
   584  	})
   585  
   586  	Describe("UpdateProcess", func() {
   587  		var (
   588  			inputProcess Process
   589  
   590  			process  Process
   591  			warnings []string
   592  			err      error
   593  		)
   594  
   595  		BeforeEach(func() {
   596  			inputProcess = Process{
   597  				GUID: "some-process-guid",
   598  			}
   599  		})
   600  
   601  		JustBeforeEach(func() {
   602  			process, warnings, err = client.UpdateProcess(inputProcess)
   603  		})
   604  
   605  		When("patching the process succeeds", func() {
   606  			When("the command is set", func() {
   607  				When("the start command is an arbitrary command", func() {
   608  					BeforeEach(func() {
   609  						inputProcess.Command = types.FilteredString{IsSet: true, Value: "some-command"}
   610  
   611  						expectedBody := `{
   612  							"command": "some-command"
   613  						}`
   614  
   615  						expectedResponse := `{
   616  							"command": "some-command"
   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 command with the provided command", func() {
   629  						Expect(err).ToNot(HaveOccurred())
   630  						Expect(warnings).To(ConsistOf("this is a warning"))
   631  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   632  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-command"}),
   633  						}))
   634  					})
   635  				})
   636  
   637  				When("the start command reset", func() {
   638  					BeforeEach(func() {
   639  						inputProcess.Command = types.FilteredString{IsSet: true}
   640  
   641  						expectedBody := `{
   642  							"command": null
   643  						}`
   644  
   645  						expectedResponse := `{
   646  							"command": "some-default-command"
   647  						}`
   648  
   649  						server.AppendHandlers(
   650  							CombineHandlers(
   651  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   652  								VerifyJSON(expectedBody),
   653  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   654  							),
   655  						)
   656  					})
   657  
   658  					It("patches this process's command with 'null' and returns the default command", func() {
   659  						Expect(err).ToNot(HaveOccurred())
   660  						Expect(warnings).To(ConsistOf("this is a warning"))
   661  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   662  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-default-command"}),
   663  						}))
   664  					})
   665  				})
   666  			})
   667  
   668  			When("the endpoint is set", func() {
   669  				BeforeEach(func() {
   670  					inputProcess.HealthCheckEndpoint = "some-endpoint"
   671  					inputProcess.HealthCheckType = "some-type"
   672  
   673  					expectedBody := `{
   674  					"health_check": {
   675  						"type": "some-type",
   676  						"data": {
   677  							"endpoint": "some-endpoint"
   678  						}
   679  					}
   680  				}`
   681  					expectedResponse := `{
   682  					"health_check": {
   683  						"type": "some-type",
   684  						"data": {
   685  							"endpoint": "some-endpoint",
   686  							"invocation_timeout": null
   687  						}
   688  					}
   689  				}`
   690  					server.AppendHandlers(
   691  						CombineHandlers(
   692  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   693  							VerifyJSON(expectedBody),
   694  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   695  						),
   696  					)
   697  				})
   698  
   699  				It("patches this process's health check", func() {
   700  					Expect(err).ToNot(HaveOccurred())
   701  					Expect(warnings).To(ConsistOf("this is a warning"))
   702  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   703  						"HealthCheckType":     Equal(constant.HealthCheckType("some-type")),
   704  						"HealthCheckEndpoint": Equal("some-endpoint"),
   705  					}))
   706  				})
   707  			})
   708  
   709  			When("the invocation timeout is set", func() {
   710  				BeforeEach(func() {
   711  					inputProcess.HealthCheckInvocationTimeout = 42
   712  					inputProcess.HealthCheckType = "some-type"
   713  
   714  					expectedBody := `{
   715  					"health_check": {
   716  						"type": "some-type",
   717  						"data": {
   718  							"invocation_timeout": 42
   719  						}
   720  					}
   721  				}`
   722  					expectedResponse := `{
   723  					"health_check": {
   724  						"type": "some-type",
   725  						"data": {
   726  							"invocation_timeout": 42
   727  						}
   728  					}
   729  				}`
   730  					server.AppendHandlers(
   731  						CombineHandlers(
   732  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   733  							VerifyJSON(expectedBody),
   734  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   735  						),
   736  					)
   737  				})
   738  
   739  				It("patches this process's health check", func() {
   740  					Expect(err).ToNot(HaveOccurred())
   741  					Expect(warnings).To(ConsistOf("this is a warning"))
   742  					Expect(process).To(Equal(Process{
   743  						HealthCheckType:              "some-type",
   744  						HealthCheckInvocationTimeout: 42,
   745  					}))
   746  				})
   747  			})
   748  
   749  			When("the health check timeout is set", func() {
   750  				BeforeEach(func() {
   751  					inputProcess.HealthCheckTimeout = 77
   752  					inputProcess.HealthCheckType = "some-type"
   753  
   754  					expectedBody := `{
   755  					"health_check": {
   756  						"type": "some-type",
   757  						"data": {
   758  							"timeout": 77
   759  						}
   760  					}
   761  				}`
   762  					expectedResponse := `{
   763  					"health_check": {
   764  						"type": "some-type",
   765  						"data": {
   766  							"timeout": 77
   767  						}
   768  					}
   769  				}`
   770  					server.AppendHandlers(
   771  						CombineHandlers(
   772  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   773  							VerifyJSON(expectedBody),
   774  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   775  						),
   776  					)
   777  				})
   778  
   779  				It("patches this process's health check", func() {
   780  					Expect(err).ToNot(HaveOccurred())
   781  					Expect(warnings).To(ConsistOf("this is a warning"))
   782  					Expect(process).To(Equal(Process{
   783  						HealthCheckType:     "some-type",
   784  						HealthCheckEndpoint: "",
   785  						HealthCheckTimeout:  77,
   786  					}))
   787  				})
   788  			})
   789  
   790  			When("the endpoint and timeout are not set", func() {
   791  				BeforeEach(func() {
   792  					inputProcess.HealthCheckType = "some-type"
   793  
   794  					expectedBody := `{
   795  					"health_check": {
   796  						"type": "some-type",
   797  						"data": {}
   798  					}
   799  				}`
   800  					responseBody := `{
   801  					"health_check": {
   802  						"type": "some-type"
   803  					}
   804  				}`
   805  					server.AppendHandlers(
   806  						CombineHandlers(
   807  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   808  							VerifyJSON(expectedBody),
   809  							RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   810  						),
   811  					)
   812  				})
   813  
   814  				It("patches this process's health check", func() {
   815  					Expect(err).ToNot(HaveOccurred())
   816  					Expect(warnings).To(ConsistOf("this is a warning"))
   817  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   818  						"HealthCheckType": Equal(constant.HealthCheckType("some-type")),
   819  					}))
   820  				})
   821  			})
   822  		})
   823  
   824  		When("the process does not exist", func() {
   825  			BeforeEach(func() {
   826  				response := `{
   827  					"errors": [
   828  						{
   829  							"detail": "Process not found",
   830  							"title": "CF-ResourceNotFound",
   831  							"code": 10010
   832  						}
   833  					]
   834  				}`
   835  
   836  				server.AppendHandlers(
   837  					CombineHandlers(
   838  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   839  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   840  					),
   841  				)
   842  			})
   843  
   844  			It("returns an error and warnings", func() {
   845  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
   846  				Expect(warnings).To(ConsistOf("this is a warning"))
   847  			})
   848  		})
   849  
   850  		When("the cloud controller returns errors and warnings", func() {
   851  			BeforeEach(func() {
   852  				response := `{
   853  						"errors": [
   854  							{
   855  								"code": 10008,
   856  								"detail": "The request is semantically invalid: command presence",
   857  								"title": "CF-UnprocessableEntity"
   858  							},
   859  							{
   860  								"code": 10009,
   861  								"detail": "Some CC Error",
   862  								"title": "CF-SomeNewError"
   863  							}
   864  						]
   865  					}`
   866  				server.AppendHandlers(
   867  					CombineHandlers(
   868  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   869  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   870  					),
   871  				)
   872  			})
   873  
   874  			It("returns the error and all warnings", func() {
   875  				Expect(err).To(MatchError(ccerror.MultiError{
   876  					ResponseCode: http.StatusTeapot,
   877  					Errors: []ccerror.V3Error{
   878  						{
   879  							Code:   10008,
   880  							Detail: "The request is semantically invalid: command presence",
   881  							Title:  "CF-UnprocessableEntity",
   882  						},
   883  						{
   884  							Code:   10009,
   885  							Detail: "Some CC Error",
   886  							Title:  "CF-SomeNewError",
   887  						},
   888  					},
   889  				}))
   890  				Expect(warnings).To(ConsistOf("this is a warning"))
   891  			})
   892  		})
   893  	})
   894  })