github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+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  		actor, fakeV2Actor, _, fakeSharedActor = getTestPushActor()
    94  		config = ApplicationConfig{
    95  			DesiredApplication: Application{
    96  				Application: v2action.Application{
    97  					Name:      "some-app-name",
    98  					SpaceGUID: "some-space-guid",
    99  				}},
   100  			DesiredRoutes: []v2action.Route{{Host: "banana"}},
   101  		}
   102  		fakeProgressBar = new(pushactionfakes.FakeProgressBar)
   103  	})
   104  
   105  	JustBeforeEach(func() {
   106  		configStream, eventStream, warningsStream, errorStream = actor.Apply(config, fakeProgressBar)
   107  
   108  		nextEvent = setUpNextEvent(configStream, eventStream, warningsStream)
   109  	})
   110  
   111  	AfterEach(func() {
   112  		Eventually(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).Should(BeTrue())
   113  	})
   114  
   115  	When("creating/updating the application is successful", func() {
   116  		var createdApp v2action.Application
   117  
   118  		BeforeEach(func() {
   119  			fakeV2Actor.CreateApplicationStub = func(application v2action.Application) (v2action.Application, v2action.Warnings, error) {
   120  				createdApp = application
   121  				createdApp.GUID = "some-app-guid"
   122  
   123  				return createdApp, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, nil
   124  			}
   125  		})
   126  
   127  		JustBeforeEach(func() {
   128  			Eventually(eventStream).Should(Receive(Equal(SettingUpApplication)))
   129  			Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2")))
   130  			Eventually(eventStream).Should(Receive(Equal(CreatedApplication)))
   131  		})
   132  
   133  		When("the route creation is successful", func() {
   134  			var createdRoutes []v2action.Route
   135  
   136  			BeforeEach(func() {
   137  				createdRoutes = []v2action.Route{{Host: "banana", GUID: "some-route-guid"}}
   138  				fakeV2Actor.CreateRouteReturns(createdRoutes[0], v2action.Warnings{"create-route-warnings-1", "create-route-warnings-2"}, nil)
   139  			})
   140  
   141  			JustBeforeEach(func() {
   142  				Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes)))
   143  				Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warnings-1", "create-route-warnings-2")))
   144  				Eventually(eventStream).Should(Receive(Equal(CreatedRoutes)))
   145  			})
   146  
   147  			When("mapping the routes is successful", func() {
   148  				var desiredServices map[string]v2action.ServiceInstance
   149  
   150  				BeforeEach(func() {
   151  					desiredServices = map[string]v2action.ServiceInstance{
   152  						"service_1": {Name: "service_1", GUID: "service_guid"},
   153  					}
   154  					config.DesiredServices = desiredServices
   155  					fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"map-route-warnings-1", "map-route-warnings-2"}, nil)
   156  				})
   157  
   158  				JustBeforeEach(func() {
   159  					Eventually(warningsStream).Should(Receive(ConsistOf("map-route-warnings-1", "map-route-warnings-2")))
   160  					Eventually(eventStream).Should(Receive(Equal(BoundRoutes)))
   161  				})
   162  
   163  				When("service binding is successful", func() {
   164  					BeforeEach(func() {
   165  						fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warnings-1", "bind-service-warnings-2"}, nil)
   166  					})
   167  
   168  					JustBeforeEach(func() {
   169  						Eventually(eventStream).Should(Receive(Equal(ConfiguringServices)))
   170  						Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warnings-1", "bind-service-warnings-2")))
   171  						Eventually(eventStream).Should(Receive(Equal(BoundServices)))
   172  					})
   173  
   174  					When("resource matching happens", func() {
   175  						BeforeEach(func() {
   176  							config.Path = "some-path"
   177  						})
   178  
   179  						JustBeforeEach(func() {
   180  							Eventually(eventStream).Should(Receive(Equal(ResourceMatching)))
   181  							Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2")))
   182  						})
   183  
   184  						When("there is at least one resource that has not been matched", func() {
   185  							BeforeEach(func() {
   186  								fakeV2Actor.ResourceMatchReturns(nil, []v2action.Resource{{}}, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil)
   187  							})
   188  
   189  							When("the archive creation is successful", func() {
   190  								var archivePath string
   191  
   192  								BeforeEach(func() {
   193  									tmpfile, err := ioutil.TempFile("", "fake-archive")
   194  									Expect(err).ToNot(HaveOccurred())
   195  									_, err = tmpfile.Write([]byte("123456"))
   196  									Expect(err).ToNot(HaveOccurred())
   197  									Expect(tmpfile.Close()).ToNot(HaveOccurred())
   198  
   199  									archivePath = tmpfile.Name()
   200  									fakeSharedActor.ZipDirectoryResourcesReturns(archivePath, nil)
   201  								})
   202  
   203  								JustBeforeEach(func() {
   204  									Eventually(eventStream).Should(Receive(Equal(CreatingArchive)))
   205  								})
   206  
   207  								When("the upload is successful", func() {
   208  									BeforeEach(func() {
   209  										fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil)
   210  									})
   211  
   212  									JustBeforeEach(func() {
   213  										Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive)))
   214  										Eventually(eventStream).Should(Receive(Equal(UploadWithArchiveComplete)))
   215  										Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   216  									})
   217  
   218  									It("sends the updated config and a complete event", func() {
   219  										Eventually(configStream).Should(Receive(Equal(ApplicationConfig{
   220  											CurrentApplication: Application{Application: createdApp},
   221  											CurrentRoutes:      createdRoutes,
   222  											CurrentServices:    desiredServices,
   223  											DesiredApplication: Application{Application: createdApp},
   224  											DesiredRoutes:      createdRoutes,
   225  											DesiredServices:    desiredServices,
   226  											UnmatchedResources: []v2action.Resource{{}},
   227  											Path:               "some-path",
   228  										})))
   229  										Eventually(eventStream).Should(Receive(Equal(Complete)))
   230  
   231  										Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1))
   232  									})
   233  								})
   234  							})
   235  						})
   236  
   237  						When("all the resources have been matched", func() {
   238  							BeforeEach(func() {
   239  								fakeV2Actor.ResourceMatchReturns(nil, nil, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil)
   240  							})
   241  
   242  							JustBeforeEach(func() {
   243  								Eventually(eventStream).Should(Receive(Equal(UploadingApplication)))
   244  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   245  							})
   246  
   247  							When("the upload is successful", func() {
   248  								BeforeEach(func() {
   249  									fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil)
   250  								})
   251  
   252  								It("sends the updated config and a complete event", func() {
   253  									Eventually(configStream).Should(Receive(Equal(ApplicationConfig{
   254  										CurrentApplication: Application{Application: createdApp},
   255  										CurrentRoutes:      createdRoutes,
   256  										CurrentServices:    desiredServices,
   257  										DesiredApplication: Application{Application: createdApp},
   258  										DesiredRoutes:      createdRoutes,
   259  										DesiredServices:    desiredServices,
   260  										Path:               "some-path",
   261  									})))
   262  									Eventually(eventStream).Should(Receive(Equal(Complete)))
   263  
   264  									Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1))
   265  									_, _, reader, readerLength := fakeV2Actor.UploadApplicationPackageArgsForCall(0)
   266  									Expect(reader).To(BeNil())
   267  									Expect(readerLength).To(BeNumerically("==", 0))
   268  								})
   269  							})
   270  						})
   271  					})
   272  
   273  					When("a droplet is provided", func() {
   274  						var dropletPath string
   275  
   276  						BeforeEach(func() {
   277  							tmpfile, err := ioutil.TempFile("", "fake-droplet")
   278  							Expect(err).ToNot(HaveOccurred())
   279  							_, err = tmpfile.Write([]byte("123456"))
   280  							Expect(err).ToNot(HaveOccurred())
   281  							Expect(tmpfile.Close()).ToNot(HaveOccurred())
   282  
   283  							dropletPath = tmpfile.Name()
   284  							config.DropletPath = dropletPath
   285  						})
   286  
   287  						AfterEach(func() {
   288  							Expect(os.RemoveAll(dropletPath)).ToNot(HaveOccurred())
   289  						})
   290  
   291  						When("the upload is successful", func() {
   292  							BeforeEach(func() {
   293  								fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil)
   294  							})
   295  
   296  							It("sends an updated config and a complete event", func() {
   297  								Eventually(eventStream).Should(Receive(Equal(UploadDropletComplete)))
   298  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   299  								Eventually(configStream).Should(Receive(Equal(ApplicationConfig{
   300  									CurrentApplication: Application{Application: createdApp},
   301  									CurrentRoutes:      createdRoutes,
   302  									CurrentServices:    desiredServices,
   303  									DesiredApplication: Application{Application: createdApp},
   304  									DesiredRoutes:      createdRoutes,
   305  									DesiredServices:    desiredServices,
   306  									DropletPath:        dropletPath,
   307  								})))
   308  
   309  								Expect(fakeV2Actor.UploadDropletCallCount()).To(Equal(1))
   310  								_, droplet, dropletLength := fakeV2Actor.UploadDropletArgsForCall(0)
   311  								Expect(droplet).To(BeNil())
   312  								Expect(dropletLength).To(BeNumerically("==", 6))
   313  								Expect(fakeSharedActor.ZipDirectoryResourcesCallCount()).To(Equal(0))
   314  							})
   315  						})
   316  					})
   317  				})
   318  			})
   319  		})
   320  	})
   321  
   322  	When("creating/updating errors", func() {
   323  		var expectedErr error
   324  
   325  		BeforeEach(func() {
   326  			expectedErr = errors.New("dios mio")
   327  			fakeV2Actor.CreateApplicationReturns(v2action.Application{}, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, expectedErr)
   328  		})
   329  
   330  		It("sends warnings and errors, then stops", func() {
   331  			Eventually(eventStream).Should(Receive(Equal(SettingUpApplication)))
   332  			Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2")))
   333  			Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   334  			Consistently(eventStream).ShouldNot(Receive())
   335  		})
   336  	})
   337  
   338  	Describe("Routes", func() {
   339  		BeforeEach(func() {
   340  			config.DesiredRoutes = v2action.Routes{{GUID: "some-route-guid"}}
   341  		})
   342  
   343  		When("NoRoutes is set", func() {
   344  			BeforeEach(func() {
   345  				config.NoRoute = true
   346  			})
   347  
   348  			When("config has at least one route", func() {
   349  				BeforeEach(func() {
   350  					config.CurrentRoutes = []v2action.Route{{GUID: "some-route-guid-1"}}
   351  				})
   352  
   353  				When("unmapping routes succeeds", func() {
   354  					BeforeEach(func() {
   355  						fakeV2Actor.UnmapRouteFromApplicationReturns(v2action.Warnings{"unmapping-route-warnings"}, nil)
   356  					})
   357  
   358  					It("sends the UnmappingRoutes event and does not raise an error", func() {
   359  						Eventually(nextEvent).Should(Equal(UnmappingRoutes))
   360  						Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings")))
   361  						Eventually(nextEvent).Should(Equal(Complete))
   362  					})
   363  				})
   364  
   365  				When("unmapping routes fails", func() {
   366  					BeforeEach(func() {
   367  						fakeV2Actor.UnmapRouteFromApplicationReturns(v2action.Warnings{"unmapping-route-warnings"}, errors.New("ohno"))
   368  					})
   369  
   370  					It("sends the UnmappingRoutes event and raise an error", func() {
   371  						Eventually(nextEvent).Should(Equal(UnmappingRoutes))
   372  						Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings")))
   373  						Eventually(errorStream).Should(Receive(MatchError("ohno")))
   374  						Consistently(nextEvent).ShouldNot(Equal(Complete))
   375  					})
   376  				})
   377  			})
   378  
   379  			When("config has no routes", func() {
   380  				BeforeEach(func() {
   381  					config.CurrentRoutes = nil
   382  				})
   383  
   384  				It("should not send the UnmappingRoutes event", func() {
   385  					Consistently(nextEvent).ShouldNot(Equal(UnmappingRoutes))
   386  					Consistently(errorStream).ShouldNot(Receive())
   387  
   388  					Expect(fakeV2Actor.UnmapRouteFromApplicationCallCount()).To(Equal(0))
   389  				})
   390  			})
   391  		})
   392  
   393  		When("NoRoutes is NOT set", func() {
   394  			BeforeEach(func() {
   395  				config.NoRoute = false
   396  			})
   397  
   398  			It("should send the CreatingAndMappingRoutes event", func() {
   399  				Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   400  			})
   401  
   402  			When("no new routes are provided", func() {
   403  				BeforeEach(func() {
   404  					config.DesiredRoutes = nil
   405  				})
   406  
   407  				It("should not send the CreatedRoutes event", func() {
   408  					Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   409  					Eventually(warningsStream).Should(Receive(BeEmpty()))
   410  					Consistently(nextEvent).ShouldNot(Equal(CreatedRoutes))
   411  				})
   412  			})
   413  
   414  			When("new routes are provided", func() {
   415  				BeforeEach(func() {
   416  					config.DesiredRoutes = []v2action.Route{{}}
   417  				})
   418  
   419  				When("route creation fails", func() {
   420  					BeforeEach(func() {
   421  						fakeV2Actor.CreateRouteReturns(v2action.Route{}, v2action.Warnings{"create-route-warning"}, errors.New("ohno"))
   422  					})
   423  
   424  					It("raise an error", func() {
   425  						Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   426  						Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warning")))
   427  						Eventually(errorStream).Should(Receive(MatchError("ohno")))
   428  						Consistently(nextEvent).ShouldNot(EqualEither(CreatedRoutes, Complete))
   429  					})
   430  				})
   431  
   432  				When("route creation succeeds", func() {
   433  					BeforeEach(func() {
   434  						fakeV2Actor.CreateRouteReturns(v2action.Route{}, v2action.Warnings{"create-route-warning"}, nil)
   435  					})
   436  
   437  					It("should send the CreatedRoutes event", func() {
   438  						Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   439  						Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warning")))
   440  						Expect(nextEvent()).To(Equal(CreatedRoutes))
   441  					})
   442  				})
   443  			})
   444  
   445  			When("there are no routes to map", func() {
   446  				BeforeEach(func() {
   447  					config.CurrentRoutes = config.DesiredRoutes
   448  				})
   449  
   450  				It("should not send the BoundRoutes event", func() {
   451  					Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   452  
   453  					// First warning picks up CreatedRoute warnings, second one picks up
   454  					// MapRoute warnings. No easy way to improve this today
   455  					Eventually(warningsStream).Should(Receive())
   456  					Eventually(warningsStream).Should(Receive())
   457  					Consistently(nextEvent).ShouldNot(Equal(BoundRoutes))
   458  				})
   459  			})
   460  
   461  			When("there are routes to map", func() {
   462  				BeforeEach(func() {
   463  					config.DesiredRoutes = []v2action.Route{{GUID: "new-guid"}}
   464  				})
   465  
   466  				When("binding the route fails", func() {
   467  					BeforeEach(func() {
   468  						fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"bind-route-warning"}, errors.New("ohno"))
   469  					})
   470  
   471  					It("raise an error", func() {
   472  						Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   473  						Eventually(warningsStream).Should(Receive(ConsistOf("bind-route-warning")))
   474  						Eventually(errorStream).Should(Receive(MatchError("ohno")))
   475  						Consistently(nextEvent).ShouldNot(EqualEither(BoundRoutes, Complete))
   476  					})
   477  				})
   478  
   479  				When("binding the route succeeds", func() {
   480  					BeforeEach(func() {
   481  						fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"bind-route-warning"}, nil)
   482  					})
   483  
   484  					It("should send the BoundRoutes event", func() {
   485  						Eventually(nextEvent).Should(Equal(CreatingAndMappingRoutes))
   486  						Eventually(warningsStream).Should(Receive(ConsistOf("bind-route-warning")))
   487  						Expect(nextEvent()).To(Equal(BoundRoutes))
   488  					})
   489  				})
   490  			})
   491  		})
   492  	})
   493  
   494  	Describe("Services", func() {
   495  		var (
   496  			service1 v2action.ServiceInstance
   497  			service2 v2action.ServiceInstance
   498  		)
   499  
   500  		BeforeEach(func() {
   501  			service1 = v2action.ServiceInstance{Name: "service_1", GUID: "service_1_guid"}
   502  			service2 = v2action.ServiceInstance{Name: "service_2", GUID: "service_2_guid"}
   503  		})
   504  
   505  		When("there are no new services", func() {
   506  			BeforeEach(func() {
   507  				config.CurrentServices = map[string]v2action.ServiceInstance{"service1": service1}
   508  				config.DesiredServices = map[string]v2action.ServiceInstance{"service1": service1}
   509  			})
   510  
   511  			It("should not send the ConfiguringServices or BoundServices event", func() {
   512  				Consistently(nextEvent).ShouldNot(EqualEither(ConfiguringServices, BoundServices))
   513  			})
   514  		})
   515  
   516  		When("are new services", func() {
   517  			BeforeEach(func() {
   518  				config.CurrentServices = map[string]v2action.ServiceInstance{"service1": service1}
   519  				config.DesiredServices = map[string]v2action.ServiceInstance{"service1": service1, "service2": service2}
   520  			})
   521  
   522  			When("binding services fails", func() {
   523  				BeforeEach(func() {
   524  					fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warning"}, errors.New("ohno"))
   525  				})
   526  
   527  				It("raises an error", func() {
   528  					Eventually(nextEvent).Should(Equal(ConfiguringServices))
   529  					Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warning")))
   530  					Eventually(errorStream).Should(Receive(MatchError("ohno")))
   531  					Consistently(nextEvent).ShouldNot(EqualEither(BoundServices, Complete))
   532  				})
   533  			})
   534  
   535  			When("binding services suceeds", func() {
   536  				BeforeEach(func() {
   537  					fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warning"}, nil)
   538  				})
   539  
   540  				It("sends the ConfiguringServices and BoundServices events", func() {
   541  					Eventually(nextEvent).Should(Equal(ConfiguringServices))
   542  					Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warning")))
   543  					Expect(nextEvent()).To(Equal(BoundServices))
   544  				})
   545  			})
   546  		})
   547  	})
   548  
   549  	Describe("Upload", func() {
   550  		When("a droplet is provided", func() {
   551  			var dropletPath string
   552  
   553  			BeforeEach(func() {
   554  				tmpfile, err := ioutil.TempFile("", "fake-droplet")
   555  				Expect(err).ToNot(HaveOccurred())
   556  				_, err = tmpfile.Write([]byte("123456"))
   557  				Expect(err).ToNot(HaveOccurred())
   558  				Expect(tmpfile.Close()).ToNot(HaveOccurred())
   559  
   560  				dropletPath = tmpfile.Name()
   561  				config.DropletPath = dropletPath
   562  			})
   563  
   564  			AfterEach(func() {
   565  				Expect(os.RemoveAll(dropletPath)).ToNot(HaveOccurred())
   566  			})
   567  
   568  			When("uploading the droplet fails", func() {
   569  				When("the error is a retryable error", func() {
   570  					var someErr error
   571  					BeforeEach(func() {
   572  						someErr = errors.New("I AM A BANANA")
   573  						fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"droplet-upload-warning"}, ccerror.PipeSeekError{Err: someErr})
   574  					})
   575  
   576  					It("should send a RetryUpload event and retry uploading up to 3x", func() {
   577  						Eventually(nextEvent).Should(Equal(UploadingDroplet))
   578  						Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning")))
   579  						Expect(nextEvent()).To(Equal(RetryUpload))
   580  
   581  						Expect(nextEvent()).To(Equal(UploadingDroplet))
   582  						Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning")))
   583  						Expect(nextEvent()).To(Equal(RetryUpload))
   584  
   585  						Expect(nextEvent()).To(Equal(UploadingDroplet))
   586  						Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning")))
   587  						Expect(nextEvent()).To(Equal(RetryUpload))
   588  
   589  						Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadDropletComplete, Complete))
   590  						Eventually(fakeV2Actor.UploadDropletCallCount).Should(Equal(3))
   591  						Expect(errorStream).To(Receive(MatchError(actionerror.UploadFailedError{Err: someErr})))
   592  					})
   593  				})
   594  
   595  				When("the error is not a retryable error", func() {
   596  					BeforeEach(func() {
   597  						fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"droplet-upload-warning"}, errors.New("ohnos"))
   598  					})
   599  
   600  					It("raises an error", func() {
   601  						Eventually(nextEvent).Should(Equal(UploadingDroplet))
   602  						Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning")))
   603  						Eventually(errorStream).Should(Receive(MatchError("ohnos")))
   604  
   605  						Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadDropletComplete, Complete))
   606  					})
   607  				})
   608  			})
   609  
   610  			When("uploading the droplet is successful", func() {
   611  				BeforeEach(func() {
   612  					fakeV2Actor.UploadDropletReturns(v2action.Job{}, v2action.Warnings{"droplet-upload-warning"}, nil)
   613  				})
   614  
   615  				It("sends the UploadingDroplet event", func() {
   616  					Eventually(nextEvent).Should(Equal(UploadingDroplet))
   617  					Expect(nextEvent()).To(Equal(UploadDropletComplete))
   618  					Eventually(warningsStream).Should(Receive(ConsistOf("droplet-upload-warning")))
   619  				})
   620  			})
   621  		})
   622  
   623  		When("app bits are provided", func() {
   624  			When("there is at least one unmatched resource", func() {
   625  				BeforeEach(func() {
   626  					fakeV2Actor.ResourceMatchReturns(nil, []v2action.Resource{{}}, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil)
   627  				})
   628  
   629  				It("returns resource match warnings", func() {
   630  					Eventually(nextEvent).Should(Equal(ResourceMatching))
   631  					Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2")))
   632  				})
   633  
   634  				When("creating the archive is successful", func() {
   635  					var archivePath string
   636  
   637  					BeforeEach(func() {
   638  						tmpfile, err := ioutil.TempFile("", "fake-archive")
   639  						Expect(err).ToNot(HaveOccurred())
   640  						_, err = tmpfile.Write([]byte("123456"))
   641  						Expect(err).ToNot(HaveOccurred())
   642  						Expect(tmpfile.Close()).ToNot(HaveOccurred())
   643  
   644  						archivePath = tmpfile.Name()
   645  						fakeSharedActor.ZipDirectoryResourcesReturns(archivePath, nil)
   646  					})
   647  
   648  					It("sends a CreatingArchive event", func() {
   649  						Eventually(nextEvent).Should(Equal(CreatingArchive))
   650  					})
   651  
   652  					When("the upload is successful", func() {
   653  						BeforeEach(func() {
   654  							fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil)
   655  						})
   656  
   657  						It("sends a UploadingApplicationWithArchive event", func() {
   658  							Eventually(nextEvent).Should(Equal(UploadingApplicationWithArchive))
   659  							Expect(nextEvent()).To(Equal(UploadWithArchiveComplete))
   660  							Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   661  						})
   662  					})
   663  
   664  					When("the upload fails", func() {
   665  						When("the upload error is a retryable error", func() {
   666  							var someErr error
   667  
   668  							BeforeEach(func() {
   669  								someErr = errors.New("I AM A BANANA")
   670  								fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, ccerror.PipeSeekError{Err: someErr})
   671  							})
   672  
   673  							It("should send a RetryUpload event and retry uploading", func() {
   674  								Eventually(nextEvent).Should(Equal(UploadingApplicationWithArchive))
   675  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   676  								Expect(nextEvent()).To(Equal(RetryUpload))
   677  
   678  								Expect(nextEvent()).To(Equal(UploadingApplicationWithArchive))
   679  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   680  								Expect(nextEvent()).To(Equal(RetryUpload))
   681  
   682  								Expect(nextEvent()).To(Equal(UploadingApplicationWithArchive))
   683  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   684  								Expect(nextEvent()).To(Equal(RetryUpload))
   685  
   686  								Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadWithArchiveComplete, Complete))
   687  								Eventually(fakeV2Actor.UploadApplicationPackageCallCount).Should(Equal(3))
   688  								Expect(errorStream).To(Receive(MatchError(actionerror.UploadFailedError{Err: someErr})))
   689  							})
   690  
   691  						})
   692  
   693  						When("the upload error is not a retryable error", func() {
   694  							BeforeEach(func() {
   695  								fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, errors.New("dios mio"))
   696  							})
   697  
   698  							It("sends warnings and errors, then stops", func() {
   699  								Eventually(nextEvent).Should(Equal(UploadingApplicationWithArchive))
   700  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   701  								Consistently(nextEvent).ShouldNot(EqualEither(RetryUpload, UploadWithArchiveComplete, Complete))
   702  								Eventually(errorStream).Should(Receive(MatchError("dios mio")))
   703  							})
   704  						})
   705  					})
   706  				})
   707  
   708  				When("creating the archive fails", func() {
   709  					BeforeEach(func() {
   710  						fakeSharedActor.ZipDirectoryResourcesReturns("", errors.New("some-error"))
   711  					})
   712  
   713  					It("raises an error", func() {
   714  						Eventually(nextEvent).Should(Equal(ResourceMatching))
   715  						Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2")))
   716  						Eventually(errorStream).Should(Receive(MatchError("some-error")))
   717  						Consistently(nextEvent).ShouldNot(Equal(Complete))
   718  					})
   719  				})
   720  			})
   721  
   722  			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  				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  				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  		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  })