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