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