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