github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/command/common/install_plugin_command_test.go (about)

     1  package common_test
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/api/plugin/pluginerror"
    10  	"code.cloudfoundry.org/cli/api/plugin/pluginfakes"
    11  	"code.cloudfoundry.org/cli/command/commandfakes"
    12  	. "code.cloudfoundry.org/cli/command/common"
    13  	"code.cloudfoundry.org/cli/command/common/commonfakes"
    14  	"code.cloudfoundry.org/cli/command/translatableerror"
    15  	"code.cloudfoundry.org/cli/util/configv3"
    16  	"code.cloudfoundry.org/cli/util/ui"
    17  	. "github.com/onsi/ginkgo"
    18  	. "github.com/onsi/gomega"
    19  	. "github.com/onsi/gomega/gbytes"
    20  )
    21  
    22  var _ = Describe("install-plugin command", func() {
    23  	var (
    24  		cmd             InstallPluginCommand
    25  		testUI          *ui.UI
    26  		input           *Buffer
    27  		fakeConfig      *commandfakes.FakeConfig
    28  		fakeActor       *commonfakes.FakeInstallPluginActor
    29  		fakeProgressBar *pluginfakes.FakeProxyReader
    30  		executeErr      error
    31  		expectedErr     error
    32  		pluginHome      string
    33  	)
    34  
    35  	BeforeEach(func() {
    36  		input = NewBuffer()
    37  		testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer())
    38  		fakeConfig = new(commandfakes.FakeConfig)
    39  		fakeActor = new(commonfakes.FakeInstallPluginActor)
    40  		fakeProgressBar = new(pluginfakes.FakeProxyReader)
    41  
    42  		cmd = InstallPluginCommand{
    43  			UI:          testUI,
    44  			Config:      fakeConfig,
    45  			Actor:       fakeActor,
    46  			ProgressBar: fakeProgressBar,
    47  		}
    48  
    49  		var err error
    50  		pluginHome, err = ioutil.TempDir("", "some-pluginhome")
    51  		Expect(err).NotTo(HaveOccurred())
    52  
    53  		fakeConfig.PluginHomeReturns(pluginHome)
    54  		fakeConfig.BinaryNameReturns("faceman")
    55  	})
    56  
    57  	AfterEach(func() {
    58  		os.RemoveAll(pluginHome)
    59  	})
    60  
    61  	JustBeforeEach(func() {
    62  		executeErr = cmd.Execute(nil)
    63  	})
    64  
    65  	Describe("installing from a local file", func() {
    66  		BeforeEach(func() {
    67  			cmd.OptionalArgs.PluginNameOrLocation = "some-path"
    68  		})
    69  
    70  		When("the local file does not exist", func() {
    71  			BeforeEach(func() {
    72  				fakeActor.FileExistsReturns(false)
    73  			})
    74  
    75  			It("does not print installation messages and returns a FileNotFoundError", func() {
    76  				Expect(executeErr).To(MatchError(translatableerror.PluginNotFoundOnDiskOrInAnyRepositoryError{PluginName: "some-path", BinaryName: "faceman"}))
    77  
    78  				Expect(testUI.Out).ToNot(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
    79  				Expect(testUI.Out).ToNot(Say(`Installing plugin some-path\.\.\.`))
    80  			})
    81  		})
    82  
    83  		When("the file exists", func() {
    84  			BeforeEach(func() {
    85  				fakeActor.CreateExecutableCopyReturns("copy-path", nil)
    86  				fakeActor.FileExistsReturns(true)
    87  			})
    88  
    89  			When("the -f argument is given", func() {
    90  				BeforeEach(func() {
    91  					cmd.Force = true
    92  				})
    93  
    94  				When("the plugin is invalid", func() {
    95  					var returnedErr error
    96  
    97  					BeforeEach(func() {
    98  						returnedErr = actionerror.PluginInvalidError{}
    99  						fakeActor.GetAndValidatePluginReturns(configv3.Plugin{}, returnedErr)
   100  					})
   101  
   102  					It("returns an error", func() {
   103  						Expect(executeErr).To(MatchError(returnedErr))
   104  
   105  						Expect(testUI.Out).ToNot(Say("Installing plugin"))
   106  					})
   107  				})
   108  
   109  				When("the plugin is valid but generates an error when fetching metadata", func() {
   110  					var wrappedErr error
   111  
   112  					BeforeEach(func() {
   113  						wrappedErr = errors.New("some-error")
   114  						fakeActor.GetAndValidatePluginReturns(configv3.Plugin{}, actionerror.PluginInvalidError{Err: wrappedErr})
   115  					})
   116  
   117  					It("returns an error", func() {
   118  						Expect(executeErr).To(MatchError(actionerror.PluginInvalidError{Err: wrappedErr}))
   119  
   120  						Expect(testUI.Out).ToNot(Say("Installing plugin"))
   121  					})
   122  				})
   123  
   124  				When("the plugin is already installed", func() {
   125  					var (
   126  						plugin    configv3.Plugin
   127  						newPlugin configv3.Plugin
   128  					)
   129  					BeforeEach(func() {
   130  						plugin = configv3.Plugin{
   131  							Name: "some-plugin",
   132  							Version: configv3.PluginVersion{
   133  								Major: 1,
   134  								Minor: 2,
   135  								Build: 2,
   136  							},
   137  						}
   138  						newPlugin = configv3.Plugin{
   139  							Name: "some-plugin",
   140  							Version: configv3.PluginVersion{
   141  								Major: 1,
   142  								Minor: 2,
   143  								Build: 3,
   144  							},
   145  						}
   146  						fakeActor.GetAndValidatePluginReturns(newPlugin, nil)
   147  						fakeConfig.GetPluginCaseInsensitiveReturns(plugin, true)
   148  					})
   149  
   150  					When("an error is encountered uninstalling the existing plugin", func() {
   151  						BeforeEach(func() {
   152  							expectedErr = errors.New("uninstall plugin error")
   153  							fakeActor.UninstallPluginReturns(expectedErr)
   154  						})
   155  
   156  						It("returns the error", func() {
   157  							Expect(executeErr).To(MatchError(expectedErr))
   158  
   159  							Expect(testUI.Out).ToNot(Say(`Plugin some-plugin successfully uninstalled\.`))
   160  						})
   161  					})
   162  
   163  					When("no errors are encountered uninstalling the existing plugin", func() {
   164  						It("uninstalls the existing plugin and installs the current plugin", func() {
   165  							Expect(executeErr).ToNot(HaveOccurred())
   166  
   167  							Expect(testUI.Out).To(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   168  							Expect(testUI.Out).To(Say(`Install and use plugins at your own risk\.`))
   169  							Expect(testUI.Out).To(Say(`Plugin some-plugin 1\.2\.2 is already installed\. Uninstalling existing plugin\.\.\.`))
   170  							Expect(testUI.Out).To(Say("OK"))
   171  							Expect(testUI.Out).To(Say(`Plugin some-plugin successfully uninstalled\.`))
   172  							Expect(testUI.Out).To(Say(`Installing plugin some-plugin\.\.\.`))
   173  							Expect(testUI.Out).To(Say("OK"))
   174  							Expect(testUI.Out).To(Say(`Plugin some-plugin 1\.2\.3 successfully installed\.`))
   175  
   176  							Expect(fakeActor.FileExistsCallCount()).To(Equal(1))
   177  							Expect(fakeActor.FileExistsArgsForCall(0)).To(Equal("some-path"))
   178  
   179  							Expect(fakeActor.GetAndValidatePluginCallCount()).To(Equal(1))
   180  							_, _, path := fakeActor.GetAndValidatePluginArgsForCall(0)
   181  							Expect(path).To(Equal("copy-path"))
   182  
   183  							Expect(fakeConfig.GetPluginCaseInsensitiveCallCount()).To(Equal(1))
   184  							Expect(fakeConfig.GetPluginCaseInsensitiveArgsForCall(0)).To(Equal("some-plugin"))
   185  
   186  							Expect(fakeActor.UninstallPluginCallCount()).To(Equal(1))
   187  							_, pluginName := fakeActor.UninstallPluginArgsForCall(0)
   188  							Expect(pluginName).To(Equal("some-plugin"))
   189  
   190  							Expect(fakeActor.InstallPluginFromPathCallCount()).To(Equal(1))
   191  							path, installedPlugin := fakeActor.InstallPluginFromPathArgsForCall(0)
   192  							Expect(path).To(Equal("copy-path"))
   193  							Expect(installedPlugin).To(Equal(newPlugin))
   194  						})
   195  
   196  						When("an error is encountered installing the plugin", func() {
   197  							BeforeEach(func() {
   198  								expectedErr = errors.New("install plugin error")
   199  								fakeActor.InstallPluginFromPathReturns(expectedErr)
   200  							})
   201  
   202  							It("returns the error", func() {
   203  								Expect(executeErr).To(MatchError(expectedErr))
   204  
   205  								Expect(testUI.Out).ToNot(Say(`Plugin some-plugin 1\.2\.3 successfully installed\.`))
   206  							})
   207  						})
   208  					})
   209  				})
   210  
   211  				When("the plugin is not already installed", func() {
   212  					var plugin configv3.Plugin
   213  
   214  					BeforeEach(func() {
   215  						plugin = configv3.Plugin{
   216  							Name: "some-plugin",
   217  							Version: configv3.PluginVersion{
   218  								Major: 1,
   219  								Minor: 2,
   220  								Build: 3,
   221  							},
   222  						}
   223  						fakeActor.GetAndValidatePluginReturns(plugin, nil)
   224  					})
   225  
   226  					It("installs the plugin", func() {
   227  						Expect(executeErr).ToNot(HaveOccurred())
   228  
   229  						Expect(testUI.Out).To(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   230  						Expect(testUI.Out).To(Say(`Install and use plugins at your own risk\.`))
   231  						Expect(testUI.Out).To(Say(`Installing plugin some-plugin\.\.\.`))
   232  						Expect(testUI.Out).To(Say("OK"))
   233  						Expect(testUI.Out).To(Say(`Plugin some-plugin 1\.2\.3 successfully installed\.`))
   234  
   235  						Expect(fakeActor.FileExistsCallCount()).To(Equal(1))
   236  						Expect(fakeActor.FileExistsArgsForCall(0)).To(Equal("some-path"))
   237  
   238  						Expect(fakeActor.CreateExecutableCopyCallCount()).To(Equal(1))
   239  						pathArg, pluginDirArg := fakeActor.CreateExecutableCopyArgsForCall(0)
   240  						Expect(pathArg).To(Equal("some-path"))
   241  						Expect(pluginDirArg).To(ContainSubstring("some-pluginhome"))
   242  						Expect(pluginDirArg).To(ContainSubstring("temp"))
   243  
   244  						Expect(fakeActor.GetAndValidatePluginCallCount()).To(Equal(1))
   245  						_, _, path := fakeActor.GetAndValidatePluginArgsForCall(0)
   246  						Expect(path).To(Equal("copy-path"))
   247  
   248  						Expect(fakeConfig.GetPluginCaseInsensitiveCallCount()).To(Equal(1))
   249  						Expect(fakeConfig.GetPluginCaseInsensitiveArgsForCall(0)).To(Equal("some-plugin"))
   250  
   251  						Expect(fakeActor.InstallPluginFromPathCallCount()).To(Equal(1))
   252  						path, installedPlugin := fakeActor.InstallPluginFromPathArgsForCall(0)
   253  						Expect(path).To(Equal("copy-path"))
   254  						Expect(installedPlugin).To(Equal(plugin))
   255  
   256  						Expect(fakeActor.UninstallPluginCallCount()).To(Equal(0))
   257  					})
   258  
   259  					When("there is an error making an executable copy of the plugin binary", func() {
   260  						BeforeEach(func() {
   261  							expectedErr = errors.New("create executable copy error")
   262  							fakeActor.CreateExecutableCopyReturns("", expectedErr)
   263  						})
   264  
   265  						It("returns the error", func() {
   266  							Expect(executeErr).To(MatchError(expectedErr))
   267  						})
   268  					})
   269  
   270  					When("an error is encountered installing the plugin", func() {
   271  						BeforeEach(func() {
   272  							expectedErr = errors.New("install plugin error")
   273  							fakeActor.InstallPluginFromPathReturns(expectedErr)
   274  						})
   275  
   276  						It("returns the error", func() {
   277  							Expect(executeErr).To(MatchError(expectedErr))
   278  
   279  							Expect(testUI.Out).ToNot(Say(`Plugin some-plugin 1\.2\.3 successfully installed\.`))
   280  						})
   281  					})
   282  				})
   283  			})
   284  
   285  			When("the -f argument is not given (user is prompted for confirmation)", func() {
   286  				BeforeEach(func() {
   287  					cmd.Force = false
   288  				})
   289  
   290  				When("the user chooses no", func() {
   291  					BeforeEach(func() {
   292  						input.Write([]byte("n\n"))
   293  					})
   294  
   295  					It("cancels plugin installation", func() {
   296  						Expect(executeErr).ToNot(HaveOccurred())
   297  
   298  						Expect(testUI.Out).To(Say(`Plugin installation cancelled\.`))
   299  					})
   300  				})
   301  
   302  				When("the user chooses the default", func() {
   303  					BeforeEach(func() {
   304  						input.Write([]byte("\n"))
   305  					})
   306  
   307  					It("cancels plugin installation", func() {
   308  						Expect(executeErr).ToNot(HaveOccurred())
   309  
   310  						Expect(testUI.Out).To(Say(`Plugin installation cancelled\.`))
   311  					})
   312  				})
   313  
   314  				When("the user input is invalid", func() {
   315  					BeforeEach(func() {
   316  						input.Write([]byte("e\n"))
   317  					})
   318  
   319  					It("returns an error", func() {
   320  						Expect(executeErr).To(HaveOccurred())
   321  
   322  						Expect(testUI.Out).ToNot(Say("Installing plugin"))
   323  					})
   324  				})
   325  
   326  				When("the user chooses yes", func() {
   327  					BeforeEach(func() {
   328  						input.Write([]byte("y\n"))
   329  					})
   330  
   331  					When("the plugin is not already installed", func() {
   332  						var plugin configv3.Plugin
   333  
   334  						BeforeEach(func() {
   335  							plugin = configv3.Plugin{
   336  								Name: "some-plugin",
   337  								Version: configv3.PluginVersion{
   338  									Major: 1,
   339  									Minor: 2,
   340  									Build: 3,
   341  								},
   342  							}
   343  							fakeActor.GetAndValidatePluginReturns(plugin, nil)
   344  						})
   345  
   346  						It("installs the plugin", func() {
   347  							Expect(executeErr).ToNot(HaveOccurred())
   348  
   349  							Expect(testUI.Out).To(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   350  							Expect(testUI.Out).To(Say(`Install and use plugins at your own risk\.`))
   351  							Expect(testUI.Out).To(Say(`Do you want to install the plugin some-path\? \[yN\]`))
   352  							Expect(testUI.Out).To(Say(`Installing plugin some-plugin\.\.\.`))
   353  							Expect(testUI.Out).To(Say("OK"))
   354  							Expect(testUI.Out).To(Say(`Plugin some-plugin 1\.2\.3 successfully installed\.`))
   355  
   356  							Expect(fakeActor.FileExistsCallCount()).To(Equal(1))
   357  							Expect(fakeActor.FileExistsArgsForCall(0)).To(Equal("some-path"))
   358  
   359  							Expect(fakeActor.GetAndValidatePluginCallCount()).To(Equal(1))
   360  							_, _, path := fakeActor.GetAndValidatePluginArgsForCall(0)
   361  							Expect(path).To(Equal("copy-path"))
   362  
   363  							Expect(fakeConfig.GetPluginCaseInsensitiveCallCount()).To(Equal(1))
   364  							Expect(fakeConfig.GetPluginCaseInsensitiveArgsForCall(0)).To(Equal("some-plugin"))
   365  
   366  							Expect(fakeActor.InstallPluginFromPathCallCount()).To(Equal(1))
   367  							path, plugin := fakeActor.InstallPluginFromPathArgsForCall(0)
   368  							Expect(path).To(Equal("copy-path"))
   369  							Expect(plugin).To(Equal(plugin))
   370  
   371  							Expect(fakeActor.UninstallPluginCallCount()).To(Equal(0))
   372  						})
   373  					})
   374  
   375  					When("the plugin is already installed", func() {
   376  						BeforeEach(func() {
   377  							fakeConfig.GetPluginCaseInsensitiveReturns(configv3.Plugin{
   378  								Name: "some-plugin",
   379  								Version: configv3.PluginVersion{
   380  									Major: 1,
   381  									Minor: 2,
   382  									Build: 2,
   383  								},
   384  							}, true)
   385  							fakeActor.GetAndValidatePluginReturns(configv3.Plugin{
   386  								Name: "some-plugin",
   387  								Version: configv3.PluginVersion{
   388  									Major: 1,
   389  									Minor: 2,
   390  									Build: 3,
   391  								},
   392  							}, nil)
   393  						})
   394  
   395  						It("returns PluginAlreadyInstalledError", func() {
   396  							Expect(executeErr).To(MatchError(translatableerror.PluginAlreadyInstalledError{
   397  								BinaryName: "faceman",
   398  								Name:       "some-plugin",
   399  								Version:    "1.2.3",
   400  							}))
   401  						})
   402  					})
   403  				})
   404  			})
   405  		})
   406  	})
   407  
   408  	Describe("installing from an unsupported URL scheme", func() {
   409  		BeforeEach(func() {
   410  			cmd.OptionalArgs.PluginNameOrLocation = "ftp://some-url"
   411  		})
   412  
   413  		It("returns an error indicating an unsupported URL scheme", func() {
   414  			Expect(executeErr).To(MatchError(translatableerror.UnsupportedURLSchemeError{
   415  				UnsupportedURL: string(cmd.OptionalArgs.PluginNameOrLocation),
   416  			}))
   417  		})
   418  	})
   419  
   420  	Describe("installing from an HTTP URL", func() {
   421  		var (
   422  			plugin               configv3.Plugin
   423  			pluginName           string
   424  			executablePluginPath string
   425  		)
   426  
   427  		BeforeEach(func() {
   428  			cmd.OptionalArgs.PluginNameOrLocation = "http://some-url"
   429  			pluginName = "some-plugin"
   430  			executablePluginPath = "executable-path"
   431  		})
   432  
   433  		It("displays the plugin warning", func() {
   434  			Expect(testUI.Out).To(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   435  			Expect(testUI.Out).To(Say(`Install and use plugins at your own risk\.`))
   436  		})
   437  
   438  		When("the -f argument is given", func() {
   439  			BeforeEach(func() {
   440  				cmd.Force = true
   441  			})
   442  
   443  			It("begins downloading the plugin", func() {
   444  				Expect(testUI.Out).To(Say(`Starting download of plugin binary from URL\.\.\.`))
   445  
   446  				Expect(fakeActor.DownloadExecutableBinaryFromURLCallCount()).To(Equal(1))
   447  				url, tempPluginDir, proxyReader := fakeActor.DownloadExecutableBinaryFromURLArgsForCall(0)
   448  				Expect(url).To(Equal(cmd.OptionalArgs.PluginNameOrLocation.String()))
   449  				Expect(tempPluginDir).To(ContainSubstring("some-pluginhome"))
   450  				Expect(tempPluginDir).To(ContainSubstring("temp"))
   451  				Expect(proxyReader).To(Equal(fakeProgressBar))
   452  			})
   453  
   454  			When("getting the binary fails", func() {
   455  				BeforeEach(func() {
   456  					expectedErr = errors.New("some-error")
   457  					fakeActor.DownloadExecutableBinaryFromURLReturns("", expectedErr)
   458  				})
   459  
   460  				It("returns the error", func() {
   461  					Expect(executeErr).To(MatchError(expectedErr))
   462  
   463  					Expect(testUI.Out).ToNot(Say("downloaded"))
   464  					Expect(fakeActor.GetAndValidatePluginCallCount()).To(Equal(0))
   465  				})
   466  
   467  				When("a 4xx or 5xx status is encountered while downloading the plugin", func() {
   468  					BeforeEach(func() {
   469  						fakeActor.DownloadExecutableBinaryFromURLReturns("", pluginerror.RawHTTPStatusError{Status: "some-status"})
   470  					})
   471  
   472  					It("returns a DownloadPluginHTTPError", func() {
   473  						Expect(executeErr).To(MatchError(pluginerror.RawHTTPStatusError{Status: "some-status"}))
   474  					})
   475  				})
   476  
   477  				When("a SSL error is encountered while downloading the plugin", func() {
   478  					BeforeEach(func() {
   479  						fakeActor.DownloadExecutableBinaryFromURLReturns("", pluginerror.UnverifiedServerError{})
   480  					})
   481  
   482  					It("returns a DownloadPluginHTTPError", func() {
   483  						Expect(executeErr).To(MatchError(pluginerror.UnverifiedServerError{}))
   484  					})
   485  				})
   486  			})
   487  
   488  			When("getting the binary succeeds", func() {
   489  				BeforeEach(func() {
   490  					fakeActor.DownloadExecutableBinaryFromURLReturns("some-path", nil)
   491  					fakeActor.CreateExecutableCopyReturns(executablePluginPath, nil)
   492  				})
   493  
   494  				It("sets up the progress bar", func() {
   495  					Expect(fakeActor.GetAndValidatePluginCallCount()).To(Equal(1))
   496  					_, _, path := fakeActor.GetAndValidatePluginArgsForCall(0)
   497  					Expect(path).To(Equal(executablePluginPath))
   498  
   499  					Expect(fakeActor.DownloadExecutableBinaryFromURLCallCount()).To(Equal(1))
   500  					urlArg, pluginDirArg, proxyReader := fakeActor.DownloadExecutableBinaryFromURLArgsForCall(0)
   501  					Expect(urlArg).To(Equal("http://some-url"))
   502  					Expect(pluginDirArg).To(ContainSubstring("some-pluginhome"))
   503  					Expect(pluginDirArg).To(ContainSubstring("temp"))
   504  					Expect(proxyReader).To(Equal(fakeProgressBar))
   505  
   506  					Expect(fakeActor.CreateExecutableCopyCallCount()).To(Equal(1))
   507  					pathArg, pluginDirArg := fakeActor.CreateExecutableCopyArgsForCall(0)
   508  					Expect(pathArg).To(Equal("some-path"))
   509  					Expect(pluginDirArg).To(ContainSubstring("some-pluginhome"))
   510  					Expect(pluginDirArg).To(ContainSubstring("temp"))
   511  				})
   512  
   513  				When("the plugin is invalid", func() {
   514  					var returnedErr error
   515  
   516  					BeforeEach(func() {
   517  						returnedErr = actionerror.PluginInvalidError{}
   518  						fakeActor.GetAndValidatePluginReturns(configv3.Plugin{}, returnedErr)
   519  					})
   520  
   521  					It("returns an error", func() {
   522  						Expect(executeErr).To(MatchError(returnedErr))
   523  
   524  						Expect(fakeConfig.GetPluginCaseInsensitiveCallCount()).To(Equal(0))
   525  					})
   526  				})
   527  
   528  				When("the plugin is valid", func() {
   529  					var newPlugin configv3.Plugin
   530  
   531  					BeforeEach(func() {
   532  						plugin = configv3.Plugin{
   533  							Name: pluginName,
   534  							Version: configv3.PluginVersion{
   535  								Major: 1,
   536  								Minor: 2,
   537  								Build: 2,
   538  							},
   539  						}
   540  						newPlugin = configv3.Plugin{
   541  							Name: pluginName,
   542  							Version: configv3.PluginVersion{
   543  								Major: 1,
   544  								Minor: 2,
   545  								Build: 3,
   546  							},
   547  						}
   548  						fakeActor.GetAndValidatePluginReturns(newPlugin, nil)
   549  					})
   550  
   551  					When("the plugin is already installed", func() {
   552  						BeforeEach(func() {
   553  							fakeConfig.GetPluginCaseInsensitiveReturns(plugin, true)
   554  						})
   555  
   556  						It("displays uninstall message", func() {
   557  							Expect(testUI.Out).To(Say(`Plugin %s 1\.2\.2 is already installed\. Uninstalling existing plugin\.\.\.`, pluginName))
   558  						})
   559  
   560  						When("an error is encountered uninstalling the existing plugin", func() {
   561  							BeforeEach(func() {
   562  								expectedErr = errors.New("uninstall plugin error")
   563  								fakeActor.UninstallPluginReturns(expectedErr)
   564  							})
   565  
   566  							It("returns the error", func() {
   567  								Expect(executeErr).To(MatchError(expectedErr))
   568  
   569  								Expect(testUI.Out).ToNot(Say(`Plugin some-plugin successfully uninstalled\.`))
   570  							})
   571  						})
   572  
   573  						When("no errors are encountered uninstalling the existing plugin", func() {
   574  							It("displays uninstall message", func() {
   575  								Expect(testUI.Out).To(Say(`Plugin %s successfully uninstalled\.`, pluginName))
   576  							})
   577  
   578  							When("no errors are encountered installing the plugin", func() {
   579  								It("uninstalls the existing plugin and installs the current plugin", func() {
   580  									Expect(executeErr).ToNot(HaveOccurred())
   581  
   582  									Expect(testUI.Out).To(Say(`Installing plugin %s\.\.\.`, pluginName))
   583  									Expect(testUI.Out).To(Say("OK"))
   584  									Expect(testUI.Out).To(Say(`Plugin %s 1\.2\.3 successfully installed\.`, pluginName))
   585  								})
   586  							})
   587  
   588  							When("an error is encountered installing the plugin", func() {
   589  								BeforeEach(func() {
   590  									expectedErr = errors.New("install plugin error")
   591  									fakeActor.InstallPluginFromPathReturns(expectedErr)
   592  								})
   593  
   594  								It("returns the error", func() {
   595  									Expect(executeErr).To(MatchError(expectedErr))
   596  
   597  									Expect(testUI.Out).ToNot(Say(`Plugin some-plugin 1\.2\.3 successfully installed\.`))
   598  								})
   599  							})
   600  						})
   601  					})
   602  
   603  					When("the plugin is not already installed", func() {
   604  						It("installs the plugin", func() {
   605  							Expect(executeErr).ToNot(HaveOccurred())
   606  
   607  							Expect(testUI.Out).To(Say(`Installing plugin %s\.\.\.`, pluginName))
   608  							Expect(testUI.Out).To(Say("OK"))
   609  							Expect(testUI.Out).To(Say(`Plugin %s 1\.2\.3 successfully installed\.`, pluginName))
   610  
   611  							Expect(fakeActor.UninstallPluginCallCount()).To(Equal(0))
   612  						})
   613  					})
   614  				})
   615  			})
   616  		})
   617  
   618  		When("the -f argument is not given (user is prompted for confirmation)", func() {
   619  			BeforeEach(func() {
   620  				plugin = configv3.Plugin{
   621  					Name: pluginName,
   622  					Version: configv3.PluginVersion{
   623  						Major: 1,
   624  						Minor: 2,
   625  						Build: 3,
   626  					},
   627  				}
   628  
   629  				cmd.Force = false
   630  				fakeActor.DownloadExecutableBinaryFromURLReturns("some-path", nil)
   631  				fakeActor.CreateExecutableCopyReturns("executable-path", nil)
   632  			})
   633  
   634  			When("the user chooses no", func() {
   635  				BeforeEach(func() {
   636  					input.Write([]byte("n\n"))
   637  				})
   638  
   639  				It("cancels plugin installation", func() {
   640  					Expect(executeErr).ToNot(HaveOccurred())
   641  
   642  					Expect(testUI.Out).To(Say(`Plugin installation cancelled\.`))
   643  				})
   644  			})
   645  
   646  			When("the user chooses the default", func() {
   647  				BeforeEach(func() {
   648  					input.Write([]byte("\n"))
   649  				})
   650  
   651  				It("cancels plugin installation", func() {
   652  					Expect(executeErr).ToNot(HaveOccurred())
   653  
   654  					Expect(testUI.Out).To(Say(`Plugin installation cancelled\.`))
   655  				})
   656  			})
   657  
   658  			When("the user input is invalid", func() {
   659  				BeforeEach(func() {
   660  					input.Write([]byte("e\n"))
   661  				})
   662  
   663  				It("returns an error", func() {
   664  					Expect(executeErr).To(HaveOccurred())
   665  
   666  					Expect(testUI.Out).ToNot(Say("Installing plugin"))
   667  				})
   668  			})
   669  
   670  			When("the user chooses yes", func() {
   671  				BeforeEach(func() {
   672  					input.Write([]byte("y\n"))
   673  				})
   674  
   675  				When("the plugin is not already installed", func() {
   676  					BeforeEach(func() {
   677  						fakeActor.GetAndValidatePluginReturns(plugin, nil)
   678  					})
   679  
   680  					It("installs the plugin", func() {
   681  						Expect(executeErr).ToNot(HaveOccurred())
   682  
   683  						Expect(testUI.Out).To(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   684  						Expect(testUI.Out).To(Say(`Install and use plugins at your own risk\.`))
   685  						Expect(testUI.Out).To(Say(`Do you want to install the plugin %s\? \[yN\]`, cmd.OptionalArgs.PluginNameOrLocation))
   686  						Expect(testUI.Out).To(Say(`Starting download of plugin binary from URL\.\.\.`))
   687  						Expect(testUI.Out).To(Say(`Installing plugin %s\.\.\.`, pluginName))
   688  						Expect(testUI.Out).To(Say("OK"))
   689  						Expect(testUI.Out).To(Say(`Plugin %s 1\.2\.3 successfully installed\.`, pluginName))
   690  
   691  						Expect(fakeActor.DownloadExecutableBinaryFromURLCallCount()).To(Equal(1))
   692  						url, tempPluginDir, proxyReader := fakeActor.DownloadExecutableBinaryFromURLArgsForCall(0)
   693  						Expect(url).To(Equal(cmd.OptionalArgs.PluginNameOrLocation.String()))
   694  						Expect(tempPluginDir).To(ContainSubstring("some-pluginhome"))
   695  						Expect(tempPluginDir).To(ContainSubstring("temp"))
   696  						Expect(proxyReader).To(Equal(fakeProgressBar))
   697  
   698  						Expect(fakeActor.CreateExecutableCopyCallCount()).To(Equal(1))
   699  						path, tempPluginDir := fakeActor.CreateExecutableCopyArgsForCall(0)
   700  						Expect(path).To(Equal("some-path"))
   701  						Expect(tempPluginDir).To(ContainSubstring("some-pluginhome"))
   702  						Expect(tempPluginDir).To(ContainSubstring("temp"))
   703  
   704  						Expect(fakeActor.GetAndValidatePluginCallCount()).To(Equal(1))
   705  						_, _, path = fakeActor.GetAndValidatePluginArgsForCall(0)
   706  						Expect(path).To(Equal(executablePluginPath))
   707  
   708  						Expect(fakeConfig.GetPluginCaseInsensitiveCallCount()).To(Equal(1))
   709  						Expect(fakeConfig.GetPluginCaseInsensitiveArgsForCall(0)).To(Equal(pluginName))
   710  
   711  						Expect(fakeActor.InstallPluginFromPathCallCount()).To(Equal(1))
   712  						path, installedPlugin := fakeActor.InstallPluginFromPathArgsForCall(0)
   713  						Expect(path).To(Equal(executablePluginPath))
   714  						Expect(installedPlugin).To(Equal(plugin))
   715  
   716  						Expect(fakeActor.UninstallPluginCallCount()).To(Equal(0))
   717  					})
   718  				})
   719  
   720  				When("the plugin is already installed", func() {
   721  					BeforeEach(func() {
   722  						fakeConfig.GetPluginCaseInsensitiveReturns(configv3.Plugin{
   723  							Name: "some-plugin",
   724  							Version: configv3.PluginVersion{
   725  								Major: 1,
   726  								Minor: 2,
   727  								Build: 2,
   728  							},
   729  						}, true)
   730  						fakeActor.GetAndValidatePluginReturns(configv3.Plugin{
   731  							Name: "some-plugin",
   732  							Version: configv3.PluginVersion{
   733  								Major: 1,
   734  								Minor: 2,
   735  								Build: 3,
   736  							},
   737  						}, nil)
   738  					})
   739  
   740  					It("returns PluginAlreadyInstalledError", func() {
   741  						Expect(executeErr).To(MatchError(translatableerror.PluginAlreadyInstalledError{
   742  							BinaryName: "faceman",
   743  							Name:       pluginName,
   744  							Version:    "1.2.3",
   745  						}))
   746  					})
   747  				})
   748  			})
   749  		})
   750  	})
   751  })