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