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