github.com/sleungcy/cli@v7.1.0+incompatible/actor/pushaction/apply_test.go (about)

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