github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/api/cloudcontroller/ccv3/job_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"time"
     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  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/ginkgo/extensions/table"
    13  	. "github.com/onsi/gomega"
    14  	. "github.com/onsi/gomega/ghttp"
    15  )
    16  
    17  var _ = Describe("Job", func() {
    18  	var client *Client
    19  
    20  	Describe("Job", func() {
    21  		DescribeTable("Complete",
    22  			func(status constant.JobState, expected bool) {
    23  				job := Job{State: status}
    24  				Expect(job.Complete()).To(Equal(expected))
    25  			},
    26  
    27  			Entry("when failed, it returns false", constant.JobFailed, false),
    28  			Entry("when complete, it returns true", constant.JobComplete, true),
    29  			Entry("when processing, it returns false", constant.JobProcessing, false),
    30  		)
    31  
    32  		DescribeTable("Failed",
    33  			func(status constant.JobState, expected bool) {
    34  				job := Job{State: status}
    35  				Expect(job.Failed()).To(Equal(expected))
    36  			},
    37  
    38  			Entry("when failed, it returns true", constant.JobFailed, true),
    39  			Entry("when complete, it returns false", constant.JobComplete, false),
    40  			Entry("when processing, it returns false", constant.JobProcessing, false),
    41  		)
    42  	})
    43  
    44  	Describe("GetJob", func() {
    45  		var jobLocation string
    46  
    47  		BeforeEach(func() {
    48  			client = NewTestClient()
    49  			jobLocation = fmt.Sprintf("%s/some-job-location", server.URL())
    50  		})
    51  
    52  		Context("when no errors are encountered", func() {
    53  			BeforeEach(func() {
    54  				jsonResponse := `{
    55  						"guid": "job-guid",
    56  						"created_at": "2016-06-08T16:41:27Z",
    57  						"updated_at": "2016-06-08T16:41:27Z",
    58  						"operation": "app.delete",
    59  						"state": "PROCESSING",
    60  						"links": {
    61  							"self": {
    62  								"href": "/v3/jobs/job-guid"
    63  							}
    64  						}
    65  					}
    66  				}`
    67  
    68  				server.AppendHandlers(
    69  					CombineHandlers(
    70  						VerifyRequest(http.MethodGet, "/some-job-location"),
    71  						RespondWith(http.StatusOK, jsonResponse, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}}),
    72  					))
    73  			})
    74  
    75  			It("returns job with all warnings", func() {
    76  				job, warnings, err := client.GetJob(jobLocation)
    77  
    78  				Expect(err).NotTo(HaveOccurred())
    79  				Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"}))
    80  				Expect(job.GUID).To(Equal("job-guid"))
    81  				Expect(job.State).To(Equal(constant.JobProcessing))
    82  			})
    83  		})
    84  
    85  		Context("when the job fails", func() {
    86  			BeforeEach(func() {
    87  				jsonResponse := `{
    88  						"guid": "job-guid",
    89  						"created_at": "2016-06-08T16:41:27Z",
    90  						"updated_at": "2016-06-08T16:41:27Z",
    91  						"operation": "delete",
    92  						"state": "FAILED",
    93  						"errors": [
    94  							{
    95  								"detail": "blah blah",
    96  								"title": "CF-JobFail",
    97  								"code": 1234
    98  							}
    99  						],
   100  						"links": {
   101  							"self": {
   102  								"href": "/v3/jobs/job-guid"
   103  							}
   104  						}
   105  					}
   106  				}`
   107  
   108  				server.AppendHandlers(
   109  					CombineHandlers(
   110  						VerifyRequest(http.MethodGet, "/some-job-location"),
   111  						RespondWith(http.StatusOK, jsonResponse, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}}),
   112  					))
   113  			})
   114  
   115  			It("returns job with all warnings", func() {
   116  				job, warnings, err := client.GetJob(jobLocation)
   117  
   118  				Expect(err).NotTo(HaveOccurred())
   119  				Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"}))
   120  				Expect(job.GUID).To(Equal("job-guid"))
   121  				Expect(job.State).To(Equal(constant.JobFailed))
   122  				Expect(job.Errors[0].Detail).To(Equal("blah blah"))
   123  				Expect(job.Errors[0].Title).To(Equal("CF-JobFail"))
   124  				Expect(job.Errors[0].Code).To(Equal(1234))
   125  			})
   126  		})
   127  	})
   128  
   129  	Describe("PollJob", func() {
   130  		var jobLocation string
   131  
   132  		BeforeEach(func() {
   133  			jobLocation = fmt.Sprintf("%s/some-job-location", server.URL())
   134  			client = NewTestClient(Config{JobPollingTimeout: time.Minute})
   135  		})
   136  
   137  		Context("when the job starts queued and then finishes successfully", func() {
   138  			BeforeEach(func() {
   139  				server.AppendHandlers(
   140  					CombineHandlers(
   141  						VerifyRequest(http.MethodGet, "/some-job-location"),
   142  						RespondWith(http.StatusAccepted, `{
   143  							"guid": "job-guid",
   144  							"created_at": "2016-06-08T16:41:27Z",
   145  							"updated_at": "2016-06-08T16:41:27Z",
   146  							"operation": "app.delete",
   147  							"state": "PROCESSING",
   148  							"links": {
   149  								"self": {
   150  									"href": "/v3/jobs/job-guid"
   151  								}
   152  							}
   153  						}`, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   154  					))
   155  
   156  				server.AppendHandlers(
   157  					CombineHandlers(
   158  						VerifyRequest(http.MethodGet, "/some-job-location"),
   159  						RespondWith(http.StatusAccepted, `{
   160  							"guid": "job-guid",
   161  							"created_at": "2016-06-08T16:41:27Z",
   162  							"updated_at": "2016-06-08T16:41:27Z",
   163  							"operation": "app.delete",
   164  							"state": "PROCESSING",
   165  							"links": {
   166  								"self": {
   167  									"href": "/v3/jobs/job-guid"
   168  								}
   169  							}
   170  						}`, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   171  					))
   172  
   173  				server.AppendHandlers(
   174  					CombineHandlers(
   175  						VerifyRequest(http.MethodGet, "/some-job-location"),
   176  						RespondWith(http.StatusAccepted, `{
   177  							"guid": "job-guid",
   178  							"created_at": "2016-06-08T16:41:27Z",
   179  							"updated_at": "2016-06-08T16:41:27Z",
   180  							"operation": "app.delete",
   181  							"state": "COMPLETE",
   182  							"links": {
   183  								"self": {
   184  									"href": "/v3/jobs/job-guid"
   185  								}
   186  							}
   187  						}`, http.Header{"X-Cf-Warnings": {"warning-3, warning-4"}}),
   188  					))
   189  			})
   190  
   191  			It("should poll until completion", func() {
   192  				warnings, err := client.PollJob(jobLocation)
   193  				Expect(err).ToNot(HaveOccurred())
   194  				Expect(warnings).To(ConsistOf("warning-1", "warning-2", "warning-3", "warning-4"))
   195  			})
   196  		})
   197  
   198  		Context("when the job starts queued and then fails", func() {
   199  			var jobFailureMessage string
   200  			BeforeEach(func() {
   201  				jobFailureMessage = "I am a banana!!!"
   202  
   203  				server.AppendHandlers(
   204  					CombineHandlers(
   205  						VerifyRequest(http.MethodGet, "/some-job-location"),
   206  						RespondWith(http.StatusAccepted, `{
   207  							"guid": "job-guid",
   208  							"created_at": "2016-06-08T16:41:27Z",
   209  							"updated_at": "2016-06-08T16:41:27Z",
   210  							"operation": "app.delete",
   211  							"state": "PROCESSING",
   212  							"links": {
   213  								"self": {
   214  									"href": "/v3/jobs/job-guid"
   215  								}
   216  							}
   217  						}`, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   218  					))
   219  
   220  				server.AppendHandlers(
   221  					CombineHandlers(
   222  						VerifyRequest(http.MethodGet, "/some-job-location"),
   223  						RespondWith(http.StatusAccepted, `{
   224  							"guid": "job-guid",
   225  							"created_at": "2016-06-08T16:41:27Z",
   226  							"updated_at": "2016-06-08T16:41:27Z",
   227  							"operation": "app.delete",
   228  							"state": "PROCESSING",
   229  							"links": {
   230  								"self": {
   231  									"href": "/v3/jobs/job-guid"
   232  								}
   233  							}
   234  						}`, http.Header{"X-Cf-Warnings": {"warning-2, warning-3"}}),
   235  					))
   236  
   237  				server.AppendHandlers(
   238  					CombineHandlers(
   239  						VerifyRequest(http.MethodGet, "/some-job-location"),
   240  						RespondWith(http.StatusOK, fmt.Sprintf(`{
   241  							"guid": "job-guid",
   242  							"created_at": "2016-06-08T16:41:27Z",
   243  							"updated_at": "2016-06-08T16:41:27Z",
   244  							"operation": "app.delete",
   245  							"state": "FAILED",
   246  							"errors": [ {
   247  								"detail": "%s",
   248  								"title": "CF-AppBitsUploadInvalid",
   249  								"code": 160001
   250  							} ],
   251  							"links": {
   252  								"self": {
   253  									"href": "/v3/jobs/job-guid"
   254  								}
   255  							}
   256  						}`, jobFailureMessage), http.Header{"X-Cf-Warnings": {"warning-4"}}),
   257  					))
   258  			})
   259  
   260  			It("returns a JobFailedError", func() {
   261  				warnings, err := client.PollJob(jobLocation)
   262  				Expect(err).To(MatchError(ccerror.JobFailedError{
   263  					JobGUID: "job-guid",
   264  					Message: jobFailureMessage,
   265  				}))
   266  				Expect(warnings).To(ConsistOf("warning-1", "warning-2", "warning-3", "warning-4"))
   267  			})
   268  		})
   269  
   270  		Context("when retrieving the job errors", func() {
   271  			BeforeEach(func() {
   272  				server.AppendHandlers(
   273  					CombineHandlers(
   274  						VerifyRequest(http.MethodGet, "/some-job-location"),
   275  						RespondWith(http.StatusAccepted, `{
   276  							INVALID YAML
   277  						}`, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}}),
   278  					))
   279  			})
   280  
   281  			It("returns the CC error", func() {
   282  				warnings, err := client.PollJob(jobLocation)
   283  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   284  				Expect(err.Error()).To(MatchRegexp("invalid character"))
   285  			})
   286  		})
   287  
   288  		Describe("JobPollingTimeout", func() {
   289  			Context("when the job runs longer than the OverallPollingTimeout", func() {
   290  				var jobPollingTimeout time.Duration
   291  
   292  				BeforeEach(func() {
   293  					jobPollingTimeout = 100 * time.Millisecond
   294  					client = NewTestClient(Config{
   295  						JobPollingTimeout:  jobPollingTimeout,
   296  						JobPollingInterval: 60 * time.Millisecond,
   297  					})
   298  
   299  					server.AppendHandlers(
   300  						CombineHandlers(
   301  							VerifyRequest(http.MethodGet, "/some-job-location"),
   302  							RespondWith(http.StatusAccepted, `{
   303  							"guid": "job-guid",
   304  							"created_at": "2016-06-08T16:41:27Z",
   305  							"updated_at": "2016-06-08T16:41:27Z",
   306  							"operation": "app.delete",
   307  							"state": "PROCESSING",
   308  							"links": {
   309  								"self": {
   310  									"href": "/v3/jobs/job-guid"
   311  								}
   312  							}
   313  						}`, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   314  						))
   315  
   316  					server.AppendHandlers(
   317  						CombineHandlers(
   318  							VerifyRequest(http.MethodGet, "/some-job-location"),
   319  							RespondWith(http.StatusAccepted, `{
   320  							"guid": "job-guid",
   321  							"created_at": "2016-06-08T16:41:27Z",
   322  							"updated_at": "2016-06-08T16:41:27Z",
   323  							"operation": "app.delete",
   324  							"state": "PROCESSING",
   325  							"links": {
   326  								"self": {
   327  									"href": "/v3/jobs/job-guid"
   328  								}
   329  							}
   330  						}`, http.Header{"X-Cf-Warnings": {"warning-2, warning-3"}}),
   331  						))
   332  
   333  					server.AppendHandlers(
   334  						CombineHandlers(
   335  							VerifyRequest(http.MethodGet, "/some-job-location"),
   336  							RespondWith(http.StatusAccepted, `{
   337  							"guid": "job-guid",
   338  							"created_at": "2016-06-08T16:41:27Z",
   339  							"updated_at": "2016-06-08T16:41:27Z",
   340  							"operation": "app.delete",
   341  							"state": "FINISHED",
   342  							"links": {
   343  								"self": {
   344  									"href": "/v3/jobs/job-guid"
   345  								}
   346  							}
   347  						}`, http.Header{"X-Cf-Warnings": {"warning-4"}}),
   348  						))
   349  				})
   350  
   351  				It("raises a JobTimeoutError", func() {
   352  					_, err := client.PollJob(jobLocation)
   353  
   354  					Expect(err).To(MatchError(ccerror.JobTimeoutError{
   355  						Timeout: jobPollingTimeout,
   356  						JobGUID: "job-guid",
   357  					}))
   358  				})
   359  
   360  				// Fuzzy test to ensure that the overall function time isn't [far]
   361  				// greater than the OverallPollingTimeout. Since this is partially
   362  				// dependent on the speed of the system, the expectation is that the
   363  				// function *should* never exceed three times the timeout.
   364  				It("does not run [too much] longer than the timeout", func() {
   365  					startTime := time.Now()
   366  					_, err := client.PollJob(jobLocation)
   367  					endTime := time.Now()
   368  					Expect(err).To(HaveOccurred())
   369  
   370  					// If the jobPollingTimeout is less than the PollingInterval,
   371  					// then the margin may be too small, we should not allow the
   372  					// jobPollingTimeout to be set to less than the PollingInterval
   373  					Expect(endTime).To(BeTemporally("~", startTime, 3*jobPollingTimeout))
   374  				})
   375  			})
   376  		})
   377  	})
   378  })