github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/actor/pushaction/apply_test.go (about)

     1  package pushaction_test
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  
     7  	"code.cloudfoundry.org/cli/actor/actionerror"
     8  	. "code.cloudfoundry.org/cli/actor/pushaction"
     9  	"code.cloudfoundry.org/cli/actor/pushaction/pushactionfakes"
    10  	"code.cloudfoundry.org/cli/actor/v2action"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    12  
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  )
    16  
    17  func streamsDrainedAndClosed(configStream <-chan ApplicationConfig, eventStream <-chan Event, warningsStream <-chan Warnings, errorStream <-chan error) bool {
    18  	var configStreamClosed, eventStreamClosed, warningsStreamClosed, errorStreamClosed bool
    19  	for {
    20  		select {
    21  		case _, ok := <-configStream:
    22  			if !ok {
    23  				configStreamClosed = true
    24  			}
    25  		case _, ok := <-eventStream:
    26  			if !ok {
    27  				eventStreamClosed = true
    28  			}
    29  		case _, ok := <-warningsStream:
    30  			if !ok {
    31  				warningsStreamClosed = true
    32  			}
    33  		case _, ok := <-errorStream:
    34  			if !ok {
    35  				errorStreamClosed = true
    36  			}
    37  		}
    38  		if configStreamClosed && eventStreamClosed && warningsStreamClosed && errorStreamClosed {
    39  			break
    40  		}
    41  	}
    42  	return true
    43  }
    44  
    45  var _ = Describe("Apply", func() {
    46  	var (
    47  		actor           *Actor
    48  		fakeV2Actor     *pushactionfakes.FakeV2Actor
    49  		fakeSharedActor *pushactionfakes.FakeSharedActor
    50  
    51  		config          ApplicationConfig
    52  		fakeProgressBar *pushactionfakes.FakeProgressBar
    53  
    54  		eventStream    <-chan Event
    55  		warningsStream <-chan Warnings
    56  		errorStream    <-chan error
    57  		configStream   <-chan ApplicationConfig
    58  	)
    59  
    60  	BeforeEach(func() {
    61  		fakeV2Actor = new(pushactionfakes.FakeV2Actor)
    62  		fakeSharedActor = new(pushactionfakes.FakeSharedActor)
    63  		actor = NewActor(fakeV2Actor, fakeSharedActor)
    64  		config = ApplicationConfig{
    65  			DesiredApplication: Application{
    66  				Application: v2action.Application{
    67  					Name:      "some-app-name",
    68  					SpaceGUID: "some-space-guid",
    69  				}},
    70  			DesiredRoutes: []v2action.Route{{Host: "banana"}},
    71  			Path:          "some-path",
    72  		}
    73  		fakeProgressBar = new(pushactionfakes.FakeProgressBar)
    74  	})
    75  
    76  	JustBeforeEach(func() {
    77  		configStream, eventStream, warningsStream, errorStream = actor.Apply(config, fakeProgressBar)
    78  	})
    79  
    80  	AfterEach(func() {
    81  		Eventually(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).Should(BeTrue())
    82  	})
    83  
    84  	Context("when creating/updating the application is successful", func() {
    85  		var createdApp v2action.Application
    86  
    87  		BeforeEach(func() {
    88  			fakeV2Actor.CreateApplicationStub = func(application v2action.Application) (v2action.Application, v2action.Warnings, error) {
    89  				createdApp = application
    90  				createdApp.GUID = "some-app-guid"
    91  
    92  				return createdApp, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, nil
    93  			}
    94  		})
    95  
    96  		JustBeforeEach(func() {
    97  			Eventually(eventStream).Should(Receive(Equal(SettingUpApplication)))
    98  			Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2")))
    99  			Eventually(eventStream).Should(Receive(Equal(CreatedApplication)))
   100  		})
   101  
   102  		Context("when the route creation is successful", func() {
   103  			var createdRoutes []v2action.Route
   104  
   105  			BeforeEach(func() {
   106  				createdRoutes = []v2action.Route{{Host: "banana", GUID: "some-route-guid"}}
   107  				fakeV2Actor.CreateRouteReturns(createdRoutes[0], v2action.Warnings{"create-route-warnings-1", "create-route-warnings-2"}, nil)
   108  			})
   109  
   110  			JustBeforeEach(func() {
   111  				Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes)))
   112  				Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warnings-1", "create-route-warnings-2")))
   113  				Eventually(eventStream).Should(Receive(Equal(CreatedRoutes)))
   114  			})
   115  
   116  			Context("when mapping the routes is successful", func() {
   117  				var desiredServices map[string]v2action.ServiceInstance
   118  
   119  				BeforeEach(func() {
   120  					desiredServices = map[string]v2action.ServiceInstance{
   121  						"service_1": {Name: "service_1", GUID: "service_guid"},
   122  					}
   123  					config.DesiredServices = desiredServices
   124  					fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"map-route-warnings-1", "map-route-warnings-2"}, nil)
   125  				})
   126  
   127  				JustBeforeEach(func() {
   128  					Eventually(warningsStream).Should(Receive(ConsistOf("map-route-warnings-1", "map-route-warnings-2")))
   129  					Eventually(eventStream).Should(Receive(Equal(BoundRoutes)))
   130  				})
   131  
   132  				Context("when service binding is successful", func() {
   133  					BeforeEach(func() {
   134  						fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warnings-1", "bind-service-warnings-2"}, nil)
   135  					})
   136  
   137  					JustBeforeEach(func() {
   138  						Eventually(eventStream).Should(Receive(Equal(ConfiguringServices)))
   139  						Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warnings-1", "bind-service-warnings-2")))
   140  						Eventually(eventStream).Should(Receive(Equal(BoundServices)))
   141  					})
   142  
   143  					Context("when resource matching happens", func() {
   144  						JustBeforeEach(func() {
   145  							Eventually(eventStream).Should(Receive(Equal(ResourceMatching)))
   146  							Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2")))
   147  						})
   148  
   149  						Context("when there is at least one resource that has not been matched", func() {
   150  							BeforeEach(func() {
   151  								fakeV2Actor.ResourceMatchReturns(nil, []v2action.Resource{{}}, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil)
   152  							})
   153  
   154  							Context("when the archive creation is successful", func() {
   155  								var archivePath string
   156  
   157  								BeforeEach(func() {
   158  									tmpfile, err := ioutil.TempFile("", "fake-archive")
   159  									Expect(err).ToNot(HaveOccurred())
   160  									_, err = tmpfile.Write([]byte("123456"))
   161  									Expect(err).ToNot(HaveOccurred())
   162  									Expect(tmpfile.Close()).ToNot(HaveOccurred())
   163  
   164  									archivePath = tmpfile.Name()
   165  									fakeSharedActor.ZipDirectoryResourcesReturns(archivePath, nil)
   166  								})
   167  
   168  								JustBeforeEach(func() {
   169  									Eventually(eventStream).Should(Receive(Equal(CreatingArchive)))
   170  								})
   171  
   172  								Context("when the upload is successful", func() {
   173  									BeforeEach(func() {
   174  										fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil)
   175  									})
   176  
   177  									JustBeforeEach(func() {
   178  										Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive)))
   179  										Eventually(eventStream).Should(Receive(Equal(UploadWithArchiveComplete)))
   180  										Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   181  									})
   182  
   183  									It("sends the updated config and a complete event", func() {
   184  										Eventually(configStream).Should(Receive(Equal(ApplicationConfig{
   185  											CurrentApplication: Application{Application: createdApp},
   186  											CurrentRoutes:      createdRoutes,
   187  											CurrentServices:    desiredServices,
   188  											DesiredApplication: Application{Application: createdApp},
   189  											DesiredRoutes:      createdRoutes,
   190  											DesiredServices:    desiredServices,
   191  											UnmatchedResources: []v2action.Resource{{}},
   192  											Path:               "some-path",
   193  										})))
   194  										Eventually(eventStream).Should(Receive(Equal(Complete)))
   195  
   196  										Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1))
   197  									})
   198  								})
   199  
   200  								Context("when the upload errors", func() {
   201  									Context("with a retryable error", func() {
   202  										BeforeEach(func() {
   203  											fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, ccerror.PipeSeekError{})
   204  										})
   205  
   206  										It("retries the download up to three times", func() {
   207  											Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive)))
   208  											Eventually(fakeProgressBar.NewProgressBarWrapperCallCount).Should(Equal(1))
   209  											Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   210  											Eventually(eventStream).Should(Receive(Equal(RetryUpload)))
   211  
   212  											Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive)))
   213  											Eventually(fakeProgressBar.NewProgressBarWrapperCallCount).Should(Equal(2))
   214  											Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   215  											Eventually(eventStream).Should(Receive(Equal(RetryUpload)))
   216  
   217  											Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive)))
   218  											Eventually(fakeProgressBar.NewProgressBarWrapperCallCount).Should(Equal(3))
   219  											Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   220  											Eventually(eventStream).Should(Receive(Equal(RetryUpload)))
   221  
   222  											Eventually(errorStream).Should(Receive(Equal(actionerror.UploadFailedError{})))
   223  										})
   224  									})
   225  
   226  									Context("with a generic error", func() {
   227  										var expectedErr error
   228  
   229  										BeforeEach(func() {
   230  											expectedErr = errors.New("dios mio")
   231  											fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, expectedErr)
   232  										})
   233  
   234  										It("sends warnings and errors, then stops", func() {
   235  											Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive)))
   236  											Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   237  											Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   238  											Consistently(eventStream).ShouldNot(Receive())
   239  										})
   240  									})
   241  								})
   242  							})
   243  
   244  							Context("when the archive creation errors", func() {
   245  								var expectedErr error
   246  
   247  								BeforeEach(func() {
   248  									expectedErr = errors.New("dios mio")
   249  									fakeSharedActor.ZipDirectoryResourcesReturns("", expectedErr)
   250  								})
   251  
   252  								It("sends warnings and errors, then stops", func() {
   253  									Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   254  									Consistently(eventStream).ShouldNot(Receive())
   255  								})
   256  							})
   257  						})
   258  
   259  						Context("when all the resources have been matched", func() {
   260  							BeforeEach(func() {
   261  								fakeV2Actor.ResourceMatchReturns(nil, nil, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil)
   262  							})
   263  
   264  							JustBeforeEach(func() {
   265  								Eventually(eventStream).Should(Receive(Equal(UploadingApplication)))
   266  								Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2")))
   267  							})
   268  
   269  							Context("when the upload is successful", func() {
   270  								BeforeEach(func() {
   271  									fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil)
   272  								})
   273  
   274  								It("sends the updated config and a complete event", func() {
   275  									Eventually(configStream).Should(Receive(Equal(ApplicationConfig{
   276  										CurrentApplication: Application{Application: createdApp},
   277  										CurrentRoutes:      createdRoutes,
   278  										CurrentServices:    desiredServices,
   279  										DesiredApplication: Application{Application: createdApp},
   280  										DesiredRoutes:      createdRoutes,
   281  										DesiredServices:    desiredServices,
   282  										Path:               "some-path",
   283  									})))
   284  									Eventually(eventStream).Should(Receive(Equal(Complete)))
   285  
   286  									Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1))
   287  									_, _, reader, readerLength := fakeV2Actor.UploadApplicationPackageArgsForCall(0)
   288  									Expect(reader).To(BeNil())
   289  									Expect(readerLength).To(BeNumerically("==", 0))
   290  								})
   291  							})
   292  
   293  							Context("when the upload errors", func() {
   294  								var expectedErr error
   295  
   296  								BeforeEach(func() {
   297  									expectedErr = errors.New("dios mio")
   298  									fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, expectedErr)
   299  								})
   300  
   301  								It("sends warnings and errors, then stops", func() {
   302  									Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   303  									Consistently(eventStream).ShouldNot(Receive())
   304  								})
   305  							})
   306  						})
   307  					})
   308  
   309  					Context("when a docker image is provided", func() {
   310  						BeforeEach(func() {
   311  							config.DesiredApplication.DockerImage = "some-docker-image-path"
   312  						})
   313  
   314  						It("skips achiving and uploading", func() {
   315  							Eventually(configStream).Should(Receive())
   316  							Eventually(eventStream).Should(Receive(Equal(Complete)))
   317  
   318  							Expect(fakeSharedActor.ZipDirectoryResourcesCallCount()).To(Equal(0))
   319  						})
   320  					})
   321  				})
   322  
   323  				Context("when there are no services to bind", func() {
   324  					BeforeEach(func() {
   325  						services := map[string]v2action.ServiceInstance{
   326  							"service_1": {Name: "service_1", GUID: "service_guid"},
   327  						}
   328  						config.CurrentServices = services
   329  						config.DesiredServices = services
   330  					})
   331  
   332  					It("should not send the BoundServices event", func() {
   333  						Eventually(eventStream).ShouldNot(Receive(Equal(ConfiguringServices)))
   334  						Consistently(eventStream).ShouldNot(Receive(Equal(BoundServices)))
   335  					})
   336  				})
   337  
   338  				Context("when mapping routes fails", func() {
   339  					var expectedErr error
   340  
   341  					BeforeEach(func() {
   342  						expectedErr = errors.New("dios mio")
   343  						fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warnings-1", "bind-service-warnings-2"}, expectedErr)
   344  					})
   345  
   346  					It("sends warnings and errors, then stops", func() {
   347  						Eventually(eventStream).Should(Receive(Equal(ConfiguringServices)))
   348  						Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warnings-1", "bind-service-warnings-2")))
   349  						Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   350  						Consistently(eventStream).ShouldNot(Receive())
   351  					})
   352  				})
   353  			})
   354  
   355  			Context("when there are no routes to map", func() {
   356  				BeforeEach(func() {
   357  					config.CurrentRoutes = createdRoutes
   358  				})
   359  
   360  				It("should not send the RouteCreated event", func() {
   361  					Eventually(warningsStream).Should(Receive())
   362  					Consistently(eventStream).ShouldNot(Receive(Equal(CreatedRoutes)))
   363  				})
   364  			})
   365  
   366  			Context("when mapping the routes errors", func() {
   367  				var expectedErr error
   368  
   369  				BeforeEach(func() {
   370  					expectedErr = errors.New("dios mio")
   371  					fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"map-route-warnings-1", "map-route-warnings-2"}, expectedErr)
   372  				})
   373  
   374  				It("sends warnings and errors, then stops", func() {
   375  					Eventually(warningsStream).Should(Receive(ConsistOf("map-route-warnings-1", "map-route-warnings-2")))
   376  					Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   377  					Consistently(eventStream).ShouldNot(Receive())
   378  				})
   379  			})
   380  		})
   381  
   382  		Context("when there are no routes to create", func() {
   383  			BeforeEach(func() {
   384  				config.DesiredRoutes[0].GUID = "some-route-guid"
   385  			})
   386  
   387  			It("should not send the RouteCreated event", func() {
   388  				Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes)))
   389  				Eventually(warningsStream).Should(Receive())
   390  				Consistently(eventStream).ShouldNot(Receive())
   391  			})
   392  		})
   393  
   394  		Context("when the route creation errors", func() {
   395  			var expectedErr error
   396  
   397  			BeforeEach(func() {
   398  				expectedErr = errors.New("dios mio")
   399  				fakeV2Actor.CreateRouteReturns(v2action.Route{}, v2action.Warnings{"create-route-warnings-1", "create-route-warnings-2"}, expectedErr)
   400  			})
   401  
   402  			It("sends warnings and errors, then stops", func() {
   403  				Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes)))
   404  				Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warnings-1", "create-route-warnings-2")))
   405  				Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   406  				Consistently(eventStream).ShouldNot(Receive())
   407  			})
   408  		})
   409  
   410  		Context("when routes are to be removed", func() {
   411  			BeforeEach(func() {
   412  				config.NoRoute = true
   413  			})
   414  
   415  			Context("when there are routes", func() {
   416  				BeforeEach(func() {
   417  					config.CurrentRoutes = []v2action.Route{{GUID: "some-route-guid-1"}, {GUID: "some-route-guid-2"}}
   418  				})
   419  
   420  				JustBeforeEach(func() {
   421  					Eventually(eventStream).Should(Receive(Equal(UnmappingRoutes)))
   422  				})
   423  
   424  				Context("when the unmap is successful", func() {
   425  					BeforeEach(func() {
   426  						fakeV2Actor.UnmapRouteFromApplicationReturnsOnCall(0, v2action.Warnings{"unmapping-route-warnings-1"}, nil)
   427  						fakeV2Actor.UnmapRouteFromApplicationReturnsOnCall(1, v2action.Warnings{"unmapping-route-warnings-2"}, nil)
   428  					})
   429  
   430  					It("unmaps the routes and returns all warnings", func() {
   431  						Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings-1", "unmapping-route-warnings-2")))
   432  						Expect(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).To(BeTrue())
   433  
   434  						Expect(fakeV2Actor.UnmapRouteFromApplicationCallCount()).To(Equal(2))
   435  					})
   436  				})
   437  
   438  				Context("when unmapping routes fails", func() {
   439  					var expectedErr error
   440  
   441  					BeforeEach(func() {
   442  						expectedErr = errors.New("dios mio")
   443  						fakeV2Actor.UnmapRouteFromApplicationReturns(v2action.Warnings{"unmapping-route-warnings-1", "unmapping-route-warnings-2"}, expectedErr)
   444  					})
   445  
   446  					It("sends warnings and errors, then stops", func() {
   447  						Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings-1", "unmapping-route-warnings-2")))
   448  						Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   449  						Consistently(eventStream).ShouldNot(Receive())
   450  					})
   451  				})
   452  			})
   453  
   454  			Context("when there are no routes", func() {
   455  				BeforeEach(func() {
   456  					config.CurrentRoutes = nil
   457  				})
   458  
   459  				It("does not send an UnmappingRoutes event", func() {
   460  					Consistently(eventStream).ShouldNot(Receive(Equal(UnmappingRoutes)))
   461  					Expect(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).To(BeTrue())
   462  
   463  					Expect(fakeV2Actor.UnmapRouteFromApplicationCallCount()).To(Equal(0))
   464  				})
   465  			})
   466  		})
   467  	})
   468  
   469  	Context("when creating/updating errors", func() {
   470  		var expectedErr error
   471  
   472  		BeforeEach(func() {
   473  			expectedErr = errors.New("dios mio")
   474  			fakeV2Actor.CreateApplicationReturns(v2action.Application{}, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, expectedErr)
   475  		})
   476  
   477  		It("sends warnings and errors, then stops", func() {
   478  			Eventually(eventStream).Should(Receive(Equal(SettingUpApplication)))
   479  			Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2")))
   480  			Eventually(errorStream).Should(Receive(MatchError(expectedErr)))
   481  			Consistently(eventStream).ShouldNot(Receive())
   482  		})
   483  	})
   484  })