github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/process_test.go (about)

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