github.com/LukasHeimann/cloudfoundrycli@v7.1.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("GetProcess", func() {
   180  		var (
   181  			process  Process
   182  			warnings []string
   183  			err      error
   184  		)
   185  
   186  		JustBeforeEach(func() {
   187  			process, warnings, err = client.GetProcess("some-process-guid")
   188  		})
   189  
   190  		When("the process exists", func() {
   191  			BeforeEach(func() {
   192  				response := `{
   193  					"guid": "process-1-guid",
   194  					"type": "some-type",
   195  					"command": "start-command-1",
   196  					"instances": 22,
   197  					"memory_in_mb": 32,
   198  					"disk_in_mb": 1024,
   199  					"relationships": {
   200  						"app": {
   201  							"data": {
   202  								"guid": "some-app-guid"
   203  							}
   204  						}
   205  					},
   206  					"health_check": {
   207  						"type": "http",
   208  						"data": {
   209  							"timeout": 90,
   210  							"endpoint": "/health",
   211  							"invocation_timeout": 42
   212  						}
   213  					}
   214  				}`
   215  				server.AppendHandlers(
   216  					CombineHandlers(
   217  						VerifyRequest(http.MethodGet, "/v3/processes/some-process-guid"),
   218  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   219  					),
   220  				)
   221  			})
   222  
   223  			It("returns the process and all warnings", func() {
   224  				Expect(err).NotTo(HaveOccurred())
   225  				Expect(warnings).To(ConsistOf("this is a warning"))
   226  				Expect(process).To(MatchAllFields(Fields{
   227  					"GUID":                         Equal("process-1-guid"),
   228  					"Type":                         Equal("some-type"),
   229  					"AppGUID":                      Equal("some-app-guid"),
   230  					"Command":                      Equal(types.FilteredString{IsSet: true, Value: "start-command-1"}),
   231  					"Instances":                    Equal(types.NullInt{Value: 22, IsSet: true}),
   232  					"MemoryInMB":                   Equal(types.NullUint64{Value: 32, IsSet: true}),
   233  					"DiskInMB":                     Equal(types.NullUint64{Value: 1024, IsSet: true}),
   234  					"HealthCheckType":              Equal(constant.HTTP),
   235  					"HealthCheckEndpoint":          Equal("/health"),
   236  					"HealthCheckInvocationTimeout": BeEquivalentTo(42),
   237  					"HealthCheckTimeout":           BeEquivalentTo(90),
   238  				}))
   239  			})
   240  		})
   241  
   242  		When("the cloud controller returns errors and warnings", func() {
   243  			BeforeEach(func() {
   244  				response := `{
   245  					"errors": [
   246  						{
   247  							"code": 10008,
   248  							"detail": "The request is semantically invalid: command presence",
   249  							"title": "CF-UnprocessableEntity"
   250  						},
   251  						{
   252  							"code": 10009,
   253  							"detail": "Some CC Error",
   254  							"title": "CF-SomeNewError"
   255  						}
   256  					]
   257  				}`
   258  				server.AppendHandlers(
   259  					CombineHandlers(
   260  						VerifyRequest(http.MethodGet, "/v3/processes/some-process-guid"),
   261  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   262  					),
   263  				)
   264  			})
   265  
   266  			It("returns the error and all warnings", func() {
   267  				Expect(err).To(MatchError(ccerror.MultiError{
   268  					ResponseCode: http.StatusTeapot,
   269  					Errors: []ccerror.V3Error{
   270  						{
   271  							Code:   10008,
   272  							Detail: "The request is semantically invalid: command presence",
   273  							Title:  "CF-UnprocessableEntity",
   274  						},
   275  						{
   276  							Code:   10009,
   277  							Detail: "Some CC Error",
   278  							Title:  "CF-SomeNewError",
   279  						},
   280  					},
   281  				}))
   282  				Expect(warnings).To(ConsistOf("this is a warning"))
   283  			})
   284  		})
   285  	})
   286  
   287  	Describe("CreateApplicationProcessScale", func() {
   288  		var passedProcess Process
   289  
   290  		When("providing all scale options", func() {
   291  			BeforeEach(func() {
   292  				passedProcess = Process{
   293  					Type:       constant.ProcessTypeWeb,
   294  					Instances:  types.NullInt{Value: 2, IsSet: true},
   295  					MemoryInMB: types.NullUint64{Value: 100, IsSet: true},
   296  					DiskInMB:   types.NullUint64{Value: 200, IsSet: true},
   297  				}
   298  				expectedBody := `{
   299  					"instances": 2,
   300  					"memory_in_mb": 100,
   301  					"disk_in_mb": 200
   302  				}`
   303  				response := `{
   304  					"guid": "some-process-guid"
   305  				}`
   306  				server.AppendHandlers(
   307  					CombineHandlers(
   308  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   309  						VerifyJSON(expectedBody),
   310  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   311  					),
   312  				)
   313  			})
   314  
   315  			It("scales the application process; returns the scaled process and all warnings", func() {
   316  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   317  				Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")}))
   318  				Expect(err).ToNot(HaveOccurred())
   319  				Expect(warnings).To(ConsistOf("this is a warning"))
   320  			})
   321  		})
   322  
   323  		When("providing all scale options with 0 values", func() {
   324  			BeforeEach(func() {
   325  				passedProcess = Process{
   326  					Type:       constant.ProcessTypeWeb,
   327  					Instances:  types.NullInt{Value: 0, IsSet: true},
   328  					MemoryInMB: types.NullUint64{Value: 0, IsSet: true},
   329  					DiskInMB:   types.NullUint64{Value: 0, IsSet: true},
   330  				}
   331  				expectedBody := `{
   332  					"instances": 0,
   333  					"memory_in_mb": 0,
   334  					"disk_in_mb": 0
   335  				}`
   336  				response := `{
   337  					"guid": "some-process-guid"
   338  				}`
   339  				server.AppendHandlers(
   340  					CombineHandlers(
   341  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   342  						VerifyJSON(expectedBody),
   343  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   344  					),
   345  				)
   346  			})
   347  
   348  			It("scales the application process to 0 values; returns the scaled process and all warnings", func() {
   349  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   350  				Expect(process).To(MatchFields(IgnoreExtras, Fields{"GUID": Equal("some-process-guid")}))
   351  				Expect(err).ToNot(HaveOccurred())
   352  				Expect(warnings).To(ConsistOf("this is a warning"))
   353  			})
   354  		})
   355  
   356  		When("providing only one scale option", func() {
   357  			BeforeEach(func() {
   358  				passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   359  				expectedBody := `{
   360  					"instances": 2
   361  				}`
   362  				response := `{
   363  					"guid": "some-process-guid",
   364  					"instances": 2
   365  				}`
   366  				server.AppendHandlers(
   367  					CombineHandlers(
   368  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   369  						VerifyJSON(expectedBody),
   370  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   371  					),
   372  				)
   373  			})
   374  
   375  			It("scales the application process; returns the process object and all warnings", func() {
   376  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   377  				Expect(process).To(MatchFields(IgnoreExtras, Fields{
   378  					"GUID":      Equal("some-process-guid"),
   379  					"Instances": Equal(types.NullInt{Value: 2, IsSet: true}),
   380  				}))
   381  				Expect(err).ToNot(HaveOccurred())
   382  				Expect(warnings).To(ConsistOf("this is a warning"))
   383  			})
   384  		})
   385  
   386  		When("an error is encountered", func() {
   387  			BeforeEach(func() {
   388  				passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   389  				response := `{
   390  						"errors": [
   391  							{
   392  								"code": 10008,
   393  								"detail": "The request is semantically invalid: command presence",
   394  								"title": "CF-UnprocessableEntity"
   395  							},
   396  							{
   397  								"code": 10009,
   398  								"detail": "Some CC Error",
   399  								"title": "CF-SomeNewError"
   400  							}
   401  						]
   402  					}`
   403  				server.AppendHandlers(
   404  					CombineHandlers(
   405  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   406  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   407  					),
   408  				)
   409  			})
   410  
   411  			It("returns an empty process, the error and all warnings", func() {
   412  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   413  				Expect(process).To(BeZero())
   414  				Expect(err).To(MatchError(ccerror.MultiError{
   415  					ResponseCode: http.StatusTeapot,
   416  					Errors: []ccerror.V3Error{
   417  						{
   418  							Code:   10008,
   419  							Detail: "The request is semantically invalid: command presence",
   420  							Title:  "CF-UnprocessableEntity",
   421  						},
   422  						{
   423  							Code:   10009,
   424  							Detail: "Some CC Error",
   425  							Title:  "CF-SomeNewError",
   426  						},
   427  					},
   428  				}))
   429  				Expect(warnings).To(ConsistOf("this is a warning"))
   430  			})
   431  		})
   432  	})
   433  
   434  	Describe("GetApplicationProcessByType", func() {
   435  		var (
   436  			process  Process
   437  			warnings []string
   438  			err      error
   439  		)
   440  
   441  		JustBeforeEach(func() {
   442  			process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type")
   443  		})
   444  
   445  		When("the process exists", func() {
   446  			BeforeEach(func() {
   447  				response := `{
   448  					"guid": "process-1-guid",
   449  					"type": "some-type",
   450  					"command": "start-command-1",
   451  					"instances": 22,
   452  					"memory_in_mb": 32,
   453  					"disk_in_mb": 1024,
   454  					"relationships": {
   455  						"app": {
   456  							"data": {
   457  								"guid": "some-app-guid"
   458  							}
   459  						}
   460  					},
   461  					"health_check": {
   462  						"type": "http",
   463  						"data": {
   464  							"timeout": 90,
   465  							"endpoint": "/health",
   466  							"invocation_timeout": 42
   467  						}
   468  					}
   469  				}`
   470  				server.AppendHandlers(
   471  					CombineHandlers(
   472  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   473  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   474  					),
   475  				)
   476  			})
   477  
   478  			It("returns the process and all warnings", func() {
   479  				Expect(err).NotTo(HaveOccurred())
   480  				Expect(warnings).To(ConsistOf("this is a warning"))
   481  				Expect(process).To(MatchAllFields(Fields{
   482  					"GUID":                         Equal("process-1-guid"),
   483  					"Type":                         Equal("some-type"),
   484  					"AppGUID":                      Equal("some-app-guid"),
   485  					"Command":                      Equal(types.FilteredString{IsSet: true, Value: "start-command-1"}),
   486  					"Instances":                    Equal(types.NullInt{Value: 22, IsSet: true}),
   487  					"MemoryInMB":                   Equal(types.NullUint64{Value: 32, IsSet: true}),
   488  					"DiskInMB":                     Equal(types.NullUint64{Value: 1024, IsSet: true}),
   489  					"HealthCheckType":              Equal(constant.HTTP),
   490  					"HealthCheckEndpoint":          Equal("/health"),
   491  					"HealthCheckInvocationTimeout": BeEquivalentTo(42),
   492  					"HealthCheckTimeout":           BeEquivalentTo(90),
   493  				}))
   494  			})
   495  		})
   496  
   497  		When("the application does not exist", func() {
   498  			BeforeEach(func() {
   499  				response := `{
   500  					"errors": [
   501  						{
   502  							"detail": "Application not found",
   503  							"title": "CF-ResourceNotFound",
   504  							"code": 10010
   505  						}
   506  					]
   507  				}`
   508  				server.AppendHandlers(
   509  					CombineHandlers(
   510  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   511  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   512  					),
   513  				)
   514  			})
   515  
   516  			It("returns a ResourceNotFoundError", func() {
   517  				Expect(warnings).To(ConsistOf("this is a warning"))
   518  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"}))
   519  			})
   520  		})
   521  
   522  		When("the cloud controller returns errors and warnings", func() {
   523  			BeforeEach(func() {
   524  				response := `{
   525  					"errors": [
   526  						{
   527  							"code": 10008,
   528  							"detail": "The request is semantically invalid: command presence",
   529  							"title": "CF-UnprocessableEntity"
   530  						},
   531  						{
   532  							"code": 10009,
   533  							"detail": "Some CC Error",
   534  							"title": "CF-SomeNewError"
   535  						}
   536  					]
   537  				}`
   538  				server.AppendHandlers(
   539  					CombineHandlers(
   540  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   541  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   542  					),
   543  				)
   544  			})
   545  
   546  			It("returns the error and all warnings", func() {
   547  				Expect(err).To(MatchError(ccerror.MultiError{
   548  					ResponseCode: http.StatusTeapot,
   549  					Errors: []ccerror.V3Error{
   550  						{
   551  							Code:   10008,
   552  							Detail: "The request is semantically invalid: command presence",
   553  							Title:  "CF-UnprocessableEntity",
   554  						},
   555  						{
   556  							Code:   10009,
   557  							Detail: "Some CC Error",
   558  							Title:  "CF-SomeNewError",
   559  						},
   560  					},
   561  				}))
   562  				Expect(warnings).To(ConsistOf("this is a warning"))
   563  			})
   564  		})
   565  	})
   566  
   567  	Describe("GetApplicationProcesses", func() {
   568  		When("the application exists", func() {
   569  			BeforeEach(func() {
   570  				response1 := fmt.Sprintf(`
   571  					{
   572  						"pagination": {
   573  							"next": {
   574  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
   575  							}
   576  						},
   577  						"resources": [
   578  							{
   579  								"guid": "process-1-guid",
   580  								"type": "web",
   581  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   582  								"memory_in_mb": 32,
   583  								"health_check": {
   584                    "type": "port",
   585                    "data": {
   586                      "timeout": null,
   587                      "endpoint": null
   588                    }
   589                  }
   590  							},
   591  							{
   592  								"guid": "process-2-guid",
   593  								"type": "worker",
   594  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   595  								"memory_in_mb": 64,
   596  								"health_check": {
   597                    "type": "http",
   598                    "data": {
   599                      "timeout": 60,
   600                      "endpoint": "/health"
   601                    }
   602                  }
   603  							}
   604  						]
   605  					}`, server.URL())
   606  				response2 := `
   607  					{
   608  						"pagination": {
   609  							"next": null
   610  						},
   611  						"resources": [
   612  							{
   613  								"guid": "process-3-guid",
   614  								"type": "console",
   615  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   616  								"memory_in_mb": 128,
   617  								"health_check": {
   618                    "type": "process",
   619                    "data": {
   620                      "timeout": 90,
   621                      "endpoint": null
   622                    }
   623                  }
   624  							}
   625  						]
   626  					}`
   627  				server.AppendHandlers(
   628  					CombineHandlers(
   629  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   630  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   631  					),
   632  				)
   633  				server.AppendHandlers(
   634  					CombineHandlers(
   635  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
   636  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   637  					),
   638  				)
   639  			})
   640  
   641  			It("returns a list of processes associated with the application and all warnings", func() {
   642  				processes, warnings, err := client.GetApplicationProcesses("some-app-guid")
   643  				Expect(err).ToNot(HaveOccurred())
   644  
   645  				Expect(processes).To(ConsistOf(
   646  					Process{
   647  						GUID:               "process-1-guid",
   648  						Type:               constant.ProcessTypeWeb,
   649  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   650  						MemoryInMB:         types.NullUint64{Value: 32, IsSet: true},
   651  						HealthCheckType:    constant.Port,
   652  						HealthCheckTimeout: 0,
   653  					},
   654  					Process{
   655  						GUID:                "process-2-guid",
   656  						Type:                "worker",
   657  						Command:             types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   658  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   659  						HealthCheckType:     constant.HTTP,
   660  						HealthCheckEndpoint: "/health",
   661  						HealthCheckTimeout:  60,
   662  					},
   663  					Process{
   664  						GUID:               "process-3-guid",
   665  						Type:               "console",
   666  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   667  						MemoryInMB:         types.NullUint64{Value: 128, IsSet: true},
   668  						HealthCheckType:    constant.Process,
   669  						HealthCheckTimeout: 90,
   670  					},
   671  				))
   672  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   673  			})
   674  		})
   675  
   676  		When("cloud controller returns an error", func() {
   677  			BeforeEach(func() {
   678  				response := `{
   679  					"errors": [
   680  						{
   681  							"code": 10010,
   682  							"detail": "App not found",
   683  							"title": "CF-ResourceNotFound"
   684  						}
   685  					]
   686  				}`
   687  				server.AppendHandlers(
   688  					CombineHandlers(
   689  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   690  						RespondWith(http.StatusNotFound, response),
   691  					),
   692  				)
   693  			})
   694  
   695  			It("returns the error", func() {
   696  				_, _, err := client.GetApplicationProcesses("some-app-guid")
   697  				Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{}))
   698  			})
   699  		})
   700  	})
   701  
   702  	Describe("GetNewApplicationProcesses", func() {
   703  		When("the application exists", func() {
   704  			BeforeEach(func() {
   705  				response1 := fmt.Sprintf(`
   706  					{
   707  						"pagination": {
   708  							"next": {
   709  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
   710  							}
   711  						},
   712  						"resources": [
   713  							{
   714  								"guid": "old-web-process-guid",
   715  								"type": "web",
   716  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   717  								"memory_in_mb": 32,
   718  								"health_check": {
   719  								   "type": "port",
   720  								   "data": {
   721  								 		"timeout": null,
   722  								 		"endpoint": null
   723  								   }
   724  								}
   725  							},
   726  							{
   727  								"guid": "new-web-process-guid",
   728  								"type": "web",
   729  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   730  								"memory_in_mb": 32,
   731  								"health_check": {
   732  									"type": "port",
   733  									"data": {
   734  										"timeout": null,
   735  										"endpoint": null
   736  									}
   737  								}
   738  							},
   739  							{
   740  								"guid": "worker-process-guid",
   741  								"type": "worker",
   742  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   743  								"memory_in_mb": 64,
   744  								"health_check": {
   745  									"type": "http",
   746  									"data": {
   747  										"timeout": 60,
   748  										"endpoint": "/health"
   749  									}
   750  								}
   751  							}
   752  						]
   753  					}`, server.URL())
   754  
   755  				response2 := `
   756  					{
   757  						"pagination": {
   758  							"next": null
   759  						},
   760  						"resources": [
   761  							{
   762  								"guid": "console-process-guid",
   763  								"type": "console",
   764  								"command": "[PRIVATE DATA HIDDEN IN LISTS]",
   765  								"memory_in_mb": 128,
   766  								"health_check": {
   767  								    "type": "process",
   768  								    "data": {
   769  								  		"timeout": 90,
   770  								  		"endpoint": null
   771  								    }
   772  								}
   773  							}
   774  						]
   775  					}`
   776  
   777  				deploymentResponse := `
   778  					{
   779  						"state": "DEPLOYING",
   780  						"new_processes": [
   781  							{
   782  								"guid": "new-web-process-guid", 
   783  								"type": "web"
   784  							}
   785  						]
   786  					}
   787  				`
   788  
   789  				server.AppendHandlers(
   790  					CombineHandlers(
   791  						VerifyRequest(http.MethodGet, "/v3/deployments/some-deployment-guid"),
   792  						RespondWith(http.StatusOK, deploymentResponse, http.Header{"X-CF-Warnings": {"warning-1"}}),
   793  					),
   794  				)
   795  				server.AppendHandlers(
   796  					CombineHandlers(
   797  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   798  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   799  					),
   800  				)
   801  				server.AppendHandlers(
   802  					CombineHandlers(
   803  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
   804  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-3"}}),
   805  					),
   806  				)
   807  			})
   808  
   809  			It("returns a list of processes associated with the application and all warnings", func() {
   810  				processes, warnings, err := client.GetNewApplicationProcesses("some-app-guid", "some-deployment-guid")
   811  				Expect(err).ToNot(HaveOccurred())
   812  
   813  				Expect(processes).To(ConsistOf(
   814  					Process{
   815  						GUID:               "new-web-process-guid",
   816  						Type:               constant.ProcessTypeWeb,
   817  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   818  						MemoryInMB:         types.NullUint64{Value: 32, IsSet: true},
   819  						HealthCheckType:    constant.Port,
   820  						HealthCheckTimeout: 0,
   821  					},
   822  					Process{
   823  						GUID:                "worker-process-guid",
   824  						Type:                "worker",
   825  						Command:             types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   826  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   827  						HealthCheckType:     constant.HTTP,
   828  						HealthCheckEndpoint: "/health",
   829  						HealthCheckTimeout:  60,
   830  					},
   831  					Process{
   832  						GUID:               "console-process-guid",
   833  						Type:               "console",
   834  						Command:            types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
   835  						MemoryInMB:         types.NullUint64{Value: 128, IsSet: true},
   836  						HealthCheckType:    constant.Process,
   837  						HealthCheckTimeout: 90,
   838  					},
   839  				))
   840  				Expect(warnings).To(ConsistOf("warning-1", "warning-2", "warning-3"))
   841  			})
   842  		})
   843  
   844  		When("cloud controller returns an error", func() {
   845  			BeforeEach(func() {
   846  				response := `{
   847  					"errors": [
   848  						{
   849  							"code": 10010,
   850  							"detail": "Deployment not found",
   851  							"title": "CF-ResourceNotFound"
   852  						}
   853  					]
   854  				}`
   855  				server.AppendHandlers(
   856  					CombineHandlers(
   857  						VerifyRequest(http.MethodGet, "/v3/deployments/some-deployment-guid"),
   858  						RespondWith(http.StatusNotFound, response),
   859  					),
   860  				)
   861  			})
   862  
   863  			It("returns the error", func() {
   864  				_, _, err := client.GetNewApplicationProcesses("some-app-guid", "some-deployment-guid")
   865  				Expect(err).To(MatchError(ccerror.DeploymentNotFoundError{}))
   866  			})
   867  		})
   868  	})
   869  
   870  	Describe("UpdateProcess", func() {
   871  		var (
   872  			inputProcess Process
   873  
   874  			process  Process
   875  			warnings []string
   876  			err      error
   877  		)
   878  
   879  		BeforeEach(func() {
   880  			inputProcess = Process{
   881  				GUID: "some-process-guid",
   882  			}
   883  		})
   884  
   885  		JustBeforeEach(func() {
   886  			process, warnings, err = client.UpdateProcess(inputProcess)
   887  		})
   888  
   889  		When("patching the process succeeds", func() {
   890  			When("the command is set", func() {
   891  				When("the start command is an arbitrary command", func() {
   892  					BeforeEach(func() {
   893  						inputProcess.Command = types.FilteredString{IsSet: true, Value: "some-command"}
   894  
   895  						expectedBody := `{
   896  							"command": "some-command"
   897  						}`
   898  
   899  						expectedResponse := `{
   900  							"command": "some-command"
   901  						}`
   902  
   903  						server.AppendHandlers(
   904  							CombineHandlers(
   905  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   906  								VerifyJSON(expectedBody),
   907  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   908  							),
   909  						)
   910  					})
   911  
   912  					It("patches this process's command with the provided command", func() {
   913  						Expect(err).ToNot(HaveOccurred())
   914  						Expect(warnings).To(ConsistOf("this is a warning"))
   915  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   916  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-command"}),
   917  						}))
   918  					})
   919  				})
   920  
   921  				When("the start command reset", func() {
   922  					BeforeEach(func() {
   923  						inputProcess.Command = types.FilteredString{IsSet: true}
   924  
   925  						expectedBody := `{
   926  							"command": null
   927  						}`
   928  
   929  						expectedResponse := `{
   930  							"command": "some-default-command"
   931  						}`
   932  
   933  						server.AppendHandlers(
   934  							CombineHandlers(
   935  								VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   936  								VerifyJSON(expectedBody),
   937  								RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   938  							),
   939  						)
   940  					})
   941  
   942  					It("patches this process's command with 'null' and returns the default command", func() {
   943  						Expect(err).ToNot(HaveOccurred())
   944  						Expect(warnings).To(ConsistOf("this is a warning"))
   945  						Expect(process).To(MatchFields(IgnoreExtras, Fields{
   946  							"Command": Equal(types.FilteredString{IsSet: true, Value: "some-default-command"}),
   947  						}))
   948  					})
   949  				})
   950  			})
   951  
   952  			When("the endpoint is set", func() {
   953  				BeforeEach(func() {
   954  					inputProcess.HealthCheckEndpoint = "some-endpoint"
   955  					inputProcess.HealthCheckType = "some-type"
   956  
   957  					expectedBody := `{
   958  					"health_check": {
   959  						"type": "some-type",
   960  						"data": {
   961  							"endpoint": "some-endpoint"
   962  						}
   963  					}
   964  				}`
   965  					expectedResponse := `{
   966  					"health_check": {
   967  						"type": "some-type",
   968  						"data": {
   969  							"endpoint": "some-endpoint",
   970  							"invocation_timeout": null
   971  						}
   972  					}
   973  				}`
   974  					server.AppendHandlers(
   975  						CombineHandlers(
   976  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   977  							VerifyJSON(expectedBody),
   978  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   979  						),
   980  					)
   981  				})
   982  
   983  				It("patches this process's health check", func() {
   984  					Expect(err).ToNot(HaveOccurred())
   985  					Expect(warnings).To(ConsistOf("this is a warning"))
   986  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
   987  						"HealthCheckType":     Equal(constant.HealthCheckType("some-type")),
   988  						"HealthCheckEndpoint": Equal("some-endpoint"),
   989  					}))
   990  				})
   991  			})
   992  
   993  			When("the invocation timeout is set", func() {
   994  				BeforeEach(func() {
   995  					inputProcess.HealthCheckInvocationTimeout = 42
   996  					inputProcess.HealthCheckType = "some-type"
   997  
   998  					expectedBody := `{
   999  					"health_check": {
  1000  						"type": "some-type",
  1001  						"data": {
  1002  							"invocation_timeout": 42
  1003  						}
  1004  					}
  1005  				}`
  1006  					expectedResponse := `{
  1007  					"health_check": {
  1008  						"type": "some-type",
  1009  						"data": {
  1010  							"invocation_timeout": 42
  1011  						}
  1012  					}
  1013  				}`
  1014  					server.AppendHandlers(
  1015  						CombineHandlers(
  1016  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
  1017  							VerifyJSON(expectedBody),
  1018  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
  1019  						),
  1020  					)
  1021  				})
  1022  
  1023  				It("patches this process's health check", func() {
  1024  					Expect(err).ToNot(HaveOccurred())
  1025  					Expect(warnings).To(ConsistOf("this is a warning"))
  1026  					Expect(process).To(Equal(Process{
  1027  						HealthCheckType:              "some-type",
  1028  						HealthCheckInvocationTimeout: 42,
  1029  					}))
  1030  				})
  1031  			})
  1032  
  1033  			When("the health check timeout is set", func() {
  1034  				BeforeEach(func() {
  1035  					inputProcess.HealthCheckTimeout = 77
  1036  					inputProcess.HealthCheckType = "some-type"
  1037  
  1038  					expectedBody := `{
  1039  					"health_check": {
  1040  						"type": "some-type",
  1041  						"data": {
  1042  							"timeout": 77
  1043  						}
  1044  					}
  1045  				}`
  1046  					expectedResponse := `{
  1047  					"health_check": {
  1048  						"type": "some-type",
  1049  						"data": {
  1050  							"timeout": 77
  1051  						}
  1052  					}
  1053  				}`
  1054  					server.AppendHandlers(
  1055  						CombineHandlers(
  1056  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
  1057  							VerifyJSON(expectedBody),
  1058  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
  1059  						),
  1060  					)
  1061  				})
  1062  
  1063  				It("patches this process's health check", func() {
  1064  					Expect(err).ToNot(HaveOccurred())
  1065  					Expect(warnings).To(ConsistOf("this is a warning"))
  1066  					Expect(process).To(Equal(Process{
  1067  						HealthCheckType:     "some-type",
  1068  						HealthCheckEndpoint: "",
  1069  						HealthCheckTimeout:  77,
  1070  					}))
  1071  				})
  1072  			})
  1073  
  1074  			When("the endpoint and timeout are not set", func() {
  1075  				BeforeEach(func() {
  1076  					inputProcess.HealthCheckType = "some-type"
  1077  
  1078  					expectedBody := `{
  1079  					"health_check": {
  1080  						"type": "some-type",
  1081  						"data": {}
  1082  					}
  1083  				}`
  1084  					responseBody := `{
  1085  					"health_check": {
  1086  						"type": "some-type"
  1087  					}
  1088  				}`
  1089  					server.AppendHandlers(
  1090  						CombineHandlers(
  1091  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
  1092  							VerifyJSON(expectedBody),
  1093  							RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
  1094  						),
  1095  					)
  1096  				})
  1097  
  1098  				It("patches this process's health check", func() {
  1099  					Expect(err).ToNot(HaveOccurred())
  1100  					Expect(warnings).To(ConsistOf("this is a warning"))
  1101  					Expect(process).To(MatchFields(IgnoreExtras, Fields{
  1102  						"HealthCheckType": Equal(constant.HealthCheckType("some-type")),
  1103  					}))
  1104  				})
  1105  			})
  1106  		})
  1107  
  1108  		When("the process does not exist", func() {
  1109  			BeforeEach(func() {
  1110  				response := `{
  1111  					"errors": [
  1112  						{
  1113  							"detail": "Process not found",
  1114  							"title": "CF-ResourceNotFound",
  1115  							"code": 10010
  1116  						}
  1117  					]
  1118  				}`
  1119  
  1120  				server.AppendHandlers(
  1121  					CombineHandlers(
  1122  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
  1123  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
  1124  					),
  1125  				)
  1126  			})
  1127  
  1128  			It("returns an error and warnings", func() {
  1129  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
  1130  				Expect(warnings).To(ConsistOf("this is a warning"))
  1131  			})
  1132  		})
  1133  
  1134  		When("the cloud controller returns errors and warnings", func() {
  1135  			BeforeEach(func() {
  1136  				response := `{
  1137  						"errors": [
  1138  							{
  1139  								"code": 10008,
  1140  								"detail": "The request is semantically invalid: command presence",
  1141  								"title": "CF-UnprocessableEntity"
  1142  							},
  1143  							{
  1144  								"code": 10009,
  1145  								"detail": "Some CC Error",
  1146  								"title": "CF-SomeNewError"
  1147  							}
  1148  						]
  1149  					}`
  1150  				server.AppendHandlers(
  1151  					CombineHandlers(
  1152  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
  1153  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
  1154  					),
  1155  				)
  1156  			})
  1157  
  1158  			It("returns the error and all warnings", func() {
  1159  				Expect(err).To(MatchError(ccerror.MultiError{
  1160  					ResponseCode: http.StatusTeapot,
  1161  					Errors: []ccerror.V3Error{
  1162  						{
  1163  							Code:   10008,
  1164  							Detail: "The request is semantically invalid: command presence",
  1165  							Title:  "CF-UnprocessableEntity",
  1166  						},
  1167  						{
  1168  							Code:   10009,
  1169  							Detail: "Some CC Error",
  1170  							Title:  "CF-SomeNewError",
  1171  						},
  1172  					},
  1173  				}))
  1174  				Expect(warnings).To(ConsistOf("this is a warning"))
  1175  			})
  1176  		})
  1177  	})
  1178  })