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