github.com/nimakaviani/cli@v6.37.1-0.20180619223813-e734901a73fa+incompatible/actor/pushaction/apply_test.go (about) 1 package pushaction_test 2 3 import ( 4 "errors" 5 "io/ioutil" 6 "os" 7 "time" 8 9 "code.cloudfoundry.org/cli/actor/actionerror" 10 . "code.cloudfoundry.org/cli/actor/pushaction" 11 "code.cloudfoundry.org/cli/actor/pushaction/pushactionfakes" 12 "code.cloudfoundry.org/cli/actor/v2action" 13 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 14 15 . "github.com/onsi/ginkgo" 16 . "github.com/onsi/gomega" 17 ) 18 19 func streamsDrainedAndClosed(configStream <-chan ApplicationConfig, eventStream <-chan Event, warningsStream <-chan Warnings, errorStream <-chan error) bool { 20 var configStreamClosed, eventStreamClosed, warningsStreamClosed, errorStreamClosed bool 21 for { 22 select { 23 case _, ok := <-configStream: 24 if !ok { 25 configStreamClosed = true 26 } 27 case _, ok := <-eventStream: 28 if !ok { 29 eventStreamClosed = true 30 } 31 case _, ok := <-warningsStream: 32 if !ok { 33 warningsStreamClosed = true 34 } 35 case _, ok := <-errorStream: 36 if !ok { 37 errorStreamClosed = true 38 } 39 } 40 if configStreamClosed && eventStreamClosed && warningsStreamClosed && errorStreamClosed { 41 break 42 } 43 } 44 return true 45 } 46 47 // TODO: for refactor: We can use the following style of code to validate that 48 // each event is received in a specific order 49 50 // Expect(nextEvent()).Should(Equal(SettingUpApplication)) 51 // Expect(nextEvent()).Should(Equal(CreatingApplication)) 52 // Expect(nextEvent()).Should(Equal(...)) 53 // Expect(nextEvent()).Should(Equal(...)) 54 // Expect(nextEvent()).Should(Equal(...)) 55 func setUpNextEvent(c <-chan ApplicationConfig, e <-chan Event, w <-chan Warnings) func() Event { 56 timeOut := time.Tick(500 * time.Millisecond) 57 58 return func() Event { 59 for { 60 select { 61 case <-c: 62 case event, ok := <-e: 63 if ok { 64 return event 65 } 66 return "" 67 case <-w: 68 case <-timeOut: 69 return "" 70 } 71 } 72 } 73 } 74 75 var _ = Describe("Apply", func() { 76 var ( 77 actor *Actor 78 fakeV2Actor *pushactionfakes.FakeV2Actor 79 fakeSharedActor *pushactionfakes.FakeSharedActor 80 81 config ApplicationConfig 82 fakeProgressBar *pushactionfakes.FakeProgressBar 83 84 eventStream <-chan Event 85 warningsStream <-chan Warnings 86 errorStream <-chan error 87 configStream <-chan ApplicationConfig 88 89 nextEvent func() Event 90 ) 91 92 BeforeEach(func() { 93 fakeV2Actor = new(pushactionfakes.FakeV2Actor) 94 fakeSharedActor = new(pushactionfakes.FakeSharedActor) 95 actor = NewActor(fakeV2Actor, nil, fakeSharedActor) 96 config = ApplicationConfig{ 97 DesiredApplication: Application{ 98 Application: v2action.Application{ 99 Name: "some-app-name", 100 SpaceGUID: "some-space-guid", 101 }}, 102 DesiredRoutes: []v2action.Route{{Host: "banana"}}, 103 } 104 fakeProgressBar = new(pushactionfakes.FakeProgressBar) 105 }) 106 107 JustBeforeEach(func() { 108 configStream, eventStream, warningsStream, errorStream = actor.Apply(config, fakeProgressBar) 109 110 nextEvent = setUpNextEvent(configStream, eventStream, warningsStream) 111 }) 112 113 AfterEach(func() { 114 Eventually(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).Should(BeTrue()) 115 }) 116 117 Context("when creating/updating the application is successful", func() { 118 var createdApp v2action.Application 119 120 BeforeEach(func() { 121 fakeV2Actor.CreateApplicationStub = func(application v2action.Application) (v2action.Application, v2action.Warnings, error) { 122 createdApp = application 123 createdApp.GUID = "some-app-guid" 124 125 return createdApp, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, nil 126 } 127 }) 128 129 JustBeforeEach(func() { 130 Eventually(eventStream).Should(Receive(Equal(SettingUpApplication))) 131 Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2"))) 132 Eventually(eventStream).Should(Receive(Equal(CreatedApplication))) 133 }) 134 135 Context("when the route creation is successful", func() { 136 var createdRoutes []v2action.Route 137 138 BeforeEach(func() { 139 createdRoutes = []v2action.Route{{Host: "banana", GUID: "some-route-guid"}} 140 fakeV2Actor.CreateRouteReturns(createdRoutes[0], v2action.Warnings{"create-route-warnings-1", "create-route-warnings-2"}, nil) 141 }) 142 143 JustBeforeEach(func() { 144 Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes))) 145 Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warnings-1", "create-route-warnings-2"))) 146 Eventually(eventStream).Should(Receive(Equal(CreatedRoutes))) 147 }) 148 149 Context("when mapping the routes is successful", func() { 150 var desiredServices map[string]v2action.ServiceInstance 151 152 BeforeEach(func() { 153 desiredServices = map[string]v2action.ServiceInstance{ 154 "service_1": {Name: "service_1", GUID: "service_guid"}, 155 } 156 config.DesiredServices = desiredServices 157 fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"map-route-warnings-1", "map-route-warnings-2"}, nil) 158 }) 159 160 JustBeforeEach(func() { 161 Eventually(warningsStream).Should(Receive(ConsistOf("map-route-warnings-1", "map-route-warnings-2"))) 162 Eventually(eventStream).Should(Receive(Equal(BoundRoutes))) 163 }) 164 165 Context("when service binding is successful", func() { 166 BeforeEach(func() { 167 fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warnings-1", "bind-service-warnings-2"}, nil) 168 }) 169 170 JustBeforeEach(func() { 171 Eventually(eventStream).Should(Receive(Equal(ConfiguringServices))) 172 Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warnings-1", "bind-service-warnings-2"))) 173 Eventually(eventStream).Should(Receive(Equal(BoundServices))) 174 }) 175 176 Context("when resource matching happens", func() { 177 BeforeEach(func() { 178 config.Path = "some-path" 179 }) 180 181 JustBeforeEach(func() { 182 Eventually(eventStream).Should(Receive(Equal(ResourceMatching))) 183 Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2"))) 184 }) 185 186 Context("when there is at least one resource that has not been matched", func() { 187 BeforeEach(func() { 188 fakeV2Actor.ResourceMatchReturns(nil, []v2action.Resource{{}}, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil) 189 }) 190 191 Context("when the archive creation is successful", func() { 192 var archivePath string 193 194 BeforeEach(func() { 195 tmpfile, err := ioutil.TempFile("", "fake-archive") 196 Expect(err).ToNot(HaveOccurred()) 197 _, err = tmpfile.Write([]byte("123456")) 198 Expect(err).ToNot(HaveOccurred()) 199 Expect(tmpfile.Close()).ToNot(HaveOccurred()) 200 201 archivePath = tmpfile.Name() 202 fakeSharedActor.ZipDirectoryResourcesReturns(archivePath, nil) 203 }) 204 205 JustBeforeEach(func() { 206 Eventually(eventStream).Should(Receive(Equal(CreatingArchive))) 207 }) 208 209 Context("when the upload is successful", func() { 210 BeforeEach(func() { 211 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 212 }) 213 214 JustBeforeEach(func() { 215 Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive))) 216 Eventually(eventStream).Should(Receive(Equal(UploadWithArchiveComplete))) 217 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 218 }) 219 220 It("sends the updated config and a complete event", func() { 221 Eventually(configStream).Should(Receive(Equal(ApplicationConfig{ 222 CurrentApplication: Application{Application: createdApp}, 223 CurrentRoutes: createdRoutes, 224 CurrentServices: desiredServices, 225 DesiredApplication: Application{Application: createdApp}, 226 DesiredRoutes: createdRoutes, 227 DesiredServices: desiredServices, 228 UnmatchedResources: []v2action.Resource{{}}, 229 Path: "some-path", 230 }))) 231 Eventually(eventStream).Should(Receive(Equal(Complete))) 232 233 Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1)) 234 }) 235 }) 236 }) 237 }) 238 239 Context("when all the resources have been matched", func() { 240 BeforeEach(func() { 241 fakeV2Actor.ResourceMatchReturns(nil, nil, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil) 242 }) 243 244 JustBeforeEach(func() { 245 Eventually(eventStream).Should(Receive(Equal(UploadingApplication))) 246 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 247 }) 248 249 Context("when the upload is successful", func() { 250 BeforeEach(func() { 251 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 252 }) 253 254 It("sends the updated config and a complete event", func() { 255 Eventually(configStream).Should(Receive(Equal(ApplicationConfig{ 256 CurrentApplication: Application{Application: createdApp}, 257 CurrentRoutes: createdRoutes, 258 CurrentServices: desiredServices, 259 DesiredApplication: Application{Application: createdApp}, 260 DesiredRoutes: createdRoutes, 261 DesiredServices: desiredServices, 262 Path: "some-path", 263 }))) 264 Eventually(eventStream).Should(Receive(Equal(Complete))) 265 266 Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1)) 267 _, _, reader, readerLength := fakeV2Actor.UploadApplicationPackageArgsForCall(0) 268 Expect(reader).To(BeNil()) 269 Expect(readerLength).To(BeNumerically("==", 0)) 270 }) 271 }) 272 }) 273 }) 274 275 Context("when a droplet is provided", func() { 276 var dropletPath string 277 278 BeforeEach(func() { 279 tmpfile, err := ioutil.TempFile("", "fake-droplet") 280 Expect(err).ToNot(HaveOccurred()) 281 _, err = tmpfile.Write([]byte("123456")) 282 Expect(err).ToNot(HaveOccurred()) 283 Expect(tmpfile.Close()).ToNot(HaveOccurred()) 284 285 dropletPath = tmpfile.Name() 286 config.DropletPath = dropletPath 287 }) 288 289 AfterEach(func() { 290 Expect(os.RemoveAll(dropletPath)).ToNot(HaveOccurred()) 291 }) 292 293 Context("when the upload is successful", func() { 294 BeforeEach(func() { 295 fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 296 }) 297 298 It("sends an updated config and a complete event", func() { 299 Eventually(eventStream).Should(Receive(Equal(UploadDropletComplete))) 300 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 301 Eventually(configStream).Should(Receive(Equal(ApplicationConfig{ 302 CurrentApplication: Application{Application: createdApp}, 303 CurrentRoutes: createdRoutes, 304 CurrentServices: desiredServices, 305 DesiredApplication: Application{Application: createdApp}, 306 DesiredRoutes: createdRoutes, 307 DesiredServices: desiredServices, 308 DropletPath: dropletPath, 309 }))) 310 311 Expect(fakeV2Actor.UploadDropletCallCount()).To(Equal(1)) 312 _, droplet, dropletLength := fakeV2Actor.UploadDropletArgsForCall(0) 313 Expect(droplet).To(BeNil()) 314 Expect(dropletLength).To(BeNumerically("==", 6)) 315 Expect(fakeSharedActor.ZipDirectoryResourcesCallCount()).To(Equal(0)) 316 }) 317 }) 318 }) 319 }) 320 }) 321 }) 322 }) 323 324 Context("when creating/updating errors", func() { 325 var expectedErr error 326 327 BeforeEach(func() { 328 expectedErr = errors.New("dios mio") 329 fakeV2Actor.CreateApplicationReturns(v2action.Application{}, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, expectedErr) 330 }) 331 332 It("sends warnings and errors, then stops", func() { 333 Eventually(eventStream).Should(Receive(Equal(SettingUpApplication))) 334 Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2"))) 335 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 336 Consistently(eventStream).ShouldNot(Receive()) 337 }) 338 }) 339 340 Describe("Routes", func() { 341 BeforeEach(func() { 342 config.DesiredRoutes = v2action.Routes{{GUID: "some-route-guid"}} 343 }) 344 345 Context("when NoRoutes is set", func() { 346 BeforeEach(func() { 347 config.NoRoute = true 348 }) 349 350 Context("when config has at least one route", func() { 351 BeforeEach(func() { 352 config.CurrentRoutes = []v2action.Route{{GUID: "some-route-guid-1"}} 353 }) 354 355 Context("when unmapping routes succeeds", func() { 356 BeforeEach(func() { 357 fakeV2Actor.UnmapRouteFromApplicationReturns(v2action.Warnings{"unmapping-route-warnings"}, nil) 358 }) 359 360 It("sends the UnmappingRoutes event and does not raise an error", func() { 361 Eventually(nextEvent).Should(Equal(UnmappingRoutes)) 362 Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings"))) 363 Eventually(nextEvent).Should(Equal(Complete)) 364 }) 365 }) 366 367 Context("when unmapping routes fails", func() { 368 BeforeEach(func() { 369 fakeV2Actor.UnmapRouteFromApplicationReturns(v2action.Warnings{"unmapping-route-warnings"}, errors.New("ohno")) 370 }) 371 372 It("sends the UnmappingRoutes event and raise an error", func() { 373 Eventually(nextEvent).Should(Equal(UnmappingRoutes)) 374 Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings"))) 375 Eventually(errorStream).Should(Receive(MatchError("ohno"))) 376 Consistently(nextEvent).ShouldNot(Equal(Complete)) 377 }) 378 }) 379 }) 380 381 Context("when config has no routes", func() { 382 BeforeEach(func() { 383 config.CurrentRoutes = nil 384 }) 385 386 It("should not send the UnmappingRoutes event", func() { 387 Consistently(nextEvent).ShouldNot(Equal(UnmappingRoutes)) 388 Consistently(errorStream).ShouldNot(Receive()) 389 390 Expect(fakeV2Actor.UnmapRouteFromApplicationCallCount()).To(Equal(0)) 391 }) 392 }) 393 }) 394 395 Context("when NoRoutes is NOT set", func() { 396 BeforeEach(func() { 397 config.NoRoute = false 398 }) 399 400 It("should send the CreatingAndMappingRoutes event", func() { 401 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 402 }) 403 404 Context("when no new routes are provided", func() { 405 BeforeEach(func() { 406 config.DesiredRoutes = nil 407 }) 408 409 It("should not send the CreatedRoutes event", func() { 410 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 411 Eventually(warningsStream).Should(Receive(BeEmpty())) 412 Consistently(nextEvent).ShouldNot(Equal(CreatedRoutes)) 413 }) 414 }) 415 416 Context("when new routes are provided", func() { 417 BeforeEach(func() { 418 config.DesiredRoutes = []v2action.Route{{}} 419 }) 420 421 Context("when route creation fails", func() { 422 BeforeEach(func() { 423 fakeV2Actor.CreateRouteReturns(v2action.Route{}, v2action.Warnings{"create-route-warning"}, errors.New("ohno")) 424 }) 425 426 It("raise an error", func() { 427 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 428 Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warning"))) 429 Eventually(errorStream).Should(Receive(MatchError("ohno"))) 430 Consistently(nextEvent).ShouldNot(EqualEither(CreatedRoutes, Complete)) 431 }) 432 }) 433 434 Context("when route creation succeeds", func() { 435 BeforeEach(func() { 436 fakeV2Actor.CreateRouteReturns(v2action.Route{}, v2action.Warnings{"create-route-warning"}, nil) 437 }) 438 439 It("should send the CreatedRoutes event", func() { 440 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 441 Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warning"))) 442 Expect(nextEvent()).To(Equal(CreatedRoutes)) 443 }) 444 }) 445 }) 446 447 Context("when there are no routes to map", func() { 448 BeforeEach(func() { 449 config.CurrentRoutes = config.DesiredRoutes 450 }) 451 452 It("should not send the BoundRoutes event", func() { 453 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 454 455 // First warning picks up CreatedRoute warnings, second one picks up 456 // MapRoute warnings. No easy way to improve this today 457 Eventually(warningsStream).Should(Receive()) 458 Eventually(warningsStream).Should(Receive()) 459 Consistently(nextEvent).ShouldNot(Equal(BoundRoutes)) 460 }) 461 }) 462 463 Context("when there are routes to map", func() { 464 BeforeEach(func() { 465 config.DesiredRoutes = []v2action.Route{{GUID: "new-guid"}} 466 }) 467 468 Context("when binding the route fails", func() { 469 BeforeEach(func() { 470 fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"bind-route-warning"}, errors.New("ohno")) 471 }) 472 473 It("raise an error", func() { 474 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 475 Eventually(warningsStream).Should(Receive(ConsistOf("bind-route-warning"))) 476 Eventually(errorStream).Should(Receive(MatchError("ohno"))) 477 Consistently(nextEvent).ShouldNot(EqualEither(BoundRoutes, Complete)) 478 }) 479 }) 480 481 Context("when binding the route succeeds", func() { 482 BeforeEach(func() { 483 fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"bind-route-warning"}, nil) 484 }) 485 486 It("should send the BoundRoutes event", func() { 487 Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes)) 488 Eventually(warningsStream).Should(Receive(ConsistOf("bind-route-warning"))) 489 Expect(nextEvent()).To(Equal(BoundRoutes)) 490 }) 491 }) 492 }) 493 }) 494 }) 495 496 Describe("Services", func() { 497 var ( 498 service1 v2action.ServiceInstance 499 service2 v2action.ServiceInstance 500 ) 501 502 BeforeEach(func() { 503 service1 = v2action.ServiceInstance{Name: "service_1", GUID: "service_1_guid"} 504 service2 = v2action.ServiceInstance{Name: "service_2", GUID: "service_2_guid"} 505 }) 506 507 Context("when there are no new services", func() { 508 BeforeEach(func() { 509 config.CurrentServices = map[string]v2action.ServiceInstance{"service1": service1} 510 config.DesiredServices = map[string]v2action.ServiceInstance{"service1": service1} 511 }) 512 513 It("should not send the ConfiguringServices or BoundServices event", func() { 514 Consistently(nextEvent).ShouldNot(EqualEither(ConfiguringServices, BoundServices)) 515 }) 516 }) 517 518 Context("when are new services", func() { 519 BeforeEach(func() { 520 config.CurrentServices = map[string]v2action.ServiceInstance{"service1": service1} 521 config.DesiredServices = map[string]v2action.ServiceInstance{"service1": service1, "service2": service2} 522 }) 523 524 Context("when binding services fails", func() { 525 BeforeEach(func() { 526 fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warning"}, errors.New("ohno")) 527 }) 528 529 It("raises an error", func() { 530 Eventually(nextEvent).Should(Equal(ConfiguringServices)) 531 Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warning"))) 532 Eventually(errorStream).Should(Receive(MatchError("ohno"))) 533 Consistently(nextEvent).ShouldNot(EqualEither(BoundServices, Complete)) 534 }) 535 }) 536 537 Context("when binding services suceeds", func() { 538 BeforeEach(func() { 539 fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warning"}, nil) 540 }) 541 542 It("sends the ConfiguringServices and BoundServices events", func() { 543 Eventually(nextEvent).Should(Equal(ConfiguringServices)) 544 Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warning"))) 545 Expect(nextEvent()).To(Equal(BoundServices)) 546 }) 547 }) 548 }) 549 }) 550 551 Describe("Upload", func() { 552 Context("when a droplet is provided", func() { 553 var dropletPath string 554 555 BeforeEach(func() { 556 tmpfile, err := ioutil.TempFile("", "fake-droplet") 557 Expect(err).ToNot(HaveOccurred()) 558 _, err = tmpfile.Write([]byte("123456")) 559 Expect(err).ToNot(HaveOccurred()) 560 Expect(tmpfile.Close()).ToNot(HaveOccurred()) 561 562 dropletPath = tmpfile.Name() 563 config.DropletPath = dropletPath 564 }) 565 566 AfterEach(func() { 567 Expect(os.RemoveAll(dropletPath)).ToNot(HaveOccurred()) 568 }) 569 570 Context("when uploading the droplet fails", func() { 571 Context("when the error is a retryable error", func() { 572 var someErr error 573 BeforeEach(func() { 574 someErr = errors.New("I AM A BANANA") 575 fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"droplet-upload-warning"}, ccerror.PipeSeekError{Err: someErr}) 576 }) 577 578 It("should send a RetryUpload event and retry uploading up to 3x", func() { 579 Eventually(nextEvent).Should(Equal(UploadingDroplet)) 580 Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning"))) 581 Expect(nextEvent()).To(Equal(RetryUpload)) 582 583 Expect(nextEvent()).To(Equal(UploadingDroplet)) 584 Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning"))) 585 Expect(nextEvent()).To(Equal(RetryUpload)) 586 587 Expect(nextEvent()).To(Equal(UploadingDroplet)) 588 Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning"))) 589 Expect(nextEvent()).To(Equal(RetryUpload)) 590 591 Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadDropletComplete, Complete)) 592 Eventually(fakeV2Actor.UploadDropletCallCount()).Should(Equal(3)) 593 Expect(errorStream).To(Receive(MatchError(actionerror.UploadFailedError{Err: someErr}))) 594 }) 595 }) 596 597 Context("when the error is not a retryable error", func() { 598 BeforeEach(func() { 599 fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"droplet-upload-warning"}, errors.New("ohnos")) 600 }) 601 602 It("raises an error", func() { 603 Eventually(nextEvent).Should(Equal(UploadingDroplet)) 604 Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning"))) 605 Eventually(errorStream).Should(Receive(MatchError("ohnos"))) 606 607 Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadDropletComplete, Complete)) 608 }) 609 }) 610 }) 611 612 Context("when uploading the droplet is successful", func() { 613 BeforeEach(func() { 614 fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"droplet-upload-warning"}, nil) 615 }) 616 617 It("sends the UploadingDroplet event", func() { 618 Eventually(nextEvent).Should(Equal(UploadingDroplet)) 619 Expect(nextEvent()).To(Equal(UploadDropletComplete)) 620 Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning"))) 621 }) 622 }) 623 }) 624 625 Context("when app bits are provided", func() { 626 Context("when there is at least one unmatched resource", func() { 627 BeforeEach(func() { 628 fakeV2Actor.ResourceMatchReturns(nil, []v2action.Resource{{}}, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil) 629 }) 630 631 It("returns resource match warnings", func() { 632 Eventually(nextEvent).Should(Equal(ResourceMatching)) 633 Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2"))) 634 }) 635 636 Context("when creating the archive is successful", func() { 637 var archivePath string 638 639 BeforeEach(func() { 640 tmpfile, err := ioutil.TempFile("", "fake-archive") 641 Expect(err).ToNot(HaveOccurred()) 642 _, err = tmpfile.Write([]byte("123456")) 643 Expect(err).ToNot(HaveOccurred()) 644 Expect(tmpfile.Close()).ToNot(HaveOccurred()) 645 646 archivePath = tmpfile.Name() 647 fakeSharedActor.ZipDirectoryResourcesReturns(archivePath, nil) 648 }) 649 650 It("sends a CreatingArchive event", func() { 651 Eventually(nextEvent).Should(Equal(CreatingArchive)) 652 }) 653 654 Context("when the upload is successful", func() { 655 BeforeEach(func() { 656 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 657 }) 658 659 It("sends a UploadingApplicationWithArchive event", func() { 660 Eventually(nextEvent).Should(Equal(UploadingApplicationWithArchive)) 661 Expect(nextEvent()).To(Equal(UploadWithArchiveComplete)) 662 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 663 }) 664 }) 665 666 Context("when the upload fails", func() { 667 Context("when the upload error is a retryable error", func() { 668 var someErr error 669 670 BeforeEach(func() { 671 someErr = errors.New("I AM A BANANA") 672 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, ccerror.PipeSeekError{Err: someErr}) 673 }) 674 675 It("should send a RetryUpload event and retry uploading", func() { 676 Eventually(nextEvent).Should(Equal(UploadingApplicationWithArchive)) 677 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 678 Expect(nextEvent()).To(Equal(RetryUpload)) 679 680 Expect(nextEvent()).To(Equal(UploadingApplicationWithArchive)) 681 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 682 Expect(nextEvent()).To(Equal(RetryUpload)) 683 684 Expect(nextEvent()).To(Equal(UploadingApplicationWithArchive)) 685 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 686 Expect(nextEvent()).To(Equal(RetryUpload)) 687 688 Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadWithArchiveComplete, Complete)) 689 Eventually(fakeV2Actor.UploadApplicationPackageCallCount()).Should(Equal(3)) 690 Expect(errorStream).To(Receive(MatchError(actionerror.UploadFailedError{Err: someErr}))) 691 }) 692 693 }) 694 695 Context("when the upload error is not a retryable error", func() { 696 BeforeEach(func() { 697 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, errors.New("dios mio")) 698 }) 699 700 It("sends warnings and errors, then stops", func() { 701 Eventually(nextEvent).Should(Equal(UploadingApplicationWithArchive)) 702 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 703 Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadWithArchiveComplete, Complete)) 704 Eventually(errorStream).Should(Receive(MatchError("dios mio"))) 705 }) 706 }) 707 }) 708 }) 709 710 Context("when creating the archive fails", func() { 711 BeforeEach(func() { 712 fakeSharedActor.ZipDirectoryResourcesReturns("", errors.New("some-error")) 713 }) 714 715 It("raises an error", func() { 716 Consistently(nextEvent).ShouldNot(Equal(Complete)) 717 Eventually(errorStream).Should(Receive(MatchError("some-error"))) 718 }) 719 }) 720 }) 721 722 Context("when all resources have been matched", func() { 723 BeforeEach(func() { 724 fakeV2Actor.ResourceMatchReturns(nil, nil, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil) 725 }) 726 727 It("sends the UploadingApplication event", func() { 728 Eventually(nextEvent).Should(Equal(ResourceMatching)) 729 Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2"))) 730 Expect(nextEvent()).To(Equal(UploadingApplication)) 731 }) 732 733 Context("when the upload is successful", func() { 734 BeforeEach(func() { 735 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 736 }) 737 738 It("uploads the application and completes", func() { 739 Eventually(nextEvent).Should(Equal(UploadingApplication)) 740 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 741 Expect(nextEvent()).To(Equal(Complete)) 742 }) 743 }) 744 745 Context("when the upload fails", func() { 746 BeforeEach(func() { 747 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, errors.New("some-upload-error")) 748 }) 749 750 It("returns an error", func() { 751 Eventually(nextEvent).Should(Equal(UploadingApplication)) 752 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 753 Eventually(errorStream).Should(Receive(MatchError("some-upload-error"))) 754 Consistently(nextEvent).ShouldNot(Equal(Complete)) 755 }) 756 }) 757 }) 758 }) 759 760 Context("when a docker image is provided", func() { 761 BeforeEach(func() { 762 config.DesiredApplication.DockerImage = "hi-im-a-ge" 763 764 fakeV2Actor.CreateApplicationReturns(config.DesiredApplication.Application, nil, nil) 765 }) 766 767 It("should skip uploading anything", func() { 768 Consistently(nextEvent).ShouldNot(EqualEither(UploadingDroplet, UploadingApplication)) 769 }) 770 }) 771 }) 772 })