github.com/liamawhite/cli-with-i18n@v6.32.1-0.20171122084555-dede0a5c3448+incompatible/integration/plugin/install_plugin_command_test.go (about)

     1  package plugin
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/liamawhite/cli-with-i18n/integration/helpers"
    13  	"github.com/liamawhite/cli-with-i18n/util/generic"
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  	. "github.com/onsi/gomega/gbytes"
    17  	. "github.com/onsi/gomega/gexec"
    18  	. "github.com/onsi/gomega/ghttp"
    19  )
    20  
    21  var _ = Describe("install-plugin command", func() {
    22  	var (
    23  		buffer     *Buffer
    24  		pluginPath string
    25  	)
    26  
    27  	AfterEach(func() {
    28  		pluginsHomeDirContents, err := ioutil.ReadDir(filepath.Join(homeDir, ".cf", "plugins"))
    29  		if os.IsNotExist(err) {
    30  			return
    31  		}
    32  
    33  		Expect(err).ToNot(HaveOccurred())
    34  
    35  		for _, entry := range pluginsHomeDirContents {
    36  			Expect(entry.Name()).NotTo(ContainSubstring("temp"))
    37  		}
    38  	})
    39  
    40  	Describe("help", func() {
    41  		Context("when the --help flag is given", func() {
    42  			It("displays command usage to stdout", func() {
    43  				session := helpers.CF("install-plugin", "--help")
    44  
    45  				Eventually(session.Out).Should(Say("NAME:"))
    46  				Eventually(session.Out).Should(Say("install-plugin - Install CLI plugin"))
    47  				Eventually(session.Out).Should(Say("USAGE:"))
    48  				Eventually(session.Out).Should(Say("cf install-plugin PLUGIN_NAME \\[-r REPO_NAME\\] \\[-f\\]"))
    49  				Eventually(session.Out).Should(Say("cf install-plugin LOCAL-PATH/TO/PLUGIN | URL \\[-f\\]"))
    50  				Eventually(session.Out).Should(Say("EXAMPLES:"))
    51  				Eventually(session.Out).Should(Say("cf install-plugin ~/Downloads/plugin-foobar"))
    52  				Eventually(session.Out).Should(Say("cf install-plugin https://example.com/plugin-foobar_linux_amd64"))
    53  				Eventually(session.Out).Should(Say("cf install-plugin -r My-Repo plugin-echo"))
    54  				Eventually(session.Out).Should(Say("OPTIONS:"))
    55  				Eventually(session.Out).Should(Say("-f\\s+Force install of plugin without confirmation"))
    56  				Eventually(session.Out).Should(Say("-r\\s+Restrict search for plugin to this registered repository"))
    57  				Eventually(session.Out).Should(Say("SEE ALSO:"))
    58  				Eventually(session.Out).Should(Say("add-plugin-repo, list-plugin-repos, plugins"))
    59  
    60  				Eventually(session).Should(Exit(0))
    61  			})
    62  		})
    63  	})
    64  
    65  	Context("when the user does not provide a plugin name or location", func() {
    66  		It("errors and displays usage", func() {
    67  			session := helpers.CF("install-plugin")
    68  			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `PLUGIN_NAME_OR_LOCATION` was not provided"))
    69  			Eventually(session.Out).Should(Say("USAGE:"))
    70  
    71  			Eventually(session).Should(Exit(1))
    72  		})
    73  	})
    74  
    75  	Describe("installing a plugin from a local file", func() {
    76  		Context("when the file is compiled for a different os and architecture", func() {
    77  			BeforeEach(func() {
    78  				goos := os.Getenv("GOOS")
    79  				goarch := os.Getenv("GOARCH")
    80  
    81  				err := os.Setenv("GOOS", "openbsd")
    82  				Expect(err).ToNot(HaveOccurred())
    83  				err = os.Setenv("GOARCH", "amd64")
    84  				Expect(err).ToNot(HaveOccurred())
    85  
    86  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
    87  					[]helpers.PluginCommand{
    88  						{Name: "some-command", Help: "some-command-help"},
    89  					},
    90  				)
    91  
    92  				err = os.Setenv("GOOS", goos)
    93  				Expect(err).ToNot(HaveOccurred())
    94  				err = os.Setenv("GOARCH", goarch)
    95  				Expect(err).ToNot(HaveOccurred())
    96  			})
    97  
    98  			It("fails and reports the file is not a valid CLI plugin", func() {
    99  				session := helpers.CF("install-plugin", pluginPath, "-f")
   100  
   101  				Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   102  				Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   103  				Eventually(session.Out).Should(Say("FAILED"))
   104  				Eventually(session.Err).Should(Say("File is not a valid cf CLI plugin binary\\."))
   105  
   106  				Eventually(session).Should(Exit(1))
   107  			})
   108  		})
   109  
   110  		Context("when the file is compiled for the correct os and architecture", func() {
   111  			BeforeEach(func() {
   112  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   113  					[]helpers.PluginCommand{
   114  						{Name: "some-command", Help: "some-command-help"},
   115  					},
   116  				)
   117  			})
   118  
   119  			Context("when the -f flag is given", func() {
   120  				It("installs the plugin and cleans up all temp files", func() {
   121  					session := helpers.CF("install-plugin", pluginPath, "-f")
   122  
   123  					Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   124  					Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   125  					Eventually(session.Out).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   126  					Eventually(session.Out).Should(Say("OK"))
   127  					Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   128  
   129  					Eventually(session).Should(Exit(0))
   130  
   131  					installedPath := generic.ExecutableFilename(filepath.Join(homeDir, ".cf", "plugins", "some-plugin"))
   132  
   133  					pluginsSession := helpers.CF("plugins", "--checksum")
   134  					expectedSha := helpers.Sha1Sum(installedPath)
   135  
   136  					Eventually(pluginsSession.Out).Should(Say("some-plugin\\s+1\\.0\\.0\\s+%s", expectedSha))
   137  					Eventually(pluginsSession).Should(Exit(0))
   138  
   139  					Eventually(helpers.CF("some-command")).Should(Exit(0))
   140  
   141  					helpSession := helpers.CF("help")
   142  					Eventually(helpSession.Out).Should(Say("some-command"))
   143  					Eventually(helpSession).Should(Exit(0))
   144  				})
   145  
   146  				Context("when the file does not have executable permissions", func() {
   147  					BeforeEach(func() {
   148  						Expect(os.Chmod(pluginPath, 0666)).ToNot(HaveOccurred())
   149  					})
   150  
   151  					It("installs the plugin", func() {
   152  						session := helpers.CF("install-plugin", pluginPath, "-f")
   153  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   154  						Eventually(session).Should(Exit(0))
   155  					})
   156  				})
   157  
   158  				Context("when the plugin is already installed", func() {
   159  					BeforeEach(func() {
   160  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   161  					})
   162  
   163  					It("uninstalls the existing plugin and installs the plugin", func() {
   164  						session := helpers.CF("install-plugin", pluginPath, "-f")
   165  
   166  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 is already installed\\. Uninstalling existing plugin\\.\\.\\."))
   167  						Eventually(session.Out).Should(Say("CLI-MESSAGE-UNINSTALL"))
   168  						Eventually(session.Out).Should(Say("Plugin some-plugin successfully uninstalled\\."))
   169  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   170  
   171  						Eventually(session).Should(Exit(0))
   172  					})
   173  				})
   174  
   175  				Context("when the file does not exist", func() {
   176  					It("tells the user that the file was not found and fails", func() {
   177  						session := helpers.CF("install-plugin", "some/path/that/does/not/exist", "-f")
   178  						Eventually(session.Err).Should(Say("Plugin some/path/that/does/not/exist not found on disk or in any registered repo\\."))
   179  						Eventually(session.Err).Should(Say("Use 'cf repo-plugins' to list plugins available in the repos\\."))
   180  
   181  						Consistently(session.Out).ShouldNot(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   182  						Consistently(session.Out).ShouldNot(Say("Install and use plugins at your own risk\\."))
   183  
   184  						Eventually(session).Should(Exit(1))
   185  					})
   186  				})
   187  
   188  				Context("when the file is not an executable", func() {
   189  					BeforeEach(func() {
   190  						badPlugin, err := ioutil.TempFile("", "")
   191  						Expect(err).ToNot(HaveOccurred())
   192  						pluginPath = badPlugin.Name()
   193  						err = badPlugin.Close()
   194  						Expect(err).ToNot(HaveOccurred())
   195  					})
   196  
   197  					AfterEach(func() {
   198  						err := os.Remove(pluginPath)
   199  						Expect(err).ToNot(HaveOccurred())
   200  					})
   201  
   202  					It("tells the user that the file is not a plugin and fails", func() {
   203  						session := helpers.CF("install-plugin", pluginPath, "-f")
   204  						Eventually(session.Err).Should(Say("File is not a valid cf CLI plugin binary\\."))
   205  
   206  						Eventually(session).Should(Exit(1))
   207  					})
   208  				})
   209  
   210  				Context("when the file is not a plugin", func() {
   211  					BeforeEach(func() {
   212  						var err error
   213  						pluginPath, err = Build("github.com/liamawhite/cli-with-i18n/integration/assets/non_plugin")
   214  						Expect(err).ToNot(HaveOccurred())
   215  					})
   216  
   217  					It("tells the user that the file is not a plugin and fails", func() {
   218  						session := helpers.CF("install-plugin", pluginPath, "-f")
   219  						Eventually(session.Err).Should(Say("File is not a valid cf CLI plugin binary\\."))
   220  
   221  						Eventually(session).Should(Exit(1))
   222  					})
   223  				})
   224  
   225  				Context("when getting metadata from the plugin errors", func() {
   226  					BeforeEach(func() {
   227  						var err error
   228  						pluginPath, err = Build("github.com/liamawhite/cli-with-i18n/integration/assets/test_plugin_fails_metadata")
   229  						Expect(err).ToNot(HaveOccurred())
   230  					})
   231  
   232  					It("displays the error to stderr", func() {
   233  						session := helpers.CF("install-plugin", pluginPath, "-f")
   234  						Eventually(session.Err).Should(Say("exit status 51"))
   235  						Eventually(session.Err).Should(Say("File is not a valid cf CLI plugin binary\\."))
   236  
   237  						Eventually(session).Should(Exit(1))
   238  					})
   239  				})
   240  
   241  				Context("when there is a command conflict", func() {
   242  					Context("when the plugin has a command that is the same as a built-in command", func() {
   243  						BeforeEach(func() {
   244  							pluginPath = helpers.BuildConfigurablePlugin(
   245  								"configurable_plugin", "some-plugin", "1.1.1",
   246  								[]helpers.PluginCommand{
   247  									{Name: "version"},
   248  								})
   249  						})
   250  
   251  						It("tells the user about the conflict and fails", func() {
   252  							session := helpers.CF("install-plugin", "-f", pluginPath)
   253  
   254  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   255  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   256  
   257  							Eventually(session.Out).Should(Say("FAILED"))
   258  							Eventually(session.Err).Should(Say("Plugin some-plugin v1\\.1\\.1 could not be installed as it contains commands with names that are already used: version"))
   259  
   260  							Eventually(session).Should(Exit(1))
   261  						})
   262  					})
   263  
   264  					Context("when the plugin has a command that is the same as a built-in alias", func() {
   265  						BeforeEach(func() {
   266  							pluginPath = helpers.BuildConfigurablePlugin(
   267  								"configurable_plugin", "some-plugin", "1.1.1",
   268  								[]helpers.PluginCommand{
   269  									{Name: "cups"},
   270  								})
   271  						})
   272  
   273  						It("tells the user about the conflict and fails", func() {
   274  							session := helpers.CF("install-plugin", "-f", pluginPath)
   275  
   276  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   277  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   278  
   279  							Eventually(session.Out).Should(Say("FAILED"))
   280  							Eventually(session.Err).Should(Say("Plugin some-plugin v1\\.1\\.1 could not be installed as it contains commands with names that are already used: cups"))
   281  
   282  							Eventually(session).Should(Exit(1))
   283  						})
   284  					})
   285  
   286  					Context("when the plugin has a command that is the same as another plugin command", func() {
   287  						BeforeEach(func() {
   288  							helpers.InstallConfigurablePlugin("existing-plugin", "1.1.1",
   289  								[]helpers.PluginCommand{
   290  									{Name: "existing-command"},
   291  								})
   292  
   293  							pluginPath = helpers.BuildConfigurablePlugin(
   294  								"configurable_plugin", "new-plugin", "1.1.1",
   295  								[]helpers.PluginCommand{
   296  									{Name: "existing-command"},
   297  								})
   298  						})
   299  
   300  						It("tells the user about the conflict and fails", func() {
   301  							session := helpers.CF("install-plugin", "-f", pluginPath)
   302  
   303  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   304  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   305  
   306  							Eventually(session.Out).Should(Say("FAILED"))
   307  							Eventually(session.Err).Should(Say("Plugin new-plugin v1\\.1\\.1 could not be installed as it contains commands with names that are already used: existing-command\\."))
   308  
   309  							Eventually(session).Should(Exit(1))
   310  						})
   311  					})
   312  
   313  					Context("when the plugin has a command that is the same as another plugin alias", func() {
   314  						BeforeEach(func() {
   315  							helpers.InstallConfigurablePlugin("existing-plugin", "1.1.1",
   316  								[]helpers.PluginCommand{
   317  									{Name: "existing-command"},
   318  								})
   319  
   320  							pluginPath = helpers.BuildConfigurablePlugin(
   321  								"configurable_plugin", "new-plugin", "1.1.1",
   322  								[]helpers.PluginCommand{
   323  									{Name: "new-command", Alias: "existing-command"},
   324  								})
   325  						})
   326  
   327  						It("tells the user about the conflict and fails", func() {
   328  							session := helpers.CF("install-plugin", "-f", pluginPath)
   329  
   330  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   331  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   332  
   333  							Eventually(session.Out).Should(Say("FAILED"))
   334  							Eventually(session.Err).Should(Say("Plugin new-plugin v1\\.1\\.1 could not be installed as it contains commands with aliases that are already used: existing-command\\."))
   335  
   336  							Eventually(session).Should(Exit(1))
   337  						})
   338  					})
   339  				})
   340  
   341  				Context("alias conflict", func() {
   342  					Context("when the plugin has an alias that is the same as a built-in command", func() {
   343  
   344  						BeforeEach(func() {
   345  							pluginPath = helpers.BuildConfigurablePlugin(
   346  								"configurable_plugin", "some-plugin", "1.1.1",
   347  								[]helpers.PluginCommand{
   348  									{Name: "some-command", Alias: "version"},
   349  								})
   350  						})
   351  
   352  						It("tells the user about the conflict and fails", func() {
   353  							session := helpers.CF("install-plugin", "-f", pluginPath)
   354  
   355  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   356  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   357  
   358  							Eventually(session.Out).Should(Say("FAILED"))
   359  							Eventually(session.Err).Should(Say("Plugin some-plugin v1\\.1\\.1 could not be installed as it contains commands with aliases that are already used: version"))
   360  
   361  							Eventually(session).Should(Exit(1))
   362  						})
   363  					})
   364  
   365  					Context("when the plugin has an alias that is the same as a built-in alias", func() {
   366  						BeforeEach(func() {
   367  							pluginPath = helpers.BuildConfigurablePlugin(
   368  								"configurable_plugin", "some-plugin", "1.1.1",
   369  								[]helpers.PluginCommand{
   370  									{Name: "some-command", Alias: "cups"},
   371  								})
   372  						})
   373  
   374  						It("tells the user about the conflict and fails", func() {
   375  							session := helpers.CF("install-plugin", "-f", pluginPath)
   376  
   377  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   378  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   379  
   380  							Eventually(session.Out).Should(Say("FAILED"))
   381  							Eventually(session.Err).Should(Say("Plugin some-plugin v1\\.1\\.1 could not be installed as it contains commands with aliases that are already used: cups"))
   382  
   383  							Eventually(session).Should(Exit(1))
   384  						})
   385  					})
   386  
   387  					Context("when the plugin has an alias that is the same as another plugin command", func() {
   388  						BeforeEach(func() {
   389  							helpers.InstallConfigurablePlugin("existing-plugin", "1.1.1",
   390  								[]helpers.PluginCommand{
   391  									{Name: "existing-command"},
   392  								})
   393  
   394  							pluginPath = helpers.BuildConfigurablePlugin(
   395  								"configurable_plugin", "new-plugin", "1.1.1",
   396  								[]helpers.PluginCommand{
   397  									{Name: "new-command", Alias: "existing-command"},
   398  								})
   399  						})
   400  
   401  						It("tells the user about the conflict and fails", func() {
   402  							session := helpers.CF("install-plugin", "-f", pluginPath)
   403  
   404  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   405  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   406  
   407  							Eventually(session.Out).Should(Say("FAILED"))
   408  							Eventually(session.Err).Should(Say("Plugin new-plugin v1\\.1\\.1 could not be installed as it contains commands with aliases that are already used: existing-command\\."))
   409  
   410  							Eventually(session).Should(Exit(1))
   411  						})
   412  					})
   413  
   414  					Context("when the plugin has an alias that is the same as another plugin alias", func() {
   415  						BeforeEach(func() {
   416  							helpers.InstallConfigurablePlugin("existing-plugin", "1.1.1",
   417  								[]helpers.PluginCommand{
   418  									{Name: "existing-command", Alias: "existing-alias"},
   419  								})
   420  
   421  							pluginPath = helpers.BuildConfigurablePlugin(
   422  								"configurable_plugin", "new-plugin", "1.1.1",
   423  								[]helpers.PluginCommand{
   424  									{Name: "new-command", Alias: "existing-alias"},
   425  								})
   426  						})
   427  
   428  						It("tells the user about the conflict and fails", func() {
   429  							session := helpers.CF("install-plugin", "-f", pluginPath)
   430  
   431  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   432  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   433  
   434  							Eventually(session.Out).Should(Say("FAILED"))
   435  							Eventually(session.Err).Should(Say("Plugin new-plugin v1\\.1\\.1 could not be installed as it contains commands with aliases that are already used: existing-alias\\."))
   436  
   437  							Eventually(session).Should(Exit(1))
   438  						})
   439  					})
   440  				})
   441  
   442  				Context("alias and command conflicts", func() {
   443  					Context("when the plugin has a command and an alias that are both taken by another plugin", func() {
   444  						BeforeEach(func() {
   445  							helpers.InstallConfigurablePlugin("existing-plugin", "1.1.1",
   446  								[]helpers.PluginCommand{
   447  									{Name: "existing-command", Alias: "existing-alias"},
   448  								})
   449  
   450  							pluginPath = helpers.BuildConfigurablePlugin(
   451  								"configurable_plugin", "new-plugin", "1.1.1",
   452  								[]helpers.PluginCommand{
   453  									{Name: "existing-command", Alias: "existing-alias"},
   454  								})
   455  						})
   456  
   457  						It("tells the user about the conflict and fails", func() {
   458  							session := helpers.CF("install-plugin", "-f", pluginPath)
   459  
   460  							Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   461  							Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   462  
   463  							Eventually(session.Out).Should(Say("FAILED"))
   464  							Eventually(session.Err).Should(Say("Plugin new-plugin v1\\.1\\.1 could not be installed as it contains commands with names and aliases that are already used: existing-command, existing-alias\\."))
   465  
   466  							Eventually(session).Should(Exit(1))
   467  						})
   468  					})
   469  				})
   470  			})
   471  
   472  			Context("when the -f flag is not given", func() {
   473  				Context("when the user says yes", func() {
   474  					BeforeEach(func() {
   475  						buffer = NewBuffer()
   476  						_, _ = buffer.Write([]byte("y\n"))
   477  					})
   478  
   479  					It("installs the plugin", func() {
   480  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   481  
   482  						Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   483  						Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   484  						Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: y", helpers.ConvertPathToRegularExpression(pluginPath)))
   485  						Eventually(session.Out).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   486  						Eventually(session.Out).Should(Say("OK"))
   487  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   488  
   489  						Eventually(session).Should(Exit(0))
   490  
   491  						pluginsSession := helpers.CF("plugins", "--checksum")
   492  						expectedSha := helpers.Sha1Sum(
   493  							generic.ExecutableFilename(filepath.Join(homeDir, ".cf/plugins/some-plugin")))
   494  						Eventually(pluginsSession.Out).Should(Say("some-plugin\\s+1.0.0\\s+%s", expectedSha))
   495  						Eventually(pluginsSession).Should(Exit(0))
   496  
   497  						Eventually(helpers.CF("some-command")).Should(Exit(0))
   498  
   499  						helpSession := helpers.CF("help")
   500  						Eventually(helpSession.Out).Should(Say("some-command"))
   501  						Eventually(helpSession).Should(Exit(0))
   502  					})
   503  
   504  					Context("when the plugin is already installed", func() {
   505  						BeforeEach(func() {
   506  							Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   507  						})
   508  
   509  						It("fails and tells the user how to force a reinstall", func() {
   510  							session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   511  
   512  							Eventually(session.Out).Should(Say("FAILED"))
   513  							Eventually(session.Err).Should(Say("Plugin some-plugin 1\\.0\\.0 could not be installed\\. A plugin with that name is already installed\\."))
   514  							Eventually(session.Err).Should(Say("TIP: Use 'cf install-plugin -f' to force a reinstall\\."))
   515  
   516  							Eventually(session).Should(Exit(1))
   517  						})
   518  					})
   519  				})
   520  
   521  				Context("when the user says no", func() {
   522  					BeforeEach(func() {
   523  						buffer = NewBuffer()
   524  						_, _ = buffer.Write([]byte("n\n"))
   525  					})
   526  
   527  					It("does not install the plugin", func() {
   528  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   529  
   530  						Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   531  						Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   532  						Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: n", helpers.ConvertPathToRegularExpression(pluginPath)))
   533  						Eventually(session.Out).Should(Say("Plugin installation cancelled\\."))
   534  
   535  						Eventually(session).Should(Exit(0))
   536  					})
   537  
   538  					Context("when the plugin is already installed", func() {
   539  						BeforeEach(func() {
   540  							Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   541  						})
   542  
   543  						It("does not uninstall the existing plugin", func() {
   544  							session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   545  
   546  							Eventually(session.Out).Should(Say("Plugin installation cancelled\\."))
   547  
   548  							Consistently(session.Out).ShouldNot(Say("Plugin some-plugin 1\\.0\\.0 is already installed\\. Uninstalling existing plugin\\.\\.\\."))
   549  							Consistently(session.Out).ShouldNot(Say("CLI-MESSAGE-UNINSTALL"))
   550  							Consistently(session.Out).ShouldNot(Say("Plugin some-plugin successfully uninstalled\\."))
   551  
   552  							Eventually(session).Should(Exit(0))
   553  						})
   554  					})
   555  				})
   556  
   557  				Context("when the user interrupts with control-c", func() {
   558  					BeforeEach(func() {
   559  						buffer = NewBuffer()
   560  						_, _ = buffer.Write([]byte("y")) // but not enter
   561  					})
   562  
   563  					It("does not install the plugin and does not create a bad state", func() {
   564  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   565  
   566  						Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   567  						Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   568  						Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]:", helpers.ConvertPathToRegularExpression(pluginPath)))
   569  
   570  						session.Interrupt()
   571  
   572  						Eventually(session.Out).Should(Say("FAILED"))
   573  
   574  						// There is a timing issue -- the exit code may be either 1 (processed error), 2 (config writing error), or 130 (Ctrl-C)
   575  						Eventually(session).Should(SatisfyAny(Exit(1), Exit(2), Exit(130)))
   576  
   577  						// make sure cf plugins did not break
   578  						Eventually(helpers.CF("plugins", "--checksum")).Should(Exit(0))
   579  
   580  						// make sure a retry of the plugin install works
   581  						retrySession := helpers.CF("install-plugin", pluginPath, "-f")
   582  						Eventually(retrySession.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   583  						Eventually(retrySession).Should(Exit(0))
   584  					})
   585  				})
   586  			})
   587  		})
   588  	})
   589  
   590  	Describe("installing a plugin from a URL", func() {
   591  		var (
   592  			server *Server
   593  		)
   594  
   595  		BeforeEach(func() {
   596  			server = NewTLSServer()
   597  			// Suppresses ginkgo server logs
   598  			server.HTTPTestServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   599  		})
   600  
   601  		AfterEach(func() {
   602  			server.Close()
   603  		})
   604  
   605  		Context("when a URL and the -f flag are provided", func() {
   606  			Context("when an executable is available for download at the URL", func() {
   607  				var (
   608  					pluginData []byte
   609  				)
   610  
   611  				BeforeEach(func() {
   612  					pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   613  						[]helpers.PluginCommand{
   614  							{Name: "some-command", Help: "some-command-help"},
   615  						},
   616  					)
   617  
   618  					var err error
   619  					pluginData, err = ioutil.ReadFile(pluginPath)
   620  					Expect(err).ToNot(HaveOccurred())
   621  					server.AppendHandlers(
   622  						CombineHandlers(
   623  							VerifyRequest(http.MethodGet, "/"),
   624  							RespondWith(http.StatusOK, pluginData),
   625  						),
   626  					)
   627  				})
   628  
   629  				AfterEach(func() {
   630  					err := os.Remove(pluginPath)
   631  					Expect(err).ToNot(HaveOccurred())
   632  				})
   633  
   634  				It("installs the plugin", func() {
   635  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   636  
   637  					Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   638  					Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   639  
   640  					Eventually(session.Out).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   641  					Eventually(session.Out).Should(Say("\\d.* .*B / ?"))
   642  
   643  					Eventually(session.Out).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   644  					Eventually(session.Out).Should(Say("OK"))
   645  					Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   646  
   647  					Eventually(session).Should(Exit(0))
   648  				})
   649  
   650  				Context("when the URL redirects", func() {
   651  					BeforeEach(func() {
   652  						server.Reset()
   653  						server.AppendHandlers(
   654  							CombineHandlers(
   655  								VerifyRequest(http.MethodGet, "/redirect"),
   656  								RespondWith(http.StatusMovedPermanently, nil, http.Header{"Location": []string{server.URL()}}),
   657  							),
   658  							CombineHandlers(
   659  								VerifyRequest(http.MethodGet, "/"),
   660  								RespondWith(http.StatusOK, pluginData),
   661  							))
   662  					})
   663  
   664  					It("installs the plugin", func() {
   665  						session := helpers.CF("install-plugin", "-f", fmt.Sprintf("%s/redirect", server.URL()), "-k")
   666  
   667  						Eventually(session.Out).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   668  						Eventually(session.Out).Should(Say("OK"))
   669  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   670  
   671  						Eventually(session).Should(Exit(0))
   672  					})
   673  				})
   674  
   675  				Context("when the plugin has already been installed", func() {
   676  					BeforeEach(func() {
   677  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   678  					})
   679  
   680  					It("uninstalls and reinstalls the plugin", func() {
   681  						session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   682  
   683  						Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   684  						Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   685  
   686  						Eventually(session.Out).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   687  						Eventually(session.Out).Should(Say("\\d.* .*B / ?"))
   688  
   689  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 is already installed\\. Uninstalling existing plugin\\.\\.\\."))
   690  						Eventually(session.Out).Should(Say("CLI-MESSAGE-UNINSTALL"))
   691  						Eventually(session.Out).Should(Say("Plugin some-plugin successfully uninstalled\\."))
   692  						Eventually(session.Out).Should(Say("OK"))
   693  						Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   694  
   695  						Eventually(session).Should(Exit(0))
   696  					})
   697  				})
   698  			})
   699  
   700  			Context("when a 4xx or 5xx HTTP response status is encountered", func() {
   701  				BeforeEach(func() {
   702  					server.AppendHandlers(
   703  						CombineHandlers(
   704  							VerifyRequest(http.MethodGet, "/"),
   705  							RespondWith(http.StatusNotFound, nil),
   706  						),
   707  					)
   708  				})
   709  
   710  				It("displays an appropriate error", func() {
   711  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   712  
   713  					Eventually(session.Out).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   714  					Eventually(session.Out).Should(Say("FAILED"))
   715  					Eventually(session.Err).Should(Say("Download attempt failed; server returned 404 Not Found"))
   716  					Eventually(session.Err).Should(Say("Unable to install; plugin is not available from the given URL\\."))
   717  
   718  					Eventually(session).Should(Exit(1))
   719  				})
   720  			})
   721  
   722  			Context("when the file is not a plugin", func() {
   723  				BeforeEach(func() {
   724  					var err error
   725  					pluginPath, err = Build("github.com/liamawhite/cli-with-i18n/integration/assets/non_plugin")
   726  					Expect(err).ToNot(HaveOccurred())
   727  
   728  					pluginData, err := ioutil.ReadFile(pluginPath)
   729  					Expect(err).ToNot(HaveOccurred())
   730  					server.AppendHandlers(
   731  						CombineHandlers(
   732  							VerifyRequest(http.MethodGet, "/"),
   733  							RespondWith(http.StatusOK, pluginData),
   734  						),
   735  					)
   736  				})
   737  
   738  				AfterEach(func() {
   739  					err := os.Remove(pluginPath)
   740  					Expect(err).ToNot(HaveOccurred())
   741  				})
   742  
   743  				It("tells the user that the file is not a plugin and fails", func() {
   744  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   745  
   746  					Eventually(session.Out).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   747  					Eventually(session.Out).Should(Say("FAILED"))
   748  					Eventually(session.Err).Should(Say("File is not a valid cf CLI plugin binary\\."))
   749  
   750  					Eventually(session).Should(Exit(1))
   751  				})
   752  			})
   753  		})
   754  
   755  		Context("when the -f flag is not provided", func() {
   756  			var (
   757  				pluginData []byte
   758  			)
   759  
   760  			BeforeEach(func() {
   761  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   762  					[]helpers.PluginCommand{
   763  						{Name: "some-command", Help: "some-command-help"},
   764  					},
   765  				)
   766  
   767  				var err error
   768  				pluginData, err = ioutil.ReadFile(pluginPath)
   769  				Expect(err).ToNot(HaveOccurred())
   770  				server.AppendHandlers(
   771  					CombineHandlers(
   772  						VerifyRequest(http.MethodGet, "/"),
   773  						RespondWith(http.StatusOK, pluginData),
   774  					),
   775  				)
   776  			})
   777  
   778  			AfterEach(func() {
   779  				err := os.Remove(pluginPath)
   780  				Expect(err).ToNot(HaveOccurred())
   781  			})
   782  
   783  			Context("when the user says yes", func() {
   784  				BeforeEach(func() {
   785  					buffer = NewBuffer()
   786  					_, _ = buffer.Write([]byte("y\n"))
   787  				})
   788  
   789  				It("installs the plugin", func() {
   790  					session := helpers.CFWithStdin(buffer, "install-plugin", server.URL(), "-k")
   791  
   792  					Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   793  					Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   794  					Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: y", server.URL()))
   795  
   796  					Eventually(session.Out).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   797  					Eventually(session.Out).Should(Say("\\d.* .*B / ?"))
   798  
   799  					Eventually(session.Out).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   800  					Eventually(session.Out).Should(Say("OK"))
   801  					Eventually(session.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   802  
   803  					Eventually(session).Should(Exit(0))
   804  				})
   805  
   806  				Context("when the plugin is already installed", func() {
   807  					BeforeEach(func() {
   808  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   809  					})
   810  
   811  					It("fails and tells the user how to force a reinstall", func() {
   812  						session := helpers.CFWithStdin(buffer, "install-plugin", server.URL(), "-k")
   813  
   814  						Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   815  						Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   816  						Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: y", server.URL()))
   817  
   818  						Eventually(session.Out).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   819  						Eventually(session.Out).Should(Say("\\d.* .*B / ?"))
   820  
   821  						Eventually(session.Out).Should(Say("FAILED"))
   822  						Eventually(session.Err).Should(Say("Plugin some-plugin 1\\.0\\.0 could not be installed\\. A plugin with that name is already installed\\."))
   823  						Eventually(session.Err).Should(Say("TIP: Use 'cf install-plugin -f' to force a reinstall\\."))
   824  						Eventually(session).Should(Exit(1))
   825  					})
   826  				})
   827  			})
   828  
   829  			Context("when the user says no", func() {
   830  				BeforeEach(func() {
   831  					buffer = NewBuffer()
   832  					_, _ = buffer.Write([]byte("n\n"))
   833  				})
   834  
   835  				It("does not install the plugin", func() {
   836  					session := helpers.CFWithStdin(buffer, "install-plugin", server.URL())
   837  
   838  					Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   839  					Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   840  					Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: n", server.URL()))
   841  					Eventually(session.Out).Should(Say("Plugin installation cancelled\\."))
   842  
   843  					Eventually(session).Should(Exit(0))
   844  
   845  					Expect(server.ReceivedRequests()).To(HaveLen(0))
   846  				})
   847  			})
   848  
   849  			Context("when the user interrupts with control-c", func() {
   850  				BeforeEach(func() {
   851  					buffer = NewBuffer()
   852  					_, _ = buffer.Write([]byte("y")) // but not enter
   853  				})
   854  
   855  				It("does not install the plugin and does not create a bad state", func() {
   856  					session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   857  
   858  					Eventually(session.Out).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   859  					Eventually(session.Out).Should(Say("Install and use plugins at your own risk\\."))
   860  					Eventually(session.Out).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]:", helpers.ConvertPathToRegularExpression(pluginPath)))
   861  
   862  					session.Interrupt()
   863  
   864  					Eventually(session.Out).Should(Say("FAILED"))
   865  
   866  					// There is a timing issue -- the exit code may be either 1 (processed error), 2 (config writing error), or 130 (Ctrl-C)
   867  					Eventually(session).Should(SatisfyAny(Exit(1), Exit(2), Exit(130)))
   868  
   869  					Expect(server.ReceivedRequests()).To(HaveLen(0))
   870  
   871  					// make sure cf plugins did not break
   872  					Eventually(helpers.CF("plugins", "--checksum")).Should(Exit(0))
   873  
   874  					// make sure a retry of the plugin install works
   875  					retrySession := helpers.CF("install-plugin", pluginPath, "-f")
   876  					Eventually(retrySession.Out).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   877  					Eventually(retrySession).Should(Exit(0))
   878  				})
   879  			})
   880  		})
   881  	})
   882  })