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