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