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