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 })