github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/api/cloudcontroller/ccv3/process_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"code.cloudfoundry.org/cli/resources"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    10  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    12  	"code.cloudfoundry.org/cli/types"
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  	. "github.com/onsi/gomega/ghttp"
    16  	. "github.com/onsi/gomega/gstruct"
    17  )
    18  
    19  var _ = Describe("Process", func() {
    20  	var client *Client
    21  
    22  	BeforeEach(func() {
    23  		client, _ = NewTestClient()
    24  	})
    25  
    26  	Describe("Process", func() {
    27  		Describe("MarshalJSON", func() {
    28  			var (
    29  				process      resources.Process
    30  				processBytes []byte
    31  				err          error
    32  			)
    33  
    34  			BeforeEach(func() {
    35  				process = resources.Process{}
    36  			})
    37  
    38  			JustBeforeEach(func() {
    39  				processBytes, err = process.MarshalJSON()
    40  				Expect(err).ToNot(HaveOccurred())
    41  			})
    42  
    43  			When("instances is provided", func() {
    44  				BeforeEach(func() {
    45  					process = resources.Process{
    46  						Instances: types.NullInt{Value: 0, IsSet: true},
    47  					}
    48  				})
    49  
    50  				It("sets the instances to be set", func() {
    51  					Expect(string(processBytes)).To(MatchJSON(`{"instances": 0}`))
    52  				})
    53  			})
    54  
    55  			When("memory is provided", func() {
    56  				BeforeEach(func() {
    57  					process = resources.Process{
    58  						MemoryInMB: types.NullUint64{Value: 0, IsSet: true},
    59  					}
    60  				})
    61  
    62  				It("sets the memory to be set", func() {
    63  					Expect(string(processBytes)).To(MatchJSON(`{"memory_in_mb": 0}`))
    64  				})
    65  			})
    66  
    67  			When("disk is provided", func() {
    68  				BeforeEach(func() {
    69  					process = resources.Process{
    70  						DiskInMB: types.NullUint64{Value: 0, IsSet: true},
    71  					}
    72  				})
    73  
    74  				It("sets the disk to be set", func() {
    75  					Expect(string(processBytes)).To(MatchJSON(`{"disk_in_mb": 0}`))
    76  				})
    77  			})
    78  
    79  			When("health check type http is provided", func() {
    80  				BeforeEach(func() {
    81  					process = resources.Process{
    82  						HealthCheckType:     constant.HTTP,
    83  						HealthCheckEndpoint: "some-endpoint",
    84  					}
    85  				})
    86  
    87  				It("sets the health check type to http and has an endpoint", func() {
    88  					Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`))
    89  				})
    90  			})
    91  
    92  			When("health check type port is provided", func() {
    93  				BeforeEach(func() {
    94  					process = resources.Process{
    95  						HealthCheckType: constant.Port,
    96  					}
    97  				})
    98  
    99  				It("sets the health check type to port", func() {
   100  					Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"port", "data": {}}}`))
   101  				})
   102  			})
   103  
   104  			When("health check type process is provided", func() {
   105  				BeforeEach(func() {
   106  					process = resources.Process{
   107  						HealthCheckType: constant.Process,
   108  					}
   109  				})
   110  
   111  				It("sets the health check type to process", func() {
   112  					Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"process", "data": {}}}`))
   113  				})
   114  			})
   115  
   116  			When("process has no fields provided", func() {
   117  				BeforeEach(func() {
   118  					process = resources.Process{}
   119  				})
   120  
   121  				It("sets the health check type to process", func() {
   122  					Expect(string(processBytes)).To(MatchJSON(`{}`))
   123  				})
   124  			})
   125  		})
   126  
   127  		Describe("UnmarshalJSON", func() {
   128  			var (
   129  				process      resources.Process
   130  				processBytes []byte
   131  				err          error
   132  			)
   133  			BeforeEach(func() {
   134  				processBytes = []byte("{}")
   135  			})
   136  
   137  			JustBeforeEach(func() {
   138  				err = json.Unmarshal(processBytes, &process)
   139  				Expect(err).ToNot(HaveOccurred())
   140  			})
   141  			When("health check type http is provided", func() {
   142  				BeforeEach(func() {
   143  					processBytes = []byte(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`)
   144  				})
   145  
   146  				It("sets the health check type to http and has an endpoint", func() {
   147  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   148  						"HealthCheckType":     Equal(constant.HTTP),
   149  						"HealthCheckEndpoint": Equal("some-endpoint"),
   150  					}))
   151  				})
   152  			})
   153  
   154  			When("health check type port is provided", func() {
   155  				BeforeEach(func() {
   156  					processBytes = []byte(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`)
   157  				})
   158  
   159  				It("sets the health check type to port", func() {
   160  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   161  						"HealthCheckType": Equal(constant.Port),
   162  					}))
   163  				})
   164  			})
   165  
   166  			When("health check type process is provided", func() {
   167  				BeforeEach(func() {
   168  					processBytes = []byte(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`)
   169  				})
   170  
   171  				It("sets the health check type to process", func() {
   172  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   173  						"HealthCheckType": Equal(constant.Process),
   174  					}))
   175  				})
   176  			})
   177  		})
   178  	})
   179  
   180  	Describe("CreateApplicationProcessScale", func() {
   181  		var passedProcess resources.Process
   182  
   183  		When("providing all scale options", func() {
   184  			BeforeEach(func() {
   185  				passedProcess = resources.Process{
   186  					Type:       constant.ProcessTypeWeb,
   187  					Instances:  types.NullInt{Value: 2, IsSet: true},
   188  					MemoryInMB: types.NullUint64{Value: 100, IsSet: true},
   189  					DiskInMB:   types.NullUint64{Value: 200, IsSet: true},
   190  				}
   191  				expectedBody := `{
   192  					"instances": 2,
   193  					"memory_in_mb": 100,
   194  					"disk_in_mb": 200
   195  				}`
   196  				response := `{
   197  					"guid": "some-process-guid"
   198  				}`
   199  				server.AppendHandlers(
   200  					CombineHandlers(
   201  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   202  						VerifyJSON(expectedBody),
   203  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   204  					),
   205  				)
   206  			})
   207  
   208  			It("scales the application process; returns the scaled process and all warnings", func() {
   209  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   210  				Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")}))
   211  				Expect(err).ToNot(HaveOccurred())
   212  				Expect(warnings).To(ConsistOf("this is a warning"))
   213  			})
   214  		})
   215  
   216  		When("providing all scale options with 0 values", func() {
   217  			BeforeEach(func() {
   218  				passedProcess = resources.Process{
   219  					Type:       constant.ProcessTypeWeb,
   220  					Instances:  types.NullInt{Value: 0, IsSet: true},
   221  					MemoryInMB: types.NullUint64{Value: 0, IsSet: true},
   222  					DiskInMB:   types.NullUint64{Value: 0, IsSet: true},
   223  				}
   224  				expectedBody := `{
   225  					"instances": 0,
   226  					"memory_in_mb": 0,
   227  					"disk_in_mb": 0
   228  				}`
   229  				response := `{
   230  					"guid": "some-process-guid"
   231  				}`
   232  				server.AppendHandlers(
   233  					CombineHandlers(
   234  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   235  						VerifyJSON(expectedBody),
   236  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   237  					),
   238  				)
   239  			})
   240  
   241  			It("scales the application process to 0 values; returns the scaled process and all warnings", func() {
   242  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   243  				Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")}))
   244  				Expect(err).ToNot(HaveOccurred())
   245  				Expect(warnings).To(ConsistOf("this is a warning"))
   246  			})
   247  		})
   248  
   249  		When("providing only one scale option", func() {
   250  			BeforeEach(func() {
   251  				passedProcess = resources.Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   252  				expectedBody := `{
   253  					"instances": 2
   254  				}`
   255  				response := `{
   256  					"guid": "some-process-guid",
   257  					"instances": 2
   258  				}`
   259  				server.AppendHandlers(
   260  					CombineHandlers(
   261  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   262  						VerifyJSON(expectedBody),
   263  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   264  					),
   265  				)
   266  			})
   267  
   268  			It("scales the application process; returns the process object and all warnings", func() {
   269  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   270  				Expect(process).To(MatchFields(IgnoreExtras, Fields{
   271  					"GUID":      Equal("some-process-guid"),
   272  					"Instances": Equal(types.NullInt{Value: 2, IsSet: true}),
   273  				}))
   274  				Expect(err).ToNot(HaveOccurred())
   275  				Expect(warnings).To(ConsistOf("this is a warning"))
   276  			})
   277  		})
   278  
   279  		When("an error is encountered", func() {
   280  			BeforeEach(func() {
   281  				passedProcess = resources.Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   282  				response := `{
   283  						"errors": [
   284  							{
   285  								"code": 10008,
   286  								"detail": "The request is semantically invalid: command presence",
   287  								"title": "CF-UnprocessableEntity"
   288  							},
   289  							{
   290  								"code": 10009,
   291  								"detail": "Some CC Error",
   292  								"title": "CF-SomeNewError"
   293  							}
   294  						]
   295  					}`
   296  				server.AppendHandlers(
   297  					CombineHandlers(
   298  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   299  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   300  					),
   301  				)
   302  			})
   303  
   304  			It("returns an empty process, the error and all warnings", func() {
   305  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   306  				Expect(process).To(BeZero())
   307  				Expect(err).To(MatchError(ccerror.MultiError{
   308  					ResponseCode: http.StatusTeapot,
   309  					Errors: []ccerror.V3Error{
   310  						{
   311  							Code:   10008,
   312  							Detail: "The request is semantically invalid: command presence",
   313  							Title:  "CF-UnprocessableEntity",
   314  						},
   315  						{
   316  							Code:   10009,
   317  							Detail: "Some CC Error",
   318  							Title:  "CF-SomeNewError",
   319  						},
   320  					},
   321  				}))
   322  				Expect(warnings).To(ConsistOf("this is a warning"))
   323  			})
   324  		})
   325  	})
   326  
   327  	Describe("GetApplicationProcessByType", func() {
   328  		var (
   329  			process  resources.Process
   330  			warnings []string
   331  			err      error
   332  		)
   333  
   334  		JustBeforeEach(func() {
   335  			process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type")
   336  		})
   337  
   338  		When("the process exists", func() {
   339  			BeforeEach(func() {
   340  				response := `{
   341  					"guid": "process-1-guid",
   342  					"type": "some-type",
   343  					"command": "start-command-1",
   344  					"instances": 22,
   345  					"memory_in_mb": 32,
   346  					"disk_in_mb": 1024,
   347  					"health_check": {
   348  						"type": "http",
   349  						"data": {
   350  							"timeout": 90,
   351  							"endpoint": "/health",
   352  							"invocation_timeout": 42
   353  						}
   354  					}
   355  				}`
   356  				server.AppendHandlers(
   357  					CombineHandlers(
   358  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   359  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   360  					),
   361  				)
   362  			})
   363  
   364  			It("returns the process and all warnings", func() {
   365  				Expect(err).NotTo(HaveOccurred())
   366  				Expect(warnings).To(ConsistOf("this is a warning"))
   367  				Expect(process).To(MatchAllFields(Fields{
   368  					"GUID":                         Equal("process-1-guid"),
   369  					"Type":                         Equal("some-type"),
   370  					"Command":                      Equal(types.FilteredString{IsSet: true, Value: "start-command-1"}),
   371  					"Instances":                    Equal(types.NullInt{Value: 22, IsSet: true}),
   372  					"MemoryInMB":                   Equal(types.NullUint64{Value: 32, IsSet: true}),
   373  					"DiskInMB":                     Equal(types.NullUint64{Value: 1024, IsSet: true}),
   374  					"HealthCheckType":              Equal(constant.HTTP),
   375  					"HealthCheckEndpoint":          Equal("/health"),
   376  					"HealthCheckInvocationTimeout": BeEquivalentTo(42),
   377  					"HealthCheckTimeout":           BeEquivalentTo(90),
   378  				}))
   379  			})
   380  		})
   381  
   382  		When("the application does not exist", func() {
   383  			BeforeEach(func() {
   384  				response := `{
   385  					"errors": [
   386  						{
   387  							"detail": "Application not found",
   388  							"title": "CF-ResourceNotFound",
   389  							"code": 10010
   390  						}
   391  					]
   392  				}`
   393  				server.AppendHandlers(
   394  					CombineHandlers(
   395  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   396  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   397  					),
   398  				)
   399  			})
   400  
   401  			It("returns a ResourceNotFoundError", func() {
   402  				Expect(warnings).To(ConsistOf("this is a warning"))
   403  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"}))
   404  			})
   405  		})
   406  
   407  		When("the cloud controller returns errors and warnings", func() {
   408  			BeforeEach(func() {
   409  				response := `{
   410  					"errors": [
   411  						{
   412  							"code": 10008,
   413  							"detail": "The request is semantically invalid: command presence",
   414  							"title": "CF-UnprocessableEntity"
   415  						},
   416  						{
   417  							"code": 10009,
   418  							"detail": "Some CC Error",
   419  							"title": "CF-SomeNewError"
   420  						}
   421  					]
   422  				}`
   423  				server.AppendHandlers(
   424  					CombineHandlers(
   425  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   426  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   427  					),
   428  				)
   429  			})
   430  
   431  			It("returns the error and all warnings", func() {
   432  				Expect(err).To(MatchError(ccerror.MultiError{
   433  					ResponseCode: http.StatusTeapot,
   434  					Errors: []ccerror.V3Error{
   435  						{
   436  							Code:   10008,
   437  							Detail: "The request is semantically invalid: command presence",
   438  							Title:  "CF-UnprocessableEntity",
   439  						},
   440  						{
   441  							Code:   10009,
   442  							Detail: "Some CC Error",
   443  							Title:  "CF-SomeNewError",
   444  						},
   445  					},
   446  				}))
   447  				Expect(warnings).To(ConsistOf("this is a warning"))
   448  			})
   449  		})
   450  	})
   451  
   452  	Describe("GetApplicationProcesses", func() {
   453  		When("the application exists", func() {
   454  			BeforeEach(func() {
   455  				response1 := fmt.Sprintf(`
   456  					{
   457  						"pagination": {
   458  							"next": {
   459  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
   460  							}
   461  						},
   462  						"resources": [
   463  							{
   464  								"guid": "process-1-guid",
   465  								"type": "web",
   466  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   467  								"memory_in_mb": 32,
   468  								"health_check": {
   469                    "type": "port",
   470                    "data": {
   471                      "timeout": null,
   472                      "endpoint": null
   473                    }
   474                  }
   475  							},
   476  							{
   477  								"guid": "process-2-guid",
   478  								"type": "worker",
   479  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   480  								"memory_in_mb": 64,
   481  								"health_check": {
   482                    "type": "http",
   483                    "data": {
   484                      "timeout": 60,
   485                      "endpoint": "/health"
   486                    }
   487                  }
   488  							}
   489  						]
   490  					}`, server.URL())
   491  				response2 := `
   492  					{
   493  						"pagination": {
   494  							"next": null
   495  						},
   496  						"resources": [
   497  							{
   498  								"guid": "process-3-guid",
   499  								"type": "console",
   500  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   501  								"memory_in_mb": 128,
   502  								"health_check": {
   503                    "type": "process",
   504                    "data": {
   505                      "timeout": 90,
   506                      "endpoint": null
   507                    }
   508                  }
   509  							}
   510  						]
   511  					}`
   512  				server.AppendHandlers(
   513  					CombineHandlers(
   514  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   515  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   516  					),
   517  				)
   518  				server.AppendHandlers(
   519  					CombineHandlers(
   520  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
   521  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   522  					),
   523  				)
   524  			})
   525  
   526  			It("returns a list of processes associated with the application and all warnings", func() {
   527  				processes, warnings, err := client.GetApplicationProcesses("some-app-guid")
   528  				Expect(err).ToNot(HaveOccurred())
   529  
   530  				Expect(processes).To(ConsistOf(
   531  					resources.Process{
   532  						GUID:               "process-1-guid",
   533  						Type:               constant.ProcessTypeWeb,
   534  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   535  						MemoryInMB:         types.NullUint64{Value: 32, IsSet: true},
   536  						HealthCheckType:    constant.Port,
   537  						HealthCheckTimeout: 0,
   538  					},
   539  					resources.Process{
   540  						GUID:                "process-2-guid",
   541  						Type:                "worker",
   542  						Command:             types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   543  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   544  						HealthCheckType:     constant.HTTP,
   545  						HealthCheckEndpoint: "/health",
   546  						HealthCheckTimeout:  60,
   547  					},
   548  					resources.Process{
   549  						GUID:               "process-3-guid",
   550  						Type:               "console",
   551  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   552  						MemoryInMB:         types.NullUint64{Value: 128, IsSet: true},
   553  						HealthCheckType:    constant.Process,
   554  						HealthCheckTimeout: 90,
   555  					},
   556  				))
   557  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   558  			})
   559  		})
   560  
   561  		When("cloud controller returns an error", func() {
   562  			BeforeEach(func() {
   563  				response := `{
   564  					"errors": [
   565  						{
   566  							"code": 10010,
   567  							"detail": "App not found",
   568  							"title": "CF-ResourceNotFound"
   569  						}
   570  					]
   571  				}`
   572  				server.AppendHandlers(
   573  					CombineHandlers(
   574  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   575  						RespondWith(http.StatusNotFound, response),
   576  					),
   577  				)
   578  			})
   579  
   580  			It("returns the error", func() {
   581  				_, _, err := client.GetApplicationProcesses("some-app-guid")
   582  				Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{}))
   583  			})
   584  		})
   585  	})
   586  
   587  	Describe("UpdateProcess", func() {
   588  		var (
   589  			inputProcess resources.Process
   590  
   591  			process  resources.Process
   592  			warnings []string
   593  			err      error
   594  		)
   595  
   596  		BeforeEach(func() {
   597  			inputProcess = resources.Process{
   598  				GUID: "some-process-guid",
   599  			}
   600  		})
   601  
   602  		JustBeforeEach(func() {
   603  			process, warnings, err = client.UpdateProcess(inputProcess)
   604  		})
   605  
   606  		When("patching the process succeeds", func() {
   607  			When("the command is set", func() {
   608  				When("the start command is an arbitrary command", func() {
   609  					BeforeEach(func() {
   610  						inputProcess.Command = types.FilteredString{IsSet: true, Value: "some-command"}
   611  
   612  						expectedBody := `{
   613  							"command": "some-command"
   614  						}`
   615  
   616  						expectedResponse := `{
   617  							"command": "some-command"
   618  						}`
   619  
   620  						server.AppendHandlers(
   621  							CombineHandlers(
   622  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   623  								VerifyJSON(expectedBody),
   624  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   625  							),
   626  						)
   627  					})
   628  
   629  					It("patches this process's command with the provided command", func() {
   630  						Expect(err).ToNot(HaveOccurred())
   631  						Expect(warnings).To(ConsistOf("this is a warning"))
   632  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   633  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-command"}),
   634  						}))
   635  					})
   636  				})
   637  
   638  				When("the start command reset", func() {
   639  					BeforeEach(func() {
   640  						inputProcess.Command = types.FilteredString{IsSet: true}
   641  
   642  						expectedBody := `{
   643  							"command": null
   644  						}`
   645  
   646  						expectedResponse := `{
   647  							"command": "some-default-command"
   648  						}`
   649  
   650  						server.AppendHandlers(
   651  							CombineHandlers(
   652  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   653  								VerifyJSON(expectedBody),
   654  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   655  							),
   656  						)
   657  					})
   658  
   659  					It("patches this process's command with 'null' and returns the default command", func() {
   660  						Expect(err).ToNot(HaveOccurred())
   661  						Expect(warnings).To(ConsistOf("this is a warning"))
   662  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   663  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-default-command"}),
   664  						}))
   665  					})
   666  				})
   667  			})
   668  
   669  			When("the endpoint is set", func() {
   670  				BeforeEach(func() {
   671  					inputProcess.HealthCheckEndpoint = "some-endpoint"
   672  					inputProcess.HealthCheckType = "some-type"
   673  
   674  					expectedBody := `{
   675  					"health_check": {
   676  						"type": "some-type",
   677  						"data": {
   678  							"endpoint": "some-endpoint"
   679  						}
   680  					}
   681  				}`
   682  					expectedResponse := `{
   683  					"health_check": {
   684  						"type": "some-type",
   685  						"data": {
   686  							"endpoint": "some-endpoint",
   687  							"invocation_timeout": null
   688  						}
   689  					}
   690  				}`
   691  					server.AppendHandlers(
   692  						CombineHandlers(
   693  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   694  							VerifyJSON(expectedBody),
   695  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   696  						),
   697  					)
   698  				})
   699  
   700  				It("patches this process's health check", func() {
   701  					Expect(err).ToNot(HaveOccurred())
   702  					Expect(warnings).To(ConsistOf("this is a warning"))
   703  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   704  						"HealthCheckType":     Equal(constant.HealthCheckType("some-type")),
   705  						"HealthCheckEndpoint": Equal("some-endpoint"),
   706  					}))
   707  				})
   708  			})
   709  
   710  			When("the invocation timeout is set", func() {
   711  				BeforeEach(func() {
   712  					inputProcess.HealthCheckInvocationTimeout = 42
   713  					inputProcess.HealthCheckType = "some-type"
   714  
   715  					expectedBody := `{
   716  					"health_check": {
   717  						"type": "some-type",
   718  						"data": {
   719  							"invocation_timeout": 42
   720  						}
   721  					}
   722  				}`
   723  					expectedResponse := `{
   724  					"health_check": {
   725  						"type": "some-type",
   726  						"data": {
   727  							"invocation_timeout": 42
   728  						}
   729  					}
   730  				}`
   731  					server.AppendHandlers(
   732  						CombineHandlers(
   733  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   734  							VerifyJSON(expectedBody),
   735  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   736  						),
   737  					)
   738  				})
   739  
   740  				It("patches this process's health check", func() {
   741  					Expect(err).ToNot(HaveOccurred())
   742  					Expect(warnings).To(ConsistOf("this is a warning"))
   743  					Expect(process).To(Equal(resources.Process{
   744  						HealthCheckType:              "some-type",
   745  						HealthCheckInvocationTimeout: 42,
   746  					}))
   747  				})
   748  			})
   749  
   750  			When("the health check timeout is set", func() {
   751  				BeforeEach(func() {
   752  					inputProcess.HealthCheckTimeout = 77
   753  					inputProcess.HealthCheckType = "some-type"
   754  
   755  					expectedBody := `{
   756  					"health_check": {
   757  						"type": "some-type",
   758  						"data": {
   759  							"timeout": 77
   760  						}
   761  					}
   762  				}`
   763  					expectedResponse := `{
   764  					"health_check": {
   765  						"type": "some-type",
   766  						"data": {
   767  							"timeout": 77
   768  						}
   769  					}
   770  				}`
   771  					server.AppendHandlers(
   772  						CombineHandlers(
   773  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   774  							VerifyJSON(expectedBody),
   775  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   776  						),
   777  					)
   778  				})
   779  
   780  				It("patches this process's health check", func() {
   781  					Expect(err).ToNot(HaveOccurred())
   782  					Expect(warnings).To(ConsistOf("this is a warning"))
   783  					Expect(process).To(Equal(resources.Process{
   784  						HealthCheckType:     "some-type",
   785  						HealthCheckEndpoint: "",
   786  						HealthCheckTimeout:  77,
   787  					}))
   788  				})
   789  			})
   790  
   791  			When("the endpoint and timeout are not set", func() {
   792  				BeforeEach(func() {
   793  					inputProcess.HealthCheckType = "some-type"
   794  
   795  					expectedBody := `{
   796  					"health_check": {
   797  						"type": "some-type",
   798  						"data": {}
   799  					}
   800  				}`
   801  					responseBody := `{
   802  					"health_check": {
   803  						"type": "some-type"
   804  					}
   805  				}`
   806  					server.AppendHandlers(
   807  						CombineHandlers(
   808  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   809  							VerifyJSON(expectedBody),
   810  							RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   811  						),
   812  					)
   813  				})
   814  
   815  				It("patches this process's health check", func() {
   816  					Expect(err).ToNot(HaveOccurred())
   817  					Expect(warnings).To(ConsistOf("this is a warning"))
   818  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   819  						"HealthCheckType": Equal(constant.HealthCheckType("some-type")),
   820  					}))
   821  				})
   822  			})
   823  		})
   824  
   825  		When("the process does not exist", func() {
   826  			BeforeEach(func() {
   827  				response := `{
   828  					"errors": [
   829  						{
   830  							"detail": "Process not found",
   831  							"title": "CF-ResourceNotFound",
   832  							"code": 10010
   833  						}
   834  					]
   835  				}`
   836  
   837  				server.AppendHandlers(
   838  					CombineHandlers(
   839  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   840  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   841  					),
   842  				)
   843  			})
   844  
   845  			It("returns an error and warnings", func() {
   846  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
   847  				Expect(warnings).To(ConsistOf("this is a warning"))
   848  			})
   849  		})
   850  
   851  		When("the cloud controller returns errors and warnings", func() {
   852  			BeforeEach(func() {
   853  				response := `{
   854  						"errors": [
   855  							{
   856  								"code": 10008,
   857  								"detail": "The request is semantically invalid: command presence",
   858  								"title": "CF-UnprocessableEntity"
   859  							},
   860  							{
   861  								"code": 10009,
   862  								"detail": "Some CC Error",
   863  								"title": "CF-SomeNewError"
   864  							}
   865  						]
   866  					}`
   867  				server.AppendHandlers(
   868  					CombineHandlers(
   869  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   870  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   871  					),
   872  				)
   873  			})
   874  
   875  			It("returns the error and all warnings", func() {
   876  				Expect(err).To(MatchError(ccerror.MultiError{
   877  					ResponseCode: http.StatusTeapot,
   878  					Errors: []ccerror.V3Error{
   879  						{
   880  							Code:   10008,
   881  							Detail: "The request is semantically invalid: command presence",
   882  							Title:  "CF-UnprocessableEntity",
   883  						},
   884  						{
   885  							Code:   10009,
   886  							Detail: "Some CC Error",
   887  							Title:  "CF-SomeNewError",
   888  						},
   889  					},
   890  				}))
   891  				Expect(warnings).To(ConsistOf("this is a warning"))
   892  			})
   893  		})
   894  	})
   895  })