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