github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/api/cloudcontroller/ccv3/process_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
     8  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    10  	"code.cloudfoundry.org/cli/types"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  	. "github.com/onsi/gomega/ghttp"
    14  )
    15  
    16  var _ = Describe("Process", func() {
    17  	var client *Client
    18  
    19  	BeforeEach(func() {
    20  		client = NewTestClient()
    21  	})
    22  
    23  	Describe("GetApplicationProcesses", func() {
    24  		Context("when the application exists", func() {
    25  			BeforeEach(func() {
    26  				response1 := fmt.Sprintf(`
    27  					{
    28  						"pagination": {
    29  							"next": {
    30  								"href": "%s/v3/apps/some-app-guid/processes?page=2"
    31  							}
    32  						},
    33  						"resources": [
    34  							{
    35  								"guid": "process-1-guid",
    36  								"type": "web",
    37  								"memory_in_mb": 32,
    38  								"health_check": {
    39                    "type": "port",
    40                    "data": {
    41                      "timeout": null,
    42                      "endpoint": null
    43                    }
    44                  }
    45  							},
    46  							{
    47  								"guid": "process-2-guid",
    48  								"type": "worker",
    49  								"memory_in_mb": 64,
    50  								"health_check": {
    51                    "type": "http",
    52                    "data": {
    53                      "timeout": 60,
    54                      "endpoint": "/health"
    55                    }
    56                  }
    57  							}
    58  						]
    59  					}`, server.URL())
    60  				response2 := `
    61  					{
    62  						"pagination": {
    63  							"next": null
    64  						},
    65  						"resources": [
    66  							{
    67  								"guid": "process-3-guid",
    68  								"type": "console",
    69  								"memory_in_mb": 128,
    70  								"health_check": {
    71                    "type": "process",
    72                    "data": {
    73                      "timeout": 90,
    74                      "endpoint": null
    75                    }
    76                  }
    77  							}
    78  						]
    79  					}`
    80  				server.AppendHandlers(
    81  					CombineHandlers(
    82  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
    83  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
    84  					),
    85  				)
    86  				server.AppendHandlers(
    87  					CombineHandlers(
    88  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"),
    89  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
    90  					),
    91  				)
    92  			})
    93  
    94  			It("returns a list of processes associated with the application and all warnings", func() {
    95  				processes, warnings, err := client.GetApplicationProcesses("some-app-guid")
    96  				Expect(err).ToNot(HaveOccurred())
    97  
    98  				Expect(processes).To(ConsistOf(
    99  					Process{
   100  						GUID:        "process-1-guid",
   101  						Type:        constant.ProcessTypeWeb,
   102  						MemoryInMB:  types.NullUint64{Value: 32, IsSet: true},
   103  						HealthCheck: ProcessHealthCheck{Type: "port"},
   104  					},
   105  					Process{
   106  						GUID:       "process-2-guid",
   107  						Type:       "worker",
   108  						MemoryInMB: types.NullUint64{Value: 64, IsSet: true},
   109  						HealthCheck: ProcessHealthCheck{
   110  							Type: "http",
   111  							Data: ProcessHealthCheckData{Endpoint: "/health"},
   112  						},
   113  					},
   114  					Process{
   115  						GUID:        "process-3-guid",
   116  						Type:        "console",
   117  						MemoryInMB:  types.NullUint64{Value: 128, IsSet: true},
   118  						HealthCheck: ProcessHealthCheck{Type: "process"},
   119  					},
   120  				))
   121  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   122  			})
   123  		})
   124  
   125  		Context("when cloud controller returns an error", func() {
   126  			BeforeEach(func() {
   127  				response := `{
   128  					"errors": [
   129  						{
   130  							"code": 10010,
   131  							"detail": "App not found",
   132  							"title": "CF-ResourceNotFound"
   133  						}
   134  					]
   135  				}`
   136  				server.AppendHandlers(
   137  					CombineHandlers(
   138  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"),
   139  						RespondWith(http.StatusNotFound, response),
   140  					),
   141  				)
   142  			})
   143  
   144  			It("returns the error", func() {
   145  				_, _, err := client.GetApplicationProcesses("some-app-guid")
   146  				Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{}))
   147  			})
   148  		})
   149  	})
   150  
   151  	Describe("GetApplicationProcessByType", func() {
   152  		var (
   153  			process  Process
   154  			warnings []string
   155  			err      error
   156  		)
   157  
   158  		JustBeforeEach(func() {
   159  			process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type")
   160  		})
   161  
   162  		Context("when the process exists", func() {
   163  			BeforeEach(func() {
   164  				response := `{
   165  					"guid": "process-1-guid",
   166  					"type": "some-type",
   167  					"memory_in_mb": 32,
   168  					"health_check": {
   169  						"type": "http",
   170  						"data": {
   171  							"timeout": 90,
   172  							"endpoint": "/health"
   173  						}
   174  					}
   175  				}`
   176  				server.AppendHandlers(
   177  					CombineHandlers(
   178  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   179  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   180  					),
   181  				)
   182  			})
   183  
   184  			It("returns the process and all warnings", func() {
   185  				Expect(err).NotTo(HaveOccurred())
   186  				Expect(warnings).To(ConsistOf("this is a warning"))
   187  				Expect(process).To(Equal(Process{
   188  					GUID:       "process-1-guid",
   189  					Type:       "some-type",
   190  					MemoryInMB: types.NullUint64{Value: 32, IsSet: true},
   191  					HealthCheck: ProcessHealthCheck{
   192  						Type: "http",
   193  						Data: ProcessHealthCheckData{Endpoint: "/health"}},
   194  				}))
   195  			})
   196  		})
   197  
   198  		Context("when the application does not exist", func() {
   199  			BeforeEach(func() {
   200  				response := `{
   201  					"errors": [
   202  						{
   203  							"detail": "Application not found",
   204  							"title": "CF-ResourceNotFound",
   205  							"code": 10010
   206  						}
   207  					]
   208  				}`
   209  				server.AppendHandlers(
   210  					CombineHandlers(
   211  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   212  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   213  					),
   214  				)
   215  			})
   216  
   217  			It("returns a ResourceNotFoundError", func() {
   218  				Expect(warnings).To(ConsistOf("this is a warning"))
   219  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"}))
   220  			})
   221  		})
   222  
   223  		Context("when the cloud controller returns errors and warnings", func() {
   224  			BeforeEach(func() {
   225  				response := `{
   226  					"errors": [
   227  						{
   228  							"code": 10008,
   229  							"detail": "The request is semantically invalid: command presence",
   230  							"title": "CF-UnprocessableEntity"
   231  						},
   232  						{
   233  							"code": 10009,
   234  							"detail": "Some CC Error",
   235  							"title": "CF-SomeNewError"
   236  						}
   237  					]
   238  				}`
   239  				server.AppendHandlers(
   240  					CombineHandlers(
   241  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"),
   242  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   243  					),
   244  				)
   245  			})
   246  
   247  			It("returns the error and all warnings", func() {
   248  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   249  					ResponseCode: http.StatusTeapot,
   250  					V3ErrorResponse: ccerror.V3ErrorResponse{
   251  						Errors: []ccerror.V3Error{
   252  							{
   253  								Code:   10008,
   254  								Detail: "The request is semantically invalid: command presence",
   255  								Title:  "CF-UnprocessableEntity",
   256  							},
   257  							{
   258  								Code:   10009,
   259  								Detail: "Some CC Error",
   260  								Title:  "CF-SomeNewError",
   261  							},
   262  						},
   263  					},
   264  				}))
   265  				Expect(warnings).To(ConsistOf("this is a warning"))
   266  			})
   267  		})
   268  	})
   269  
   270  	Describe("PatchApplicationProcessHealthCheck", func() {
   271  		var (
   272  			endpoint string
   273  
   274  			warnings []string
   275  			err      error
   276  		)
   277  
   278  		JustBeforeEach(func() {
   279  			warnings, err = client.PatchApplicationProcessHealthCheck("some-process-guid", "some-type", endpoint)
   280  		})
   281  
   282  		Context("when patching the process succeeds", func() {
   283  			Context("and the endpoint is non-empty", func() {
   284  				BeforeEach(func() {
   285  					endpoint = "some-endpoint"
   286  					expectedBody := `{
   287  					"health_check": {
   288  						"type": "some-type",
   289  						"data": {
   290  							"endpoint": "some-endpoint"
   291  						}
   292  					}
   293  				}`
   294  					server.AppendHandlers(
   295  						CombineHandlers(
   296  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   297  							VerifyJSON(expectedBody),
   298  							RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   299  						),
   300  					)
   301  				})
   302  
   303  				It("patches this process's health check", func() {
   304  					Expect(err).ToNot(HaveOccurred())
   305  					Expect(warnings).To(ConsistOf("this is a warning"))
   306  				})
   307  			})
   308  
   309  			Context("and the endpoint is empty", func() {
   310  				BeforeEach(func() {
   311  					endpoint = ""
   312  					expectedBody := `{
   313  					"health_check": {
   314  						"type": "some-type",
   315  						"data": {
   316  							"endpoint": null
   317  						}
   318  					}
   319  				}`
   320  					server.AppendHandlers(
   321  						CombineHandlers(
   322  							VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   323  							VerifyJSON(expectedBody),
   324  							RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   325  						),
   326  					)
   327  				})
   328  
   329  				It("patches this process's health check", func() {
   330  					Expect(err).ToNot(HaveOccurred())
   331  					Expect(warnings).To(ConsistOf("this is a warning"))
   332  				})
   333  			})
   334  		})
   335  
   336  		Context("when the process does not exist", func() {
   337  			BeforeEach(func() {
   338  				endpoint = "some-endpoint"
   339  				response := `{
   340  					"errors": [
   341  						{
   342  							"detail": "Process not found",
   343  							"title": "CF-ResourceNotFound",
   344  							"code": 10010
   345  						}
   346  					]
   347  				}`
   348  
   349  				server.AppendHandlers(
   350  					CombineHandlers(
   351  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   352  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   353  					),
   354  				)
   355  			})
   356  
   357  			It("returns an error and warnings", func() {
   358  				Expect(err).To(MatchError(ccerror.ProcessNotFoundError{}))
   359  				Expect(warnings).To(ConsistOf("this is a warning"))
   360  			})
   361  		})
   362  
   363  		Context("when the cloud controller returns errors and warnings", func() {
   364  			BeforeEach(func() {
   365  				endpoint = "some-endpoint"
   366  				response := `{
   367  						"errors": [
   368  							{
   369  								"code": 10008,
   370  								"detail": "The request is semantically invalid: command presence",
   371  								"title": "CF-UnprocessableEntity"
   372  							},
   373  							{
   374  								"code": 10009,
   375  								"detail": "Some CC Error",
   376  								"title": "CF-SomeNewError"
   377  							}
   378  						]
   379  					}`
   380  				server.AppendHandlers(
   381  					CombineHandlers(
   382  						VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"),
   383  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   384  					),
   385  				)
   386  			})
   387  
   388  			It("returns the error and all warnings", func() {
   389  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   390  					ResponseCode: http.StatusTeapot,
   391  					V3ErrorResponse: ccerror.V3ErrorResponse{
   392  						Errors: []ccerror.V3Error{
   393  							{
   394  								Code:   10008,
   395  								Detail: "The request is semantically invalid: command presence",
   396  								Title:  "CF-UnprocessableEntity",
   397  							},
   398  							{
   399  								Code:   10009,
   400  								Detail: "Some CC Error",
   401  								Title:  "CF-SomeNewError",
   402  							},
   403  						},
   404  					},
   405  				}))
   406  				Expect(warnings).To(ConsistOf("this is a warning"))
   407  			})
   408  		})
   409  	})
   410  
   411  	Describe("CreateApplicationProcessScale", func() {
   412  		var passedProcess Process
   413  
   414  		Context("when providing all scale options", func() {
   415  			BeforeEach(func() {
   416  				passedProcess = Process{
   417  					Type:       constant.ProcessTypeWeb,
   418  					Instances:  types.NullInt{Value: 2, IsSet: true},
   419  					MemoryInMB: types.NullUint64{Value: 100, IsSet: true},
   420  					DiskInMB:   types.NullUint64{Value: 200, IsSet: true},
   421  				}
   422  				expectedBody := `{
   423  					"instances": 2,
   424  					"memory_in_mb": 100,
   425  					"disk_in_mb": 200
   426  				}`
   427  				response := `{
   428  					"guid": "some-process-guid"
   429  				}`
   430  				server.AppendHandlers(
   431  					CombineHandlers(
   432  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   433  						VerifyJSON(expectedBody),
   434  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   435  					),
   436  				)
   437  			})
   438  
   439  			It("scales the application process; returns all warnings", func() {
   440  				warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   441  				Expect(err).ToNot(HaveOccurred())
   442  				Expect(warnings).To(ConsistOf("this is a warning"))
   443  			})
   444  		})
   445  
   446  		Context("when providing all scale options with 0 values", func() {
   447  			BeforeEach(func() {
   448  				passedProcess = Process{
   449  					Type:       constant.ProcessTypeWeb,
   450  					Instances:  types.NullInt{Value: 0, IsSet: true},
   451  					MemoryInMB: types.NullUint64{Value: 0, IsSet: true},
   452  					DiskInMB:   types.NullUint64{Value: 0, IsSet: true},
   453  				}
   454  				expectedBody := `{
   455  					"instances": 0,
   456  					"memory_in_mb": 0,
   457  					"disk_in_mb": 0
   458  				}`
   459  				response := `{
   460  					"guid": "some-process-guid"
   461  				}`
   462  				server.AppendHandlers(
   463  					CombineHandlers(
   464  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   465  						VerifyJSON(expectedBody),
   466  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   467  					),
   468  				)
   469  			})
   470  
   471  			It("scales the application process to 0 values; returns the scaled process and all warnings", func() {
   472  				warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   473  				Expect(err).ToNot(HaveOccurred())
   474  				Expect(warnings).To(ConsistOf("this is a warning"))
   475  			})
   476  		})
   477  
   478  		Context("when providing only one scale option", func() {
   479  			BeforeEach(func() {
   480  				passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   481  				expectedBody := `{
   482  					"instances": 2
   483  				}`
   484  				response := `{
   485  					"guid": "some-process-guid",
   486  				}`
   487  				server.AppendHandlers(
   488  					CombineHandlers(
   489  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   490  						VerifyJSON(expectedBody),
   491  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   492  					),
   493  				)
   494  			})
   495  
   496  			It("scales the application process; returns all warnings", func() {
   497  				warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   498  				Expect(err).ToNot(HaveOccurred())
   499  				Expect(warnings).To(ConsistOf("this is a warning"))
   500  			})
   501  		})
   502  
   503  		Context("when an error is encountered", func() {
   504  			BeforeEach(func() {
   505  				passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}}
   506  				response := `{
   507  						"errors": [
   508  							{
   509  								"code": 10008,
   510  								"detail": "The request is semantically invalid: command presence",
   511  								"title": "CF-UnprocessableEntity"
   512  							},
   513  							{
   514  								"code": 10009,
   515  								"detail": "Some CC Error",
   516  								"title": "CF-SomeNewError"
   517  							}
   518  						]
   519  					}`
   520  				server.AppendHandlers(
   521  					CombineHandlers(
   522  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"),
   523  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   524  					),
   525  				)
   526  			})
   527  
   528  			It("returns the error and all warnings", func() {
   529  				warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess)
   530  				Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{
   531  					ResponseCode: http.StatusTeapot,
   532  					V3ErrorResponse: ccerror.V3ErrorResponse{
   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  				}))
   547  				Expect(warnings).To(ConsistOf("this is a warning"))
   548  			})
   549  		})
   550  	})
   551  })