github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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  )
    16  
    17  var _ = Describe("Process", func() {
    18  	var client *Client
    19  
    20  	BeforeEach(func() {
    21  		client = NewTestClient()
    22  	})
    23  	Describe("MarshalJSON", func() {
    24  		var (
    25  			process      Process
    26  			processBytes []byte
    27  			err          error
    28  		)
    29  
    30  		BeforeEach(func() {
    31  			process = Process{}
    32  		})
    33  
    34  		JustBeforeEach(func() {
    35  			processBytes, err = process.MarshalJSON()
    36  			Expect(err).ToNot(HaveOccurred())
    37  		})
    38  
    39  		Context("when health check type http is provided", func() {
    40  			BeforeEach(func() {
    41  				process = Process{
    42  					HealthCheckType:     "http",
    43  					HealthCheckEndpoint: "some-endpoint",
    44  				}
    45  			})
    46  
    47  			It("sets the health check type to http and has an endpoint", func() {
    48  				Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`))
    49  			})
    50  		})
    51  
    52  		Context("when health check type port is provided", func() {
    53  			BeforeEach(func() {
    54  				process = Process{
    55  					HealthCheckType: "port",
    56  				}
    57  			})
    58  
    59  			It("sets the health check type to port", func() {
    60  				Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`))
    61  			})
    62  		})
    63  
    64  		Context("when health check type process is provided", func() {
    65  			BeforeEach(func() {
    66  				process = Process{
    67  					HealthCheckType: "process",
    68  				}
    69  			})
    70  
    71  			It("sets the health check type to process", func() {
    72  				Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`))
    73  			})
    74  		})
    75  	})
    76  
    77  	Describe("UnmarshalJSON", func() {
    78  		var (
    79  			process      Process
    80  			processBytes []byte
    81  			err          error
    82  		)
    83  		BeforeEach(func() {
    84  			processBytes = []byte("{}")
    85  		})
    86  
    87  		JustBeforeEach(func() {
    88  			err = json.Unmarshal(processBytes, &process)
    89  			Expect(err).ToNot(HaveOccurred())
    90  		})
    91  		Context("when health check type http is provided", func() {
    92  			BeforeEach(func() {
    93  				processBytes = []byte(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`)
    94  			})
    95  
    96  			It("sets the health check type to http and has an endpoint", func() {
    97  				Expect(process).To(Equal(Process{
    98  					HealthCheckType:     "http",
    99  					HealthCheckEndpoint: "some-endpoint",
   100  				}))
   101  			})
   102  		})
   103  
   104  		Context("when health check type port is provided", func() {
   105  			BeforeEach(func() {
   106  				processBytes = []byte(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`)
   107  			})
   108  
   109  			It("sets the health check type to port", func() {
   110  				Expect(process).To(Equal(Process{
   111  					HealthCheckType: "port",
   112  				}))
   113  			})
   114  		})
   115  
   116  		Context("when health check type process is provided", func() {
   117  			BeforeEach(func() {
   118  				processBytes = []byte(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`)
   119  			})
   120  
   121  			It("sets the health check type to process", func() {
   122  				Expect(process).To(Equal(Process{
   123  					HealthCheckType: "process",
   124  				}))
   125  			})
   126  		})
   127  	})
   128  
   129  	Describe("GetApplicationProcesses", func() {
   130  		Context("when the application exists", func() {
   131  			BeforeEach(func() {
   132  				response1 := fmt.Sprintf(`
   133  					{
   134  						"pagination": {
   135  							"next": {
   136  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
   137  							}
   138  						},
   139  						"resources": [
   140  							{
   141  								"guid": "process-1-guid",
   142  								"type": "web",
   143  								"memory_in_mb": 32,
   144  								"health_check": {
   145                    "type": "port",
   146                    "data": {
   147                      "timeout": null,
   148                      "endpoint": null
   149                    }
   150                  }
   151  							},
   152  							{
   153  								"guid": "process-2-guid",
   154  								"type": "worker",
   155  								"memory_in_mb": 64,
   156  								"health_check": {
   157                    "type": "http",
   158                    "data": {
   159                      "timeout": 60,
   160                      "endpoint": "/health"
   161                    }
   162                  }
   163  							}
   164  						]
   165  					}`, server.URL())
   166  				response2 := `
   167  					{
   168  						"pagination": {
   169  							"next": null
   170  						},
   171  						"resources": [
   172  							{
   173  								"guid": "process-3-guid",
   174  								"type": "console",
   175  								"memory_in_mb": 128,
   176  								"health_check": {
   177                    "type": "process",
   178                    "data": {
   179                      "timeout": 90,
   180                      "endpoint": null
   181                    }
   182                  }
   183  							}
   184  						]
   185  					}`
   186  				server.AppendHandlers(
   187  					CombineHandlers(
   188  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   189  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   190  					),
   191  				)
   192  				server.AppendHandlers(
   193  					CombineHandlers(
   194  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
   195  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   196  					),
   197  				)
   198  			})
   199  
   200  			It("returns a list of processes associated with the application and all warnings", func() {
   201  				processes, warnings, err := client.GetApplicationProcesses("some-app-guid")
   202  				Expect(err).ToNot(HaveOccurred())
   203  
   204  				Expect(processes).To(ConsistOf(
   205  					Process{
   206  						GUID:            "process-1-guid",
   207  						Type:            constant.ProcessTypeWeb,
   208  						MemoryInMB:      types.NullUint64{Value: 32, IsSet: true},
   209  						HealthCheckType: "port",
   210  					},
   211  					Process{
   212  						GUID:                "process-2-guid",
   213  						Type:                "worker",
   214  						MemoryInMB:          types.NullUint64{Value: 64, IsSet: true},
   215  						HealthCheckType:     "http",
   216  						HealthCheckEndpoint: "/health",
   217  					},
   218  					Process{
   219  						GUID:            "process-3-guid",
   220  						Type:            "console",
   221  						MemoryInMB:      types.NullUint64{Value: 128, IsSet: true},
   222  						HealthCheckType: "process",
   223  					},
   224  				))
   225  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   226  			})
   227  		})
   228  
   229  		Context("when cloud controller returns an error", func() {
   230  			BeforeEach(func() {
   231  				response := `{
   232  					"errors": [
   233  						{
   234  							"code": 10010,
   235  							"detail": "App not found",
   236  							"title": "CF-ResourceNotFound"
   237  						}
   238  					]
   239  				}`
   240  				server.AppendHandlers(
   241  					CombineHandlers(
   242  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   243  						RespondWith(http.StatusNotFound, response),
   244  					),
   245  				)
   246  			})
   247  
   248  			It("returns the error", func() {
   249  				_, _, err := client.GetApplicationProcesses("some-app-guid")
   250  				Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{}))
   251  			})
   252  		})
   253  	})
   254  
   255  	Describe("GetApplicationProcessByType", func() {
   256  		var (
   257  			process  Process
   258  			warnings []string
   259  			err      error
   260  		)
   261  
   262  		JustBeforeEach(func() {
   263  			process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type")
   264  		})
   265  
   266  		Context("when the process exists", func() {
   267  			BeforeEach(func() {
   268  				response := `{
   269  					"guid": "process-1-guid",
   270  					"type": "some-type",
   271  					"memory_in_mb": 32,
   272  					"health_check": {
   273  						"type": "http",
   274  						"data": {
   275  							"timeout": 90,
   276  							"endpoint": "/health"
   277  						}
   278  					}
   279  				}`
   280  				server.AppendHandlers(
   281  					CombineHandlers(
   282  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   283  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   284  					),
   285  				)
   286  			})
   287  
   288  			It("returns the process and all warnings", func() {
   289  				Expect(err).NotTo(HaveOccurred())
   290  				Expect(warnings).To(ConsistOf("this is a warning"))
   291  				Expect(process).To(Equal(Process{
   292  					GUID:                "process-1-guid",
   293  					Type:                "some-type",
   294  					MemoryInMB:          types.NullUint64{Value: 32, IsSet: true},
   295  					HealthCheckType:     "http",
   296  					HealthCheckEndpoint: "/health",
   297  				}))
   298  			})
   299  		})
   300  
   301  		Context("when the application does not exist", func() {
   302  			BeforeEach(func() {
   303  				response := `{
   304  					"errors": [
   305  						{
   306  							"detail": "Application not found",
   307  							"title": "CF-ResourceNotFound",
   308  							"code": 10010
   309  						}
   310  					]
   311  				}`
   312  				server.AppendHandlers(
   313  					CombineHandlers(
   314  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   315  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   316  					),
   317  				)
   318  			})
   319  
   320  			It("returns a ResourceNotFoundError", func() {
   321  				Expect(warnings).To(ConsistOf("this is a warning"))
   322  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"}))
   323  			})
   324  		})
   325  
   326  		Context("when the cloud controller returns errors and warnings", func() {
   327  			BeforeEach(func() {
   328  				response := `{
   329  					"errors": [
   330  						{
   331  							"code": 10008,
   332  							"detail": "The request is semantically invalid: command presence",
   333  							"title": "CF-UnprocessableEntity"
   334  						},
   335  						{
   336  							"code": 10009,
   337  							"detail": "Some CC Error",
   338  							"title": "CF-SomeNewError"
   339  						}
   340  					]
   341  				}`
   342  				server.AppendHandlers(
   343  					CombineHandlers(
   344  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   345  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   346  					),
   347  				)
   348  			})
   349  
   350  			It("returns the error and all warnings", func() {
   351  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   352  					ResponseCode: http.StatusTeapot,
   353  					V3ErrorResponse: ccerror.V3ErrorResponse{
   354  						Errors: []ccerror.V3Error{
   355  							{
   356  								Code:   10008,
   357  								Detail: "The request is semantically invalid: command presence",
   358  								Title:  "CF-UnprocessableEntity",
   359  							},
   360  							{
   361  								Code:   10009,
   362  								Detail: "Some CC Error",
   363  								Title:  "CF-SomeNewError",
   364  							},
   365  						},
   366  					},
   367  				}))
   368  				Expect(warnings).To(ConsistOf("this is a warning"))
   369  			})
   370  		})
   371  	})
   372  
   373  	Describe("PatchApplicationProcessHealthCheck", func() {
   374  		var (
   375  			endpoint string
   376  
   377  			process  Process
   378  			warnings []string
   379  			err      error
   380  		)
   381  
   382  		JustBeforeEach(func() {
   383  			process, warnings, err = client.PatchApplicationProcessHealthCheck("some-process-guid", "some-type", endpoint)
   384  		})
   385  
   386  		Context("when patching the process succeeds", func() {
   387  			Context("and the endpoint is non-empty", func() {
   388  				BeforeEach(func() {
   389  					endpoint = "some-endpoint"
   390  					expectedBody := `{
   391  					"health_check": {
   392  						"type": "some-type",
   393  						"data": {
   394  							"endpoint": "some-endpoint"
   395  						}
   396  					}
   397  				}`
   398  					expectedResponse := `{
   399  					"health_check": {
   400  						"type": "some-type",
   401  						"data": {
   402  							"endpoint": "some-endpoint"
   403  						}
   404  					}
   405  				}`
   406  					server.AppendHandlers(
   407  						CombineHandlers(
   408  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   409  							VerifyJSON(expectedBody),
   410  							RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   411  						),
   412  					)
   413  				})
   414  
   415  				It("patches this process's health check", func() {
   416  					Expect(process).To(Equal(Process{
   417  						HealthCheckType:     "some-type",
   418  						HealthCheckEndpoint: "some-endpoint",
   419  					}))
   420  					Expect(err).ToNot(HaveOccurred())
   421  					Expect(warnings).To(ConsistOf("this is a warning"))
   422  				})
   423  			})
   424  
   425  			Context("and the endpoint is empty", func() {
   426  				BeforeEach(func() {
   427  					endpoint = ""
   428  					expectedBody := `{
   429  					"health_check": {
   430  						"type": "some-type",
   431  						"data": {
   432  							"endpoint": null
   433  						}
   434  					}
   435  				}`
   436  					responseBody := `{
   437  					"guid": "some-process-guid",
   438  					"health_check": {
   439  						"type": "some-type",
   440  						"data": {
   441  							"endpoint": null
   442  						}
   443  					}
   444  				}`
   445  					server.AppendHandlers(
   446  						CombineHandlers(
   447  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   448  							VerifyJSON(expectedBody),
   449  							RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   450  						),
   451  					)
   452  				})
   453  
   454  				It("patches this process's health check", func() {
   455  					Expect(process).To(Equal(Process{GUID: "some-process-guid", HealthCheckType: "some-type"}))
   456  					Expect(err).ToNot(HaveOccurred())
   457  					Expect(warnings).To(ConsistOf("this is a warning"))
   458  				})
   459  			})
   460  		})
   461  
   462  		Context("when the process does not exist", func() {
   463  			BeforeEach(func() {
   464  				endpoint = "some-endpoint"
   465  				response := `{
   466  					"errors": [
   467  						{
   468  							"detail": "Process not found",
   469  							"title": "CF-ResourceNotFound",
   470  							"code": 10010
   471  						}
   472  					]
   473  				}`
   474  
   475  				server.AppendHandlers(
   476  					CombineHandlers(
   477  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   478  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   479  					),
   480  				)
   481  			})
   482  
   483  			It("returns an error and warnings", func() {
   484  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
   485  				Expect(warnings).To(ConsistOf("this is a warning"))
   486  			})
   487  		})
   488  
   489  		Context("when the cloud controller returns errors and warnings", func() {
   490  			BeforeEach(func() {
   491  				endpoint = "some-endpoint"
   492  				response := `{
   493  						"errors": [
   494  							{
   495  								"code": 10008,
   496  								"detail": "The request is semantically invalid: command presence",
   497  								"title": "CF-UnprocessableEntity"
   498  							},
   499  							{
   500  								"code": 10009,
   501  								"detail": "Some CC Error",
   502  								"title": "CF-SomeNewError"
   503  							}
   504  						]
   505  					}`
   506  				server.AppendHandlers(
   507  					CombineHandlers(
   508  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   509  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   510  					),
   511  				)
   512  			})
   513  
   514  			It("returns the error and all warnings", func() {
   515  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   516  					ResponseCode: http.StatusTeapot,
   517  					V3ErrorResponse: ccerror.V3ErrorResponse{
   518  						Errors: []ccerror.V3Error{
   519  							{
   520  								Code:   10008,
   521  								Detail: "The request is semantically invalid: command presence",
   522  								Title:  "CF-UnprocessableEntity",
   523  							},
   524  							{
   525  								Code:   10009,
   526  								Detail: "Some CC Error",
   527  								Title:  "CF-SomeNewError",
   528  							},
   529  						},
   530  					},
   531  				}))
   532  				Expect(warnings).To(ConsistOf("this is a warning"))
   533  			})
   534  		})
   535  	})
   536  
   537  	Describe("CreateApplicationProcessScale", func() {
   538  		var passedProcess Process
   539  
   540  		Context("when providing all scale options", func() {
   541  			BeforeEach(func() {
   542  				passedProcess = Process{
   543  					Type:       constant.ProcessTypeWeb,
   544  					Instances:  types.NullInt{Value: 2, IsSet: true},
   545  					MemoryInMB: types.NullUint64{Value: 100, IsSet: true},
   546  					DiskInMB:   types.NullUint64{Value: 200, IsSet: true},
   547  				}
   548  				expectedBody := `{
   549  					"instances": 2,
   550  					"memory_in_mb": 100,
   551  					"disk_in_mb": 200
   552  				}`
   553  				response := `{
   554  					"guid": "some-process-guid"
   555  				}`
   556  				server.AppendHandlers(
   557  					CombineHandlers(
   558  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   559  						VerifyJSON(expectedBody),
   560  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   561  					),
   562  				)
   563  			})
   564  
   565  			It("scales the application process; returns the scaled process and all warnings", func() {
   566  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   567  				Expect(process).To(Equal(Process{GUID: "some-process-guid"}))
   568  				Expect(err).ToNot(HaveOccurred())
   569  				Expect(warnings).To(ConsistOf("this is a warning"))
   570  			})
   571  		})
   572  
   573  		Context("when providing all scale options with 0 values", func() {
   574  			BeforeEach(func() {
   575  				passedProcess = Process{
   576  					Type:       constant.ProcessTypeWeb,
   577  					Instances:  types.NullInt{Value: 0, IsSet: true},
   578  					MemoryInMB: types.NullUint64{Value: 0, IsSet: true},
   579  					DiskInMB:   types.NullUint64{Value: 0, IsSet: true},
   580  				}
   581  				expectedBody := `{
   582  					"instances": 0,
   583  					"memory_in_mb": 0,
   584  					"disk_in_mb": 0
   585  				}`
   586  				response := `{
   587  					"guid": "some-process-guid"
   588  				}`
   589  				server.AppendHandlers(
   590  					CombineHandlers(
   591  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   592  						VerifyJSON(expectedBody),
   593  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   594  					),
   595  				)
   596  			})
   597  
   598  			It("scales the application process to 0 values; returns the scaled process and all warnings", func() {
   599  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   600  				Expect(process).To(Equal(Process{GUID: "some-process-guid"}))
   601  				Expect(err).ToNot(HaveOccurred())
   602  				Expect(warnings).To(ConsistOf("this is a warning"))
   603  			})
   604  		})
   605  
   606  		Context("when providing only one scale option", func() {
   607  			BeforeEach(func() {
   608  				passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   609  				expectedBody := `{
   610  					"instances": 2
   611  				}`
   612  				response := `{
   613  					"guid": "some-process-guid",
   614  					"instances": 2
   615  				}`
   616  				server.AppendHandlers(
   617  					CombineHandlers(
   618  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   619  						VerifyJSON(expectedBody),
   620  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   621  					),
   622  				)
   623  			})
   624  
   625  			It("scales the application process; returns the process object and all warnings", func() {
   626  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   627  				Expect(process).To(Equal(Process{GUID: "some-process-guid", Instances: types.NullInt{Value: 2, IsSet: true}}))
   628  				Expect(err).ToNot(HaveOccurred())
   629  				Expect(warnings).To(ConsistOf("this is a warning"))
   630  			})
   631  		})
   632  
   633  		Context("when an error is encountered", func() {
   634  			BeforeEach(func() {
   635  				passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   636  				response := `{
   637  						"errors": [
   638  							{
   639  								"code": 10008,
   640  								"detail": "The request is semantically invalid: command presence",
   641  								"title": "CF-UnprocessableEntity"
   642  							},
   643  							{
   644  								"code": 10009,
   645  								"detail": "Some CC Error",
   646  								"title": "CF-SomeNewError"
   647  							}
   648  						]
   649  					}`
   650  				server.AppendHandlers(
   651  					CombineHandlers(
   652  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   653  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   654  					),
   655  				)
   656  			})
   657  
   658  			It("returns an empty process, the error and all warnings", func() {
   659  				process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   660  				Expect(process).To(BeZero())
   661  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   662  					ResponseCode: http.StatusTeapot,
   663  					V3ErrorResponse: ccerror.V3ErrorResponse{
   664  						Errors: []ccerror.V3Error{
   665  							{
   666  								Code:   10008,
   667  								Detail: "The request is semantically invalid: command presence",
   668  								Title:  "CF-UnprocessableEntity",
   669  							},
   670  							{
   671  								Code:   10009,
   672  								Detail: "Some CC Error",
   673  								Title:  "CF-SomeNewError",
   674  							},
   675  						},
   676  					},
   677  				}))
   678  				Expect(warnings).To(ConsistOf("this is a warning"))
   679  			})
   680  		})
   681  	})
   682  })