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