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