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