github.com/swisscom/cloudfoundry-cli@v7.1.0+incompatible/cf/commands/plugin/install_plugin_test.go (about)

     1  package plugin_test
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"sync"
    12  
    13  	"code.cloudfoundry.org/cli/cf/actors/pluginrepo/pluginrepofakes"
    14  	"code.cloudfoundry.org/cli/cf/commandregistry"
    15  	"code.cloudfoundry.org/cli/cf/commandregistry/commandregistryfakes"
    16  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    17  	"code.cloudfoundry.org/cli/cf/configuration/pluginconfig"
    18  	"code.cloudfoundry.org/cli/cf/configuration/pluginconfig/pluginconfigfakes"
    19  	"code.cloudfoundry.org/cli/cf/flags"
    20  	"code.cloudfoundry.org/cli/cf/models"
    21  	"code.cloudfoundry.org/cli/cf/requirements"
    22  	"code.cloudfoundry.org/cli/cf/requirements/requirementsfakes"
    23  	testcmd "code.cloudfoundry.org/cli/cf/util/testhelpers/commands"
    24  	testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration"
    25  	testterm "code.cloudfoundry.org/cli/cf/util/testhelpers/terminal"
    26  	"code.cloudfoundry.org/cli/cf/util/utilfakes"
    27  	"code.cloudfoundry.org/cli/plugin"
    28  
    29  	clipr "code.cloudfoundry.org/cli-plugin-repo/web"
    30  
    31  	. "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers"
    32  	. "github.com/onsi/ginkgo"
    33  	. "github.com/onsi/gomega"
    34  )
    35  
    36  var runCmdMutex = sync.Mutex{}
    37  
    38  var _ = Describe("Install", func() {
    39  	var (
    40  		ui                  *testterm.FakeUI
    41  		requirementsFactory *requirementsfakes.FakeFactory
    42  		config              coreconfig.Repository
    43  		pluginConfig        *pluginconfigfakes.FakePluginConfiguration
    44  		fakePluginRepo      *pluginrepofakes.FakePluginRepo
    45  		fakeChecksum        *utilfakes.FakeSha1Checksum
    46  
    47  		pluginFile *os.File
    48  		homeDir    string
    49  		pluginDir  string
    50  		curDir     string
    51  
    52  		test_1                    string
    53  		test_2                    string
    54  		test_curDir               string
    55  		test_with_help            string
    56  		test_with_orgs            string
    57  		test_with_orgs_short_name string
    58  		aliasConflicts            string
    59  		deps                      commandregistry.Dependency
    60  	)
    61  
    62  	updateCommandDependency := func(pluginCall bool) {
    63  		deps.UI = ui
    64  		deps.Config = config
    65  		deps.PluginConfig = pluginConfig
    66  		deps.PluginRepo = fakePluginRepo
    67  		deps.ChecksumUtil = fakeChecksum
    68  		commandregistry.Commands.SetCommand(commandregistry.Commands.FindCommand("install-plugin").SetDependency(deps, pluginCall))
    69  	}
    70  
    71  	BeforeEach(func() {
    72  		ui = &testterm.FakeUI{}
    73  		requirementsFactory = new(requirementsfakes.FakeFactory)
    74  		pluginConfig = new(pluginconfigfakes.FakePluginConfiguration)
    75  		config = testconfig.NewRepositoryWithDefaults()
    76  		fakePluginRepo = new(pluginrepofakes.FakePluginRepo)
    77  		fakeChecksum = new(utilfakes.FakeSha1Checksum)
    78  
    79  		dir, err := os.Getwd()
    80  		if err != nil {
    81  			panic(err)
    82  		}
    83  		test_1 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_1.exe")
    84  		test_2 = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_2.exe")
    85  		test_curDir = filepath.Join("test_1.exe")
    86  		test_with_help = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_help.exe")
    87  		test_with_orgs = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_orgs.exe")
    88  		test_with_orgs_short_name = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "test_with_orgs_short_name.exe")
    89  		aliasConflicts = filepath.Join(dir, "..", "..", "..", "fixtures", "plugins", "alias_conflicts.exe")
    90  
    91  		homeDir, err = ioutil.TempDir(os.TempDir(), "plugins")
    92  		Expect(err).ToNot(HaveOccurred())
    93  
    94  		pluginDir = filepath.Join(homeDir, ".cf", "plugins")
    95  		pluginConfig.GetPluginPathReturns(pluginDir)
    96  
    97  		curDir, err = os.Getwd()
    98  		Expect(err).ToNot(HaveOccurred())
    99  		pluginFile, err = ioutil.TempFile("./", "test_plugin")
   100  		Expect(err).ToNot(HaveOccurred())
   101  
   102  		if runtime.GOOS != "windows" {
   103  			err = os.Chmod(test_1, 0700)
   104  			Expect(err).ToNot(HaveOccurred())
   105  		}
   106  	})
   107  
   108  	AfterEach(func() {
   109  		os.RemoveAll(filepath.Join(curDir, pluginFile.Name()))
   110  		os.RemoveAll(homeDir)
   111  	})
   112  
   113  	runCommand := func(args ...string) bool {
   114  		// run command has races becuase it writes and erases temporary files, so the test runner should
   115  		// really only run one of these at a time. Often the files are actual compiled exes in the test
   116  		// fixtures path, so it's not easy to prevent the tests from sharing file handles
   117  		runCmdMutex.Lock()
   118  		defer runCmdMutex.Unlock()
   119  		return testcmd.RunCLICommand("install-plugin", args, requirementsFactory, updateCommandDependency, false, ui)
   120  	}
   121  
   122  	Describe("requirements", func() {
   123  		It("fails with usage when not provided a path to the plugin executable", func() {
   124  			Expect(runCommand()).ToNot(HavePassedRequirements())
   125  		})
   126  	})
   127  
   128  	Context("when the -f flag is not provided", func() {
   129  		Context("and the user responds with 'y'", func() {
   130  			It("continues to install the plugin", func() {
   131  				ui.Inputs = []string{"y"}
   132  				runCommand("pluggy", "-r", "somerepo")
   133  				Expect(ui.Outputs()).To(ContainSubstrings([]string{"Looking up 'pluggy' from repository 'somerepo'"}))
   134  			})
   135  		})
   136  
   137  		Context("but the user responds with 'n'", func() {
   138  			It("quits with a message", func() {
   139  				ui.Inputs = []string{"n"}
   140  				runCommand("pluggy", "-r", "somerepo")
   141  				Expect(ui.Outputs()).To(ContainSubstrings([]string{"Plugin installation cancelled"}))
   142  			})
   143  		})
   144  	})
   145  
   146  	Describe("Locating binary file", func() {
   147  
   148  		Describe("install from plugin repository when '-r' provided", func() {
   149  			Context("gets metadata of the plugin from repo", func() {
   150  				Context("when repo is not found in config", func() {
   151  					It("informs user repo is not found", func() {
   152  						runCommand("plugin1", "-r", "repo1", "-f")
   153  						Expect(ui.Outputs()).To(ContainSubstrings([]string{"Looking up 'plugin1' from repository 'repo1'"}))
   154  						Expect(ui.Outputs()).To(ContainSubstrings([]string{"repo1 not found"}))
   155  					})
   156  				})
   157  
   158  				Context("when repo is found in config", func() {
   159  					Context("when repo endpoint returns an error", func() {
   160  						It("informs user about the error", func() {
   161  							config.SetPluginRepo(models.PluginRepo{Name: "repo1", URL: ""})
   162  							fakePluginRepo.GetPluginsReturns(nil, []string{"repo error1"})
   163  							runCommand("plugin1", "-r", "repo1", "-f")
   164  
   165  							Expect(ui.Outputs()).To(ContainSubstrings([]string{"Error getting plugin metadata from repo"}))
   166  							Expect(ui.Outputs()).To(ContainSubstrings([]string{"repo error1"}))
   167  						})
   168  					})
   169  
   170  					Context("when plugin metadata is available and desired plugin is not found", func() {
   171  						It("informs user about the error", func() {
   172  							config.SetPluginRepo(models.PluginRepo{Name: "repo1", URL: ""})
   173  							fakePluginRepo.GetPluginsReturns(nil, nil)
   174  							runCommand("plugin1", "-r", "repo1", "-f")
   175  
   176  							Expect(ui.Outputs()).To(ContainSubstrings([]string{"plugin1 is not available in repo 'repo1'"}))
   177  						})
   178  					})
   179  
   180  					It("ignore cases in repo name", func() {
   181  						config.SetPluginRepo(models.PluginRepo{Name: "repo1", URL: ""})
   182  						fakePluginRepo.GetPluginsReturns(nil, nil)
   183  						runCommand("plugin1", "-r", "REPO1", "-f")
   184  
   185  						Expect(ui.Outputs()).NotTo(ContainSubstrings([]string{"REPO1 not found"}))
   186  					})
   187  				})
   188  			})
   189  
   190  			Context("downloads the binary for the machine's OS", func() {
   191  				Context("when binary is not available", func() {
   192  					It("informs user when binary is not available for OS", func() {
   193  						p := clipr.Plugin{
   194  							Name: "plugin1",
   195  						}
   196  						result := make(map[string][]clipr.Plugin)
   197  						result["repo1"] = []clipr.Plugin{p}
   198  
   199  						config.SetPluginRepo(models.PluginRepo{Name: "repo1", URL: ""})
   200  						fakePluginRepo.GetPluginsReturns(result, nil)
   201  						runCommand("plugin1", "-r", "repo1", "-f")
   202  
   203  						Expect(ui.Outputs()).To(ContainSubstrings([]string{"Plugin requested has no binary available"}))
   204  					})
   205  				})
   206  
   207  				Context("when binary is available", func() {
   208  					var (
   209  						testServer *httptest.Server
   210  					)
   211  
   212  					BeforeEach(func() {
   213  						h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   214  							fmt.Fprintln(w, "abc")
   215  						})
   216  
   217  						testServer = httptest.NewServer(h)
   218  
   219  						fakeChecksum.CheckSha1Returns(true)
   220  
   221  						p := clipr.Plugin{
   222  							Name: "plugin1",
   223  							Binaries: []clipr.Binary{
   224  								{
   225  									Platform: "osx",
   226  									Url:      testServer.URL + "/test.exe",
   227  								},
   228  								{
   229  									Platform: "win64",
   230  									Url:      testServer.URL + "/test.exe",
   231  								},
   232  								{
   233  									Platform: "win32",
   234  									Url:      testServer.URL + "/test.exe",
   235  								},
   236  								{
   237  									Platform: "linux32",
   238  									Url:      testServer.URL + "/test.exe",
   239  								},
   240  								{
   241  									Platform: "linux64",
   242  									Url:      testServer.URL + "/test.exe",
   243  								},
   244  							},
   245  						}
   246  						result := make(map[string][]clipr.Plugin)
   247  						result["repo1"] = []clipr.Plugin{p}
   248  
   249  						config.SetPluginRepo(models.PluginRepo{Name: "repo1", URL: ""})
   250  						fakePluginRepo.GetPluginsReturns(result, nil)
   251  					})
   252  
   253  					AfterEach(func() {
   254  						testServer.Close()
   255  					})
   256  
   257  					It("performs sha1 checksum validation on the downloaded binary", func() {
   258  						runCommand("plugin1", "-r", "repo1", "-f")
   259  						Expect(fakeChecksum.CheckSha1CallCount()).To(Equal(1))
   260  					})
   261  
   262  					It("reports error downloaded file's sha1 does not match the sha1 in metadata", func() {
   263  						fakeChecksum.CheckSha1Returns(false)
   264  
   265  						runCommand("plugin1", "-r", "repo1", "-f")
   266  						Expect(ui.Outputs()).To(ContainSubstrings(
   267  							[]string{"FAILED"},
   268  							[]string{"checksum does not match"},
   269  						))
   270  
   271  					})
   272  
   273  					It("downloads and installs binary when it is available and checksum matches", func() {
   274  						runCommand("plugin1", "-r", "repo1", "-f")
   275  
   276  						Expect(ui.Outputs()).To(ContainSubstrings([]string{"4 bytes downloaded..."}))
   277  						Expect(ui.Outputs()).To(ContainSubstrings([]string{"FAILED"}))
   278  						Expect(ui.Outputs()).To(ContainSubstrings([]string{"Installing plugin"}))
   279  					})
   280  				})
   281  			})
   282  		})
   283  
   284  		Describe("install from plugin repository with no '-r' provided", func() {
   285  			Context("downloads file from internet if path prefix with 'http','ftp' etc...", func() {
   286  				It("will not try locate file locally", func() {
   287  					runCommand("http://127.0.0.1/plugin.exe", "-f")
   288  
   289  					Expect(ui.Outputs()).ToNot(ContainSubstrings(
   290  						[]string{"File not found locally"},
   291  					))
   292  					Expect(ui.Outputs()).To(ContainSubstrings(
   293  						[]string{"download binary file from internet address"},
   294  					))
   295  				})
   296  
   297  				It("informs users when binary is not downloadable from net", func() {
   298  					runCommand("http://path/to/not/a/thing.exe", "-f")
   299  
   300  					Expect(ui.Outputs()).To(ContainSubstrings(
   301  						[]string{"Download attempt failed"},
   302  						[]string{"Unable to install"},
   303  						[]string{"FAILED"},
   304  					))
   305  				})
   306  
   307  				It("downloads and installs binary when it is available", func() {
   308  					h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   309  						fmt.Fprintln(w, "hi")
   310  					})
   311  
   312  					testServer := httptest.NewServer(h)
   313  					defer testServer.Close()
   314  
   315  					runCommand(testServer.URL+"/testfile.exe", "-f")
   316  
   317  					Expect(ui.Outputs()).To(ContainSubstrings([]string{"3 bytes downloaded..."}))
   318  					Expect(ui.Outputs()).To(ContainSubstrings([]string{"FAILED"}))
   319  					Expect(ui.Outputs()).To(ContainSubstrings([]string{"Installing plugin"}))
   320  				})
   321  			})
   322  
   323  			Context("tries to locate binary file at local path if path has no internet prefix", func() {
   324  				It("installs the plugin from a local file if found", func() {
   325  					runCommand("./install_plugin.go", "-f")
   326  
   327  					Expect(ui.Outputs()).ToNot(ContainSubstrings(
   328  						[]string{"download binary file from internet"},
   329  					))
   330  					Expect(ui.Outputs()).To(ContainSubstrings(
   331  						[]string{"Installing plugin install_plugin.go"},
   332  					))
   333  				})
   334  
   335  				It("reports error if local file is not found at given path", func() {
   336  					runCommand("./no/file/is/here.exe", "-f")
   337  
   338  					Expect(ui.Outputs()).To(ContainSubstrings(
   339  						[]string{"File not found locally",
   340  							"./no/file/is/here.exe",
   341  						},
   342  					))
   343  				})
   344  			})
   345  		})
   346  
   347  	})
   348  
   349  	Describe("install failures", func() {
   350  		Context("when the plugin contains a 'help' command", func() {
   351  			It("fails", func() {
   352  				runCommand(test_with_help, "-f")
   353  
   354  				Expect(ui.Outputs()).To(ContainSubstrings(
   355  					[]string{"Command `help` in the plugin being installed is a native CF command/alias.  Rename the `help` command in the plugin being installed in order to enable its installation and use."},
   356  					[]string{"FAILED"},
   357  				))
   358  			})
   359  		})
   360  
   361  		Context("when the plugin's command conflicts with a core command/alias", func() {
   362  			var originalCommand commandregistry.Command
   363  
   364  			BeforeEach(func() {
   365  				originalCommand = commandregistry.Commands.FindCommand("org")
   366  
   367  				commandregistry.Register(testOrgsCmd{})
   368  			})
   369  
   370  			AfterEach(func() {
   371  				if originalCommand != nil {
   372  					commandregistry.Register(originalCommand)
   373  				}
   374  			})
   375  
   376  			It("fails if is shares a command name", func() {
   377  				runCommand(test_with_orgs, "-f")
   378  
   379  				Expect(ui.Outputs()).To(ContainSubstrings(
   380  					[]string{"Command `orgs` in the plugin being installed is a native CF command/alias.  Rename the `orgs` command in the plugin being installed in order to enable its installation and use."},
   381  					[]string{"FAILED"},
   382  				))
   383  			})
   384  
   385  			It("fails if it shares a command short name", func() {
   386  				runCommand(test_with_orgs_short_name, "-f")
   387  
   388  				Expect(ui.Outputs()).To(ContainSubstrings(
   389  					[]string{"Command `o` in the plugin being installed is a native CF command/alias.  Rename the `o` command in the plugin being installed in order to enable its installation and use."},
   390  					[]string{"FAILED"},
   391  				))
   392  			})
   393  		})
   394  
   395  		Context("when the plugin's alias conflicts with a core command/alias", func() {
   396  			var fakeCmd *commandregistryfakes.FakeCommand
   397  			BeforeEach(func() {
   398  				fakeCmd = new(commandregistryfakes.FakeCommand)
   399  			})
   400  
   401  			AfterEach(func() {
   402  				commandregistry.Commands.RemoveCommand("non-conflict-cmd")
   403  				commandregistry.Commands.RemoveCommand("conflict-alias")
   404  			})
   405  
   406  			It("fails if it shares a command name", func() {
   407  				fakeCmd.MetaDataReturns(commandregistry.CommandMetadata{Name: "conflict-alias"})
   408  				commandregistry.Register(fakeCmd)
   409  
   410  				runCommand(aliasConflicts, "-f")
   411  
   412  				Expect(ui.Outputs()).To(ContainSubstrings(
   413  					[]string{"Alias `conflict-alias` in the plugin being installed is a native CF command/alias.  Rename the `conflict-alias` command in the plugin being installed in order to enable its installation and use."},
   414  					[]string{"FAILED"},
   415  				))
   416  			})
   417  
   418  			It("fails if it shares a command short name", func() {
   419  				fakeCmd.MetaDataReturns(commandregistry.CommandMetadata{Name: "non-conflict-cmd", ShortName: "conflict-alias"})
   420  				commandregistry.Register(fakeCmd)
   421  
   422  				runCommand(aliasConflicts, "-f")
   423  
   424  				Expect(ui.Outputs()).To(ContainSubstrings(
   425  					[]string{"Alias `conflict-alias` in the plugin being installed is a native CF command/alias.  Rename the `conflict-alias` command in the plugin being installed in order to enable its installation and use."},
   426  					[]string{"FAILED"},
   427  				))
   428  			})
   429  		})
   430  
   431  		Context("when the plugin's alias conflicts with other installed plugin", func() {
   432  			It("fails if it shares a command name", func() {
   433  				pluginsMap := make(map[string]pluginconfig.PluginMetadata)
   434  				pluginsMap["AliasCollision"] = pluginconfig.PluginMetadata{
   435  					Location: "location/to/config.exe",
   436  					Commands: []plugin.Command{
   437  						{
   438  							Name:     "conflict-alias",
   439  							HelpText: "Hi!",
   440  						},
   441  					},
   442  				}
   443  				pluginConfig.PluginsReturns(pluginsMap)
   444  
   445  				runCommand(aliasConflicts, "-f")
   446  
   447  				Expect(ui.Outputs()).To(ContainSubstrings(
   448  					[]string{"Alias `conflict-alias` is a command/alias in plugin 'AliasCollision'.  You could try uninstalling plugin 'AliasCollision' and then install this plugin in order to invoke the `conflict-alias` command.  However, you should first fully understand the impact of uninstalling the existing 'AliasCollision' plugin."},
   449  					[]string{"FAILED"},
   450  				))
   451  			})
   452  
   453  			It("fails if it shares a command alias", func() {
   454  				pluginsMap := make(map[string]pluginconfig.PluginMetadata)
   455  				pluginsMap["AliasCollision"] = pluginconfig.PluginMetadata{
   456  					Location: "location/to/alias.exe",
   457  					Commands: []plugin.Command{
   458  						{
   459  							Name:     "non-conflict-cmd",
   460  							Alias:    "conflict-alias",
   461  							HelpText: "Hi!",
   462  						},
   463  					},
   464  				}
   465  				pluginConfig.PluginsReturns(pluginsMap)
   466  
   467  				runCommand(aliasConflicts, "-f")
   468  
   469  				Expect(ui.Outputs()).To(ContainSubstrings(
   470  					[]string{"Alias `conflict-alias` is a command/alias in plugin 'AliasCollision'.  You could try uninstalling plugin 'AliasCollision' and then install this plugin in order to invoke the `conflict-alias` command.  However, you should first fully understand the impact of uninstalling the existing 'AliasCollision' plugin."},
   471  					[]string{"FAILED"},
   472  				))
   473  			})
   474  		})
   475  
   476  		Context("when the plugin's command conflicts with other installed plugin", func() {
   477  			It("fails if it shares a command name", func() {
   478  				pluginsMap := make(map[string]pluginconfig.PluginMetadata)
   479  				pluginsMap["Test1Collision"] = pluginconfig.PluginMetadata{
   480  					Location: "location/to/config.exe",
   481  					Commands: []plugin.Command{
   482  						{
   483  							Name:     "test_1_cmd1",
   484  							HelpText: "Hi!",
   485  						},
   486  					},
   487  				}
   488  				pluginConfig.PluginsReturns(pluginsMap)
   489  
   490  				runCommand(test_1, "-f")
   491  
   492  				Expect(ui.Outputs()).To(ContainSubstrings(
   493  					[]string{"Command `test_1_cmd1` is a command/alias in plugin 'Test1Collision'.  You could try uninstalling plugin 'Test1Collision' and then install this plugin in order to invoke the `test_1_cmd1` command.  However, you should first fully understand the impact of uninstalling the existing 'Test1Collision' plugin."},
   494  					[]string{"FAILED"},
   495  				))
   496  			})
   497  
   498  			It("fails if it shares a command alias", func() {
   499  				pluginsMap := make(map[string]pluginconfig.PluginMetadata)
   500  				pluginsMap["AliasCollision"] = pluginconfig.PluginMetadata{
   501  					Location: "location/to/alias.exe",
   502  					Commands: []plugin.Command{
   503  						{
   504  							Name:     "non-conflict-cmd",
   505  							Alias:    "conflict-cmd",
   506  							HelpText: "Hi!",
   507  						},
   508  					},
   509  				}
   510  				pluginConfig.PluginsReturns(pluginsMap)
   511  
   512  				runCommand(aliasConflicts, "-f")
   513  
   514  				Expect(ui.Outputs()).To(ContainSubstrings(
   515  					[]string{"Command `conflict-cmd` is a command/alias in plugin 'AliasCollision'.  You could try uninstalling plugin 'AliasCollision' and then install this plugin in order to invoke the `conflict-cmd` command.  However, you should first fully understand the impact of uninstalling the existing 'AliasCollision' plugin."},
   516  					[]string{"FAILED"},
   517  				))
   518  			})
   519  		})
   520  
   521  		It("if plugin name is already taken", func() {
   522  			pluginConfig.PluginsReturns(map[string]pluginconfig.PluginMetadata{"Test1": {}})
   523  			runCommand(test_1, "-f")
   524  
   525  			Expect(ui.Outputs()).To(ContainSubstrings(
   526  				[]string{"Plugin name", "Test1", "is already taken"},
   527  				[]string{"FAILED"},
   528  			))
   529  		})
   530  
   531  		Context("io", func() {
   532  			BeforeEach(func() {
   533  				err := os.MkdirAll(pluginDir, 0700)
   534  				Expect(err).NotTo(HaveOccurred())
   535  			})
   536  
   537  			It("if a file with the plugin name already exists under ~/.cf/plugin/", func() {
   538  				pluginConfig.PluginsReturns(map[string]pluginconfig.PluginMetadata{"useless": {}})
   539  				pluginConfig.GetPluginPathReturns(curDir)
   540  
   541  				runCommand(filepath.Join(curDir, pluginFile.Name()), "-f")
   542  				Expect(ui.Outputs()).To(ContainSubstrings(
   543  					[]string{"Installing plugin"},
   544  					[]string{"The file", pluginFile.Name(), "already exists"},
   545  					[]string{"FAILED"},
   546  				))
   547  			})
   548  		})
   549  	})
   550  
   551  	Describe("install success", func() {
   552  		BeforeEach(func() {
   553  			err := os.MkdirAll(pluginDir, 0700)
   554  			Expect(err).ToNot(HaveOccurred())
   555  			pluginConfig.GetPluginPathReturns(pluginDir)
   556  		})
   557  
   558  		It("finds plugin in the current directory without having to specify `./`", func() {
   559  			curDir, err := os.Getwd()
   560  			Expect(err).ToNot(HaveOccurred())
   561  
   562  			err = os.Chdir("../../../fixtures/plugins")
   563  			Expect(err).ToNot(HaveOccurred())
   564  
   565  			runCommand(test_curDir, "-f")
   566  			_, err = os.Stat(filepath.Join(pluginDir, "test_1.exe"))
   567  			Expect(err).ToNot(HaveOccurred())
   568  
   569  			err = os.Chdir(curDir)
   570  			Expect(err).ToNot(HaveOccurred())
   571  		})
   572  
   573  		It("copies the plugin into directory <FAKE_HOME_DIR>/.cf/plugins/PLUGIN_FILE_NAME", func() {
   574  			runCommand(test_1, "-f")
   575  
   576  			_, err := os.Stat(test_1)
   577  			Expect(err).ToNot(HaveOccurred())
   578  			_, err = os.Stat(filepath.Join(pluginDir, "test_1.exe"))
   579  			Expect(err).ToNot(HaveOccurred())
   580  		})
   581  
   582  		if runtime.GOOS != "windows" {
   583  			It("Chmods the plugin so it is executable", func() {
   584  				runCommand(test_1, "-f")
   585  
   586  				fileInfo, err := os.Stat(filepath.Join(pluginDir, "test_1.exe"))
   587  				Expect(err).ToNot(HaveOccurred())
   588  				Expect(int(fileInfo.Mode())).To(Equal(0700))
   589  			})
   590  		}
   591  
   592  		It("populate the configuration with plugin metadata", func() {
   593  			runCommand(test_1, "-f")
   594  
   595  			pluginName, pluginMetadata := pluginConfig.SetPluginArgsForCall(0)
   596  
   597  			Expect(pluginName).To(Equal("Test1"))
   598  			Expect(pluginMetadata.Location).To(Equal(filepath.Join(pluginDir, "test_1.exe")))
   599  			Expect(pluginMetadata.Version.Major).To(Equal(1))
   600  			Expect(pluginMetadata.Version.Minor).To(Equal(2))
   601  			Expect(pluginMetadata.Version.Build).To(Equal(4))
   602  			Expect(pluginMetadata.Commands[0].Name).To(Equal("test_1_cmd1"))
   603  			Expect(pluginMetadata.Commands[0].HelpText).To(Equal("help text for test_1_cmd1"))
   604  			Expect(pluginMetadata.Commands[1].Name).To(Equal("test_1_cmd2"))
   605  			Expect(pluginMetadata.Commands[1].HelpText).To(Equal("help text for test_1_cmd2"))
   606  			Expect(ui.Outputs()).To(ContainSubstrings(
   607  				[]string{"Installing plugin test_1.exe"},
   608  				[]string{"OK"},
   609  				[]string{"Plugin", "Test1", "v1.2.4", "successfully installed"},
   610  			))
   611  		})
   612  
   613  		It("installs multiple plugins with no aliases", func() {
   614  			Expect(runCommand(test_1, "-f")).To(Equal(true))
   615  			Expect(runCommand(test_2, "-f")).To(Equal(true))
   616  		})
   617  	})
   618  })
   619  
   620  type testOrgsCmd struct{}
   621  
   622  func (t testOrgsCmd) MetaData() commandregistry.CommandMetadata {
   623  	return commandregistry.CommandMetadata{
   624  		Name:      "orgs",
   625  		ShortName: "o",
   626  	}
   627  }
   628  
   629  func (cmd testOrgsCmd) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) {
   630  	return []requirements.Requirement{}, nil
   631  }
   632  
   633  func (cmd testOrgsCmd) SetDependency(deps commandregistry.Dependency, pluginCall bool) (c commandregistry.Command) {
   634  	return
   635  }
   636  
   637  func (cmd testOrgsCmd) Execute(c flags.FlagContext) error {
   638  	return nil
   639  }