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

     1  package pluginaction_test
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"code.cloudfoundry.org/cli/actor/actionerror"
    10  	. "code.cloudfoundry.org/cli/actor/pluginaction"
    11  	"code.cloudfoundry.org/cli/actor/pluginaction/pluginactionfakes"
    12  	"code.cloudfoundry.org/cli/api/plugin"
    13  	"code.cloudfoundry.org/cli/api/plugin/pluginfakes"
    14  	"code.cloudfoundry.org/cli/util/configv3"
    15  	"code.cloudfoundry.org/cli/util/generic"
    16  	. "github.com/onsi/ginkgo"
    17  	. "github.com/onsi/gomega"
    18  )
    19  
    20  var _ = Describe("install actions", func() {
    21  	var (
    22  		actor         *Actor
    23  		fakeConfig    *pluginactionfakes.FakeConfig
    24  		fakeClient    *pluginactionfakes.FakePluginClient
    25  		tempPluginDir string
    26  	)
    27  
    28  	BeforeEach(func() {
    29  		fakeConfig = new(pluginactionfakes.FakeConfig)
    30  		fakeClient = new(pluginactionfakes.FakePluginClient)
    31  		actor = NewActor(fakeConfig, fakeClient)
    32  
    33  		var err error
    34  		tempPluginDir, err = ioutil.TempDir("", "")
    35  		Expect(err).ToNot(HaveOccurred())
    36  	})
    37  
    38  	AfterEach(func() {
    39  		err := os.RemoveAll(tempPluginDir)
    40  		Expect(err).ToNot(HaveOccurred())
    41  	})
    42  
    43  	Describe("CreateExecutableCopy", func() {
    44  		Context("when the file exists", func() {
    45  			var pluginPath string
    46  
    47  			BeforeEach(func() {
    48  				tempFile, err := ioutil.TempFile("", "")
    49  				Expect(err).ToNot(HaveOccurred())
    50  
    51  				_, err = tempFile.WriteString("cthulhu")
    52  				Expect(err).ToNot(HaveOccurred())
    53  				err = tempFile.Close()
    54  				Expect(err).ToNot(HaveOccurred())
    55  
    56  				pluginPath = tempFile.Name()
    57  			})
    58  
    59  			AfterEach(func() {
    60  				err := os.Remove(pluginPath)
    61  				Expect(err).ToNot(HaveOccurred())
    62  			})
    63  
    64  			It("creates a copy of a file in plugin home", func() {
    65  				copyPath, err := actor.CreateExecutableCopy(pluginPath, tempPluginDir)
    66  				Expect(err).ToNot(HaveOccurred())
    67  
    68  				contents, err := ioutil.ReadFile(copyPath)
    69  				Expect(err).ToNot(HaveOccurred())
    70  				Expect(contents).To(BeEquivalentTo("cthulhu"))
    71  			})
    72  		})
    73  
    74  		Context("when the file does not exist", func() {
    75  			It("returns an os.PathError", func() {
    76  				_, err := actor.CreateExecutableCopy("i-don't-exist", tempPluginDir)
    77  				_, isPathError := err.(*os.PathError)
    78  				Expect(isPathError).To(BeTrue())
    79  			})
    80  		})
    81  	})
    82  
    83  	Describe("DownloadExecutableBinaryFromURL", func() {
    84  		var (
    85  			path            string
    86  			downloadErr     error
    87  			fakeProxyReader *pluginfakes.FakeProxyReader
    88  		)
    89  
    90  		JustBeforeEach(func() {
    91  			fakeProxyReader = new(pluginfakes.FakeProxyReader)
    92  			path, downloadErr = actor.DownloadExecutableBinaryFromURL("some-plugin-url.com", tempPluginDir, fakeProxyReader)
    93  		})
    94  
    95  		Context("when the downloaded is successful", func() {
    96  			var (
    97  				data []byte
    98  			)
    99  
   100  			BeforeEach(func() {
   101  				data = []byte("some test data")
   102  				fakeClient.DownloadPluginStub = func(_ string, path string, _ plugin.ProxyReader) error {
   103  					err := ioutil.WriteFile(path, data, 0700)
   104  					Expect(err).ToNot(HaveOccurred())
   105  					return nil
   106  				}
   107  			})
   108  			It("returns the path to the file and the size", func() {
   109  				Expect(downloadErr).ToNot(HaveOccurred())
   110  				fileData, err := ioutil.ReadFile(path)
   111  				Expect(err).ToNot(HaveOccurred())
   112  				Expect(fileData).To(Equal(data))
   113  
   114  				Expect(fakeClient.DownloadPluginCallCount()).To(Equal(1))
   115  				pluginURL, downloadPath, proxyReader := fakeClient.DownloadPluginArgsForCall(0)
   116  				Expect(pluginURL).To(Equal("some-plugin-url.com"))
   117  				Expect(downloadPath).To(Equal(path))
   118  				Expect(proxyReader).To(Equal(fakeProxyReader))
   119  			})
   120  		})
   121  
   122  		Context("when there is an error downloading file", func() {
   123  			var expectedErr error
   124  
   125  			BeforeEach(func() {
   126  				expectedErr = errors.New("some error")
   127  				fakeClient.DownloadPluginReturns(expectedErr)
   128  			})
   129  
   130  			It("returns the error", func() {
   131  				Expect(downloadErr).To(MatchError(expectedErr))
   132  			})
   133  		})
   134  	})
   135  
   136  	Describe("FileExists", func() {
   137  		var pluginPath string
   138  
   139  		Context("when the file exists", func() {
   140  			BeforeEach(func() {
   141  				pluginFile, err := ioutil.TempFile("", "")
   142  				Expect(err).NotTo(HaveOccurred())
   143  				err = pluginFile.Close()
   144  				Expect(err).NotTo(HaveOccurred())
   145  
   146  				pluginPath = pluginFile.Name()
   147  			})
   148  
   149  			AfterEach(func() {
   150  				err := os.Remove(pluginPath)
   151  				Expect(err).NotTo(HaveOccurred())
   152  			})
   153  
   154  			It("returns true", func() {
   155  				Expect(actor.FileExists(pluginPath)).To(BeTrue())
   156  			})
   157  		})
   158  
   159  		Context("when the file does not exist", func() {
   160  			It("returns false", func() {
   161  				Expect(actor.FileExists("/some/path/that/does/not/exist")).To(BeFalse())
   162  			})
   163  		})
   164  	})
   165  
   166  	Describe("GetAndValidatePlugin", func() {
   167  		var (
   168  			fakePluginMetadata *pluginactionfakes.FakePluginMetadata
   169  			fakeCommandList    *pluginactionfakes.FakeCommandList
   170  			plugin             configv3.Plugin
   171  			validateErr        error
   172  		)
   173  
   174  		BeforeEach(func() {
   175  			fakePluginMetadata = new(pluginactionfakes.FakePluginMetadata)
   176  			fakeCommandList = new(pluginactionfakes.FakeCommandList)
   177  		})
   178  
   179  		JustBeforeEach(func() {
   180  			plugin, validateErr = actor.GetAndValidatePlugin(fakePluginMetadata, fakeCommandList, "some-plugin-path")
   181  		})
   182  
   183  		Context("when getting the plugin metadata returns an error", func() {
   184  			var expectedErr error
   185  
   186  			BeforeEach(func() {
   187  				expectedErr = errors.New("error getting metadata")
   188  				fakePluginMetadata.GetMetadataReturns(configv3.Plugin{}, expectedErr)
   189  			})
   190  
   191  			It("returns a PluginInvalidError", func() {
   192  				Expect(validateErr).To(MatchError(actionerror.PluginInvalidError{Err: expectedErr}))
   193  			})
   194  		})
   195  
   196  		Context("when the plugin name is missing", func() {
   197  			BeforeEach(func() {
   198  				fakePluginMetadata.GetMetadataReturns(configv3.Plugin{}, nil)
   199  			})
   200  
   201  			It("returns a PluginInvalidError", func() {
   202  				Expect(validateErr).To(MatchError(actionerror.PluginInvalidError{}))
   203  			})
   204  		})
   205  
   206  		Context("when the plugin does not have any commands", func() {
   207  			BeforeEach(func() {
   208  				fakePluginMetadata.GetMetadataReturns(configv3.Plugin{Name: "some-plugin"}, nil)
   209  			})
   210  
   211  			It("returns a PluginInvalidError", func() {
   212  				Expect(validateErr).To(MatchError(actionerror.PluginInvalidError{}))
   213  			})
   214  		})
   215  
   216  		Context("when there are command conflicts", func() {
   217  			BeforeEach(func() {
   218  				fakePluginMetadata.GetMetadataReturns(configv3.Plugin{
   219  					Name: "some-plugin",
   220  					Version: configv3.PluginVersion{
   221  						Major: 1,
   222  						Minor: 1,
   223  						Build: 1,
   224  					},
   225  					Commands: []configv3.PluginCommand{
   226  						{Name: "some-other-command", Alias: "soc"},
   227  						{Name: "some-command", Alias: "sc"},
   228  						{Name: "version", Alias: "v"},
   229  						{Name: "p", Alias: "push"},
   230  					},
   231  				}, nil)
   232  			})
   233  
   234  			Context("when the plugin has command names that conflict with native command names", func() {
   235  				BeforeEach(func() {
   236  					fakeCommandList.HasCommandStub = func(commandName string) bool {
   237  						switch commandName {
   238  						case "version":
   239  							return true
   240  						default:
   241  							return false
   242  						}
   243  					}
   244  				})
   245  
   246  				It("returns a PluginCommandsConflictError containing all conflicting command names", func() {
   247  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   248  						PluginName:     "some-plugin",
   249  						PluginVersion:  "1.1.1",
   250  						CommandNames:   []string{"version"},
   251  						CommandAliases: []string{},
   252  					}))
   253  				})
   254  			})
   255  
   256  			Context("when the plugin has command names that conflict with native command aliases", func() {
   257  				BeforeEach(func() {
   258  					fakeCommandList.HasAliasStub = func(commandAlias string) bool {
   259  						switch commandAlias {
   260  						case "p":
   261  							return true
   262  						default:
   263  							return false
   264  						}
   265  					}
   266  				})
   267  
   268  				It("returns a PluginCommandsConflictError containing all conflicting command names", func() {
   269  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   270  						PluginName:     "some-plugin",
   271  						PluginVersion:  "1.1.1",
   272  						CommandNames:   []string{"p"},
   273  						CommandAliases: []string{},
   274  					}))
   275  				})
   276  			})
   277  
   278  			Context("when the plugin has command aliases that conflict with native command names", func() {
   279  				BeforeEach(func() {
   280  					fakeCommandList.HasCommandStub = func(commandName string) bool {
   281  						switch commandName {
   282  						case "push":
   283  							return true
   284  						default:
   285  							return false
   286  						}
   287  					}
   288  				})
   289  
   290  				It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() {
   291  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   292  						PluginName:     "some-plugin",
   293  						PluginVersion:  "1.1.1",
   294  						CommandAliases: []string{"push"},
   295  						CommandNames:   []string{},
   296  					}))
   297  				})
   298  			})
   299  
   300  			Context("when the plugin has command aliases that conflict with native command aliases", func() {
   301  				BeforeEach(func() {
   302  					fakeCommandList.HasAliasStub = func(commandAlias string) bool {
   303  						switch commandAlias {
   304  						case "v":
   305  							return true
   306  						default:
   307  							return false
   308  						}
   309  					}
   310  				})
   311  
   312  				It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() {
   313  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   314  						PluginName:     "some-plugin",
   315  						PluginVersion:  "1.1.1",
   316  						CommandAliases: []string{"v"},
   317  						CommandNames:   []string{},
   318  					}))
   319  				})
   320  			})
   321  
   322  			Context("when the plugin has command names that conflict with existing plugin command names", func() {
   323  				BeforeEach(func() {
   324  					fakeConfig.PluginsReturns([]configv3.Plugin{{
   325  						Name:     "installed-plugin-2",
   326  						Commands: []configv3.PluginCommand{{Name: "some-command"}},
   327  					}})
   328  				})
   329  
   330  				It("returns a PluginCommandsConflictError containing all conflicting command names", func() {
   331  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   332  						PluginName:     "some-plugin",
   333  						PluginVersion:  "1.1.1",
   334  						CommandNames:   []string{"some-command"},
   335  						CommandAliases: []string{},
   336  					}))
   337  				})
   338  			})
   339  
   340  			Context("when the plugin has command names that conflict with existing plugin command aliases", func() {
   341  				BeforeEach(func() {
   342  					fakeConfig.PluginsReturns([]configv3.Plugin{{
   343  						Name:     "installed-plugin-2",
   344  						Commands: []configv3.PluginCommand{{Alias: "some-command"}}},
   345  					})
   346  				})
   347  
   348  				It("returns a PluginCommandsConflictError containing all conflicting command names", func() {
   349  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   350  						PluginName:     "some-plugin",
   351  						PluginVersion:  "1.1.1",
   352  						CommandNames:   []string{"some-command"},
   353  						CommandAliases: []string{},
   354  					}))
   355  				})
   356  			})
   357  
   358  			Context("when the plugin has command aliases that conflict with existing plugin command names", func() {
   359  				BeforeEach(func() {
   360  					fakeConfig.PluginsReturns([]configv3.Plugin{{
   361  						Name:     "installed-plugin-2",
   362  						Commands: []configv3.PluginCommand{{Name: "sc"}}},
   363  					})
   364  				})
   365  
   366  				It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() {
   367  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   368  						PluginName:     "some-plugin",
   369  						PluginVersion:  "1.1.1",
   370  						CommandNames:   []string{},
   371  						CommandAliases: []string{"sc"},
   372  					}))
   373  				})
   374  			})
   375  
   376  			Context("when the plugin has command aliases that conflict with existing plugin command aliases", func() {
   377  				BeforeEach(func() {
   378  					fakeConfig.PluginsReturns([]configv3.Plugin{{
   379  						Name:     "installed-plugin-2",
   380  						Commands: []configv3.PluginCommand{{Alias: "sc"}}},
   381  					})
   382  				})
   383  
   384  				It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() {
   385  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   386  						PluginName:     "some-plugin",
   387  						PluginVersion:  "1.1.1",
   388  						CommandAliases: []string{"sc"},
   389  						CommandNames:   []string{},
   390  					}))
   391  				})
   392  			})
   393  
   394  			Context("when the plugin has command names and aliases that conflict with existing native and plugin command names and aliases", func() {
   395  				BeforeEach(func() {
   396  					fakeConfig.PluginsReturns([]configv3.Plugin{
   397  						{
   398  							Name: "installed-plugin-1",
   399  							Commands: []configv3.PluginCommand{
   400  								{Name: "some-command"},
   401  								{Alias: "some-other-command"},
   402  							},
   403  						},
   404  						{
   405  							Name: "installed-plugin-2",
   406  							Commands: []configv3.PluginCommand{
   407  								{Name: "sc"},
   408  								{Alias: "soc"},
   409  							},
   410  						},
   411  					})
   412  
   413  					fakeCommandList.HasCommandStub = func(commandName string) bool {
   414  						switch commandName {
   415  						case "version", "p":
   416  							return true
   417  						default:
   418  							return false
   419  						}
   420  					}
   421  
   422  					fakeCommandList.HasAliasStub = func(commandAlias string) bool {
   423  						switch commandAlias {
   424  						case "v", "push":
   425  							return true
   426  						default:
   427  							return false
   428  						}
   429  					}
   430  				})
   431  
   432  				It("returns a PluginCommandsConflictError with all conflicting command names and aliases", func() {
   433  					Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{
   434  						PluginName:     "some-plugin",
   435  						PluginVersion:  "1.1.1",
   436  						CommandNames:   []string{"p", "some-command", "some-other-command", "version"},
   437  						CommandAliases: []string{"push", "sc", "soc", "v"},
   438  					}))
   439  				})
   440  			})
   441  
   442  			Context("when the plugin is already installed", func() {
   443  				BeforeEach(func() {
   444  					fakeConfig.PluginsReturns([]configv3.Plugin{{
   445  						Name: "some-plugin",
   446  						Commands: []configv3.PluginCommand{
   447  							{Name: "some-command", Alias: "sc"},
   448  							{Name: "some-other-command", Alias: "soc"},
   449  						},
   450  					}})
   451  				})
   452  
   453  				It("does not return any errors due to command name or alias conflict", func() {
   454  					Expect(validateErr).ToNot(HaveOccurred())
   455  				})
   456  			})
   457  		})
   458  
   459  		Context("when the plugin is valid", func() {
   460  			var pluginToBeInstalled configv3.Plugin
   461  
   462  			BeforeEach(func() {
   463  				pluginToBeInstalled = configv3.Plugin{
   464  					Name: "some-plugin",
   465  					Version: configv3.PluginVersion{
   466  						Major: 1,
   467  						Minor: 1,
   468  						Build: 1,
   469  					},
   470  					Commands: []configv3.PluginCommand{
   471  						{
   472  							Name:  "some-command",
   473  							Alias: "sc",
   474  						},
   475  						{
   476  							Name:  "some-other-command",
   477  							Alias: "soc",
   478  						},
   479  					},
   480  				}
   481  				fakePluginMetadata.GetMetadataReturns(pluginToBeInstalled, nil)
   482  				fakeConfig.PluginsReturns([]configv3.Plugin{
   483  					{
   484  						Name: "installed-plugin-1",
   485  						Commands: []configv3.PluginCommand{
   486  							{
   487  								Name:  "unique-command-1",
   488  								Alias: "uc1",
   489  							},
   490  						},
   491  					},
   492  					{
   493  						Name: "installed-plugin-2",
   494  						Commands: []configv3.PluginCommand{
   495  							{
   496  								Name:  "unique-command-2",
   497  								Alias: "uc2",
   498  							},
   499  							{
   500  								Name:  "unique-command-3",
   501  								Alias: "uc3",
   502  							},
   503  						},
   504  					},
   505  				})
   506  			})
   507  
   508  			It("returns the plugin and no errors", func() {
   509  				Expect(validateErr).ToNot(HaveOccurred())
   510  				Expect(plugin).To(Equal(pluginToBeInstalled))
   511  
   512  				Expect(fakePluginMetadata.GetMetadataCallCount()).To(Equal(1))
   513  				Expect(fakePluginMetadata.GetMetadataArgsForCall(0)).To(Equal("some-plugin-path"))
   514  
   515  				Expect(fakeCommandList.HasCommandCallCount()).To(Equal(4))
   516  				Expect(fakeCommandList.HasCommandArgsForCall(0)).To(Equal("some-command"))
   517  				Expect(fakeCommandList.HasCommandArgsForCall(1)).To(Equal("sc"))
   518  				Expect(fakeCommandList.HasCommandArgsForCall(2)).To(Equal("some-other-command"))
   519  				Expect(fakeCommandList.HasCommandArgsForCall(3)).To(Equal("soc"))
   520  
   521  				Expect(fakeCommandList.HasAliasCallCount()).To(Equal(4))
   522  				Expect(fakeCommandList.HasAliasArgsForCall(0)).To(Equal("some-command"))
   523  				Expect(fakeCommandList.HasAliasArgsForCall(1)).To(Equal("sc"))
   524  				Expect(fakeCommandList.HasAliasArgsForCall(2)).To(Equal("some-other-command"))
   525  				Expect(fakeCommandList.HasAliasArgsForCall(3)).To(Equal("soc"))
   526  
   527  				Expect(fakeConfig.PluginsCallCount()).To(Equal(1))
   528  			})
   529  		})
   530  	})
   531  
   532  	Describe("InstallPluginFromLocalPath", func() {
   533  		var (
   534  			plugin     configv3.Plugin
   535  			installErr error
   536  
   537  			pluginHomeDir string
   538  			pluginPath    string
   539  			tempDir       string
   540  		)
   541  
   542  		BeforeEach(func() {
   543  			plugin = configv3.Plugin{
   544  				Name: "some-plugin",
   545  				Commands: []configv3.PluginCommand{
   546  					{Name: "some-command"},
   547  				},
   548  			}
   549  
   550  			pluginFile, err := ioutil.TempFile("", "")
   551  			Expect(err).NotTo(HaveOccurred())
   552  			err = pluginFile.Close()
   553  			Expect(err).NotTo(HaveOccurred())
   554  
   555  			pluginPath = pluginFile.Name()
   556  
   557  			tempDir, err = ioutil.TempDir("", "")
   558  			Expect(err).ToNot(HaveOccurred())
   559  
   560  			pluginHomeDir = filepath.Join(tempDir, ".cf", "plugin")
   561  		})
   562  
   563  		AfterEach(func() {
   564  			err := os.Remove(pluginPath)
   565  			Expect(err).NotTo(HaveOccurred())
   566  
   567  			err = os.RemoveAll(tempDir)
   568  			Expect(err).NotTo(HaveOccurred())
   569  		})
   570  
   571  		JustBeforeEach(func() {
   572  			installErr = actor.InstallPluginFromPath(pluginPath, plugin)
   573  		})
   574  
   575  		Context("when an error is encountered copying the plugin to the plugin directory", func() {
   576  			BeforeEach(func() {
   577  				fakeConfig.PluginHomeReturns(pluginPath)
   578  			})
   579  
   580  			It("returns the error", func() {
   581  				_, isPathError := installErr.(*os.PathError)
   582  				Expect(isPathError).To(BeTrue())
   583  			})
   584  		})
   585  
   586  		Context("when an error is encountered writing the plugin config to disk", func() {
   587  			var (
   588  				expectedErr error
   589  			)
   590  
   591  			BeforeEach(func() {
   592  				fakeConfig.PluginHomeReturns(pluginHomeDir)
   593  
   594  				expectedErr = errors.New("write config error")
   595  				fakeConfig.WritePluginConfigReturns(expectedErr)
   596  			})
   597  
   598  			It("returns the error", func() {
   599  				Expect(installErr).To(MatchError(expectedErr))
   600  			})
   601  		})
   602  
   603  		Context("when no errors are encountered", func() {
   604  			BeforeEach(func() {
   605  				fakeConfig.PluginHomeReturns(pluginHomeDir)
   606  			})
   607  
   608  			It("makes an executable copy of the plugin file in the plugin directory, updates the plugin config, and writes the config to disk", func() {
   609  				Expect(installErr).ToNot(HaveOccurred())
   610  
   611  				installedPluginPath := generic.ExecutableFilename(filepath.Join(pluginHomeDir, "some-plugin"))
   612  
   613  				Expect(fakeConfig.PluginHomeCallCount()).To(Equal(1))
   614  
   615  				Expect(fakeConfig.AddPluginCallCount()).To(Equal(1))
   616  				Expect(fakeConfig.AddPluginArgsForCall(0)).To(Equal(configv3.Plugin{
   617  					Name: "some-plugin",
   618  					Commands: []configv3.PluginCommand{
   619  						{Name: "some-command"},
   620  					},
   621  					Location: installedPluginPath,
   622  				}))
   623  
   624  				Expect(fakeConfig.WritePluginConfigCallCount()).To(Equal(1))
   625  			})
   626  		})
   627  	})
   628  })