github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+incompatible/api/cloudcontroller/ccv2/job_test.go (about)

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